-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun-sandbox.js
More file actions
43 lines (43 loc) · 2.1 KB
/
Copy pathrun-sandbox.js
File metadata and controls
43 lines (43 loc) · 2.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Execute user code in a worker owned by an opaque-origin sandbox, not WordPress.
// Its CSP denies network access, including requests carrying WordPress cookies.
class CodeStudioRunner {
constructor(sourceURL) {
this.sourceURL = sourceURL;
this.stopped = false;
this.frame = document.createElement('iframe');
this.frame.hidden = true;
this.frame.setAttribute('sandbox', 'allow-scripts');
this.frame.setAttribute('aria-hidden', 'true');
this.listener = event => {
if(this.stopped || event.source !== this.frame.contentWindow) return;
this.onmessage?.({data:event.data});
};
window.addEventListener('message',this.listener);
}
async postMessage(payload) {
try {
const response = await fetch(this.sourceURL);
if(!response.ok) throw new Error('Could not load the JavaScript runtime.');
const source = await response.text();
if(this.stopped) return;
const encode = value => JSON.stringify(value).replaceAll('<','\\u003c');
const boot = `
const worker = new Worker(URL.createObjectURL(new Blob([${encode(source)}], {type:'text/javascript'})));
worker.onmessage = event => parent.postMessage(event.data, '*');
worker.onerror = event => { event.preventDefault(); parent.postMessage({type:'error',text:event.message || 'JavaScript runtime error'}, '*'); };
worker.postMessage(${encode(payload)});
addEventListener('message', event => { if(event.source === parent && event.data === 'stop') worker.terminate(); });
`;
this.frame.srcdoc = '<!doctype html><meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob:; worker-src blob:; connect-src 'none';"><script>' + boot + '</script>';
document.body.append(this.frame);
} catch(error) {
if(!this.stopped) this.onerror?.({message:error.message,preventDefault(){}});
}
}
terminate() {
this.stopped = true;
this.frame.contentWindow?.postMessage('stop','*');
this.frame.remove();
window.removeEventListener('message',this.listener);
}
}