@casual-simulation/aux-vm-browser
Version:
A set of utilities required to securely run an AUX in a web browser.
122 lines • 4.45 kB
JavaScript
/* CasualOS is a set of web-based tools designed to facilitate the creation of real-time, multi-user, context-aware interactive experiences.
*
* Copyright (c) 2019-2025 Casual Simulation, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const _bootstrapFnSource = function _bootstrapFn(workerUrl) {
const listener = (event) => {
// uninstall handler
self.removeEventListener('message', listener);
// get data
const port = event.data;
// postMessage
// onmessage
Object.defineProperties(self, {
postMessage: {
value(data, transferOrOptions) {
port.postMessage(data, transferOrOptions);
},
},
onmessage: {
get() {
return port.onmessage;
},
set(value) {
port.onmessage = value;
},
},
// todo onerror
});
port.addEventListener('message', (msg) => {
self.dispatchEvent(new MessageEvent('message', { data: msg.data }));
});
port.start();
// fake recursively nested worker
self.Worker = class {
constructor() {
throw new TypeError('Nested workers from within nested worker are not supported.');
}
};
// load module
importScripts(workerUrl);
};
self.addEventListener('message', listener);
}.toString();
export class NestedWorker extends EventTarget {
constructor(nativePostMessage, stringOrUrl, options) {
super();
this.onmessage = null;
this.onmessageerror = null;
this.onerror = null;
// create bootstrap script
const bootstrap = `((${_bootstrapFnSource})('${stringOrUrl}'))`;
const blob = new Blob([bootstrap], { type: 'application/javascript' });
const blobUrl = URL.createObjectURL(blob);
const channel = new MessageChannel();
const id = blobUrl; // works because blob url is unique, needs ID pool otherwise
const msg = {
type: 'new_worker',
id,
port: channel.port2,
url: blobUrl,
options,
};
nativePostMessage(msg, [channel.port2]);
// worker-impl: functions
this.postMessage = channel.port1.postMessage.bind(channel.port1);
this.terminate = () => {
const msg = {
type: 'terminate_worker',
id,
};
channel.port1.postMessage(msg);
URL.revokeObjectURL(blobUrl);
channel.port1.close();
channel.port2.close();
};
// worker-impl: events
Object.defineProperties(this, {
onmessage: {
get() {
return channel.port1.onmessage;
},
set(value) {
channel.port1.onmessage = value;
},
},
onmessageerror: {
get() {
return channel.port1.onmessageerror;
},
set(value) {
channel.port1.onmessageerror = value;
},
},
// todo onerror
});
channel.port1.addEventListener('messageerror', (evt) => {
const msgEvent = new MessageEvent('messageerror', {
data: evt.data,
});
this.dispatchEvent(msgEvent);
});
channel.port1.addEventListener('message', (evt) => {
const msgEvent = new MessageEvent('message', { data: evt.data });
this.dispatchEvent(msgEvent);
});
channel.port1.start();
}
}
//# sourceMappingURL=NestedWorker.js.map