@trailstash/ultra
Version:
A web based tool for making MapLibre GL maps with data from sources such as Overpass, GeoJSON, GPX, KML, TCX, etc
163 lines (149 loc) • 4.34 kB
JavaScript
const sandboxInit = async (sandboxId, p) => {
const workerInit = (workerId) => {
self.addEventListener(
"message",
async (event) => {
console.debug("worker recived msg from sandbox", event);
let id;
try {
const msg = JSON.parse(event.data);
id = msg.id;
console.debug("parsed message", msg);
const module = await import(
`data:text/javascript;base64,${btoa(msg.module)}`
);
const data = await Promise.resolve(module[msg.exportName](msg.data));
self.postMessage(JSON.stringify({ id, data }));
} catch (e) {
self.postMessage(JSON.stringify({ id, error: e.toString() }));
}
},
false,
);
self.postMessage(JSON.stringify({ id: workerId }));
};
const workerBlob = new Blob([`(${workerInit.toString()})("${sandboxId}");`], {
type: "module",
});
const worker = new Worker(URL.createObjectURL(workerBlob));
const waitForWorkerReady = () =>
new Promise((resolve, reject) => {
const listener = (event) => {
worker.removeEventListener("message", listener, false);
console.debug("sandbox recived msg from worker", event);
try {
const msg = JSON.parse(event.data);
if (msg.id === sandboxId) {
resolve();
return;
}
console.warn("unexpected msg!");
} catch (e) {
reject(e);
return;
}
};
worker.addEventListener("message", listener, false);
});
// wait for ready msg from worker
await waitForWorkerReady();
console.log("worker ready");
// Relay messages from parent to worker
window.addEventListener(
"message",
(event) => {
console.debug("sandbox recieved msg from parent", event);
worker.postMessage(event.data);
},
false,
);
// Relay messages from worker to parent
worker.addEventListener(
"message",
(event) => {
console.debug("sandbox recieved msg from worker", event);
window.parent.postMessage(event.data, p);
},
false,
);
window.parent.postMessage(JSON.stringify({ id: sandboxId }), p);
};
const makeSandbox = () => {
const iframe = window.document.createElement("iframe");
const sandboxId = crypto.randomUUID();
iframe.style.display = "none";
const sandboxBlob = new Blob(
[
`
<!DOCTYPE html>
<script type="module">
(${sandboxInit.toString()})("${sandboxId}", "${window.location.origin}");
</script>
`,
],
{
type: "text/html",
},
);
iframe.src = URL.createObjectURL(sandboxBlob);
const run = (exportName, module, data) => {
const messageID = crypto.randomUUID();
return new Promise((resolve, reject) => {
const listener = (event) => {
console.debug("parent recieved msg from sandbox", event);
try {
const msg = JSON.parse(event.data);
if (msg.id === messageID) {
window.removeEventListener("message", listener, false);
if (msg.error) {
reject(msg.error);
} else {
resolve(msg.data);
}
} else {
console.warn("unexpected msg!");
}
} catch (e) {
console.error(e);
return;
}
};
window.addEventListener("message", listener, false);
iframe.contentWindow.postMessage(
JSON.stringify({
id: messageID,
exportName,
module,
data,
}),
"*",
);
});
};
return new Promise((resolve) => {
const listener = (event) => {
window.removeEventListener("message", listener, false);
console.debug("parent recieved msg from sandbox", event);
try {
const msg = JSON.parse(event.data);
if (msg.id === sandboxId) {
console.log("sandbox ready");
resolve(run);
return;
}
console.warn("unexpected msg!");
} catch (e) {
console.error(e);
return;
}
};
window.addEventListener("message", listener, false);
window.document.body.appendChild(iframe);
});
};
export default makeSandbox;
let sandbox;
try {
sandbox = await makeSandbox();
} catch {}
export { sandbox };