s4s-editor / local-scratch-vm /src /util /sandboxed-javascript-runner.js
soiz1's picture
Upload 811 files
30c32c8 verified
raw
history blame
6.76 kB
const uid = require('./uid');
// probably good
const generateQuadUid = () => uid() + uid() + uid() + uid();
// idk i just copied this lol
const none = "'none'";
const featurePolicy = {
'accelerometer': none,
'ambient-light-sensor': none,
'battery': none,
'camera': none,
'display-capture': none,
'document-domain': none,
'encrypted-media': none,
'fullscreen': none,
'geolocation': none,
'gyroscope': none,
'magnetometer': none,
'microphone': none,
'midi': none,
'payment': none,
'picture-in-picture': none,
'publickey-credentials-get': none,
'speaker-selection': none,
'usb': none,
'vibrate': none,
'vr': none,
'screen-wake-lock': none,
'web-share': none,
'interest-cohort': none
};
// idk i just copied this lol
const generateAllow = () => Object.entries(featurePolicy)
.map(([name, permission]) => `${name} ${permission}`)
.join('; ');
const createFrame = () => {
const element = document.createElement("iframe");
const frameId = generateQuadUid(); // this is how we differentiate iframe messages from other messages
// hopefully pm doesnt do sonme weird stuff that makes this not work lol
// console.log(frameId); // remove later lol
element.dataset.id = frameId;
element.style.display = "none";
element.setAttribute('aria-hidden', 'true');
// allow modals so people can use alert & stuff
element.sandbox = 'allow-scripts allow-modals';
element.allow = generateAllow();
document.body.append(element);
return element;
};
const origin = window.origin;
/**
* vscode give me autofill
* @param {MessageEvent} event
* @param {HTMLIFrameElement} iframe
* @param {Function} removeHandler
* @returns nothing
*/
const messageHandler = (event, iframe, removeHandler) => new Promise(resolve => {
// console.log(event.origin) // remove later
// this might not work first try cuz idk what event.origin is
// if (event.origin !== iframe.contentDocument.location.origin) return;
// yea event origin is just location
// console.log(event.origin, origin)
// why is event.origin null
// ok we arent checking origin because its just null for some reason
// if (event.origin !== origin) return;
// console.log(event.data.payload)
if (!event.data.payload) return;
// console.log({ payload: event.data.payload.id, iframe: iframe.dataset.id })
if (event.data.payload.id !== iframe.dataset.id) return;
const data = event.data.payload;
window.removeEventListener('message', removeHandler);
try {
const url = iframe.src;
// delete object url
URL.revokeObjectURL(url);
} catch {
// honestly idk how this could fail im just doing this incase
// something stupid happens and people cant use eval anymore
console.warn('failed to revoke url of iframe sandboxed eval');
}
iframe.remove();
// send back data
resolve(data);
});
/**
* generates a string that can be placed into the iframe src
* @param {string} code the code
* @returns the code that can be placed into the eval in the iframe src
*/
const prepareCodeForEval = (code) => {
const escaped = JSON.stringify(code);
// when the html encounters a closing script tag, itll end the script
// so just put a backslash before it and it should be fine
const scriptEscaped = escaped.replaceAll('<\/script>', '<\\/script>');
return scriptEscaped;
}
const generateEvaluateSrc = (code, frame) => {
// this puts some funny stuff in the iframe src
// so that it actually works
const runnerCode = `(async () => {
let result = null;
let success = true;
try {
// techincally eval can also postMessage
// and also modify success & result probably
// but theres no real reason to prevent it
// nor does the user have any reason to do it
result = await eval(${prepareCodeForEval(code)});
} catch (err) {
success = false;
result = err;
}
const parent = window.parent;
const origin = '*';
// console.log(result,success);
console.log(origin);
try {
parent.postMessage({
payload: {
success: success,
value: result,
id: ${JSON.stringify(frame.dataset.id)}
},
}, origin);
} catch (topLevelError) {
// couldnt clone likely
try {
parent.postMessage({
payload: {
success: success,
value: JSON.stringify(result),
id: ${JSON.stringify(frame.dataset.id)}
},
}, origin);
} catch (err) {
// ok we cant stringify it just error
parent.postMessage({
payload: {
success: false,
value: [String(topLevelError), String(err)].join("; "),
id: ${JSON.stringify(frame.dataset.id)}
},
}, origin);
}
}
})();`;
const html = [
'<!DOCTYPE html>',
'<html lang="en-US">',
// the html head isnt required i just think its sily to add and shouldnt affect anything
'<head>',
'<title>the an one of an iframe</title>',
'</head>',
// same story with adding actual elements
'<body>',
'<h1><p>epic computing in progress...</p></h1>',
// removed for being not cool!
// '<img src="https://media.tenor.com/jXQiJUuqfM8AAAAd/type-emoji.gif">',
'<script>',
runnerCode,
'</script>',
'</body>',
'</html>'
].join("\n");
const blob = new Blob([html], { type: 'text/html;charset=UTF-8' });
const url = URL.createObjectURL(blob);
return url;
};
class SandboxRunner {
static execute(code) {
return new Promise(resolve => {
const frame = createFrame();
/**
* please vscode show me the autofill
* @param {MessageEvent} e
*/
const trueHandler = e => {
// this code is weird but we need to remove
// event handler ladter
// console.log(e); // debug
messageHandler(e, frame, trueHandler).then(payload => {
// console.log(payload)
resolve({
success: payload.success,
value: payload.value
});
});
};
window.addEventListener('message', trueHandler);
frame.src = generateEvaluateSrc(code, frame);
});
}
}
module.exports = SandboxRunner;