deno-vm
Version:
A VM module that provides a secure runtime environment via Deno.
147 lines (129 loc) • 4.39 kB
text/typescript
import {
serializeStructure,
deserializeStructure,
Structure,
StructureWithRefs,
} from './StructureClone.ts';
import { MessageEvent, Transferrable } from './MessageTarget.ts';
import {
MessagePort as MessagePortShim,
MessageChannel as MessageChannelShim,
} from './MessageChannel.ts';
const address = Deno.args[0];
const scriptType = Deno.args[1];
const script = Deno.args[2];
let ports = new Map<number | string, MessagePortData>();
init();
async function init() {
const socket = new WebSocket(address);
let onMessage = patchGlobalThis((json) => socket.send(json));
socket.onmessage = (message) => {
onMessage(message.data);
};
socket.onerror = (err) => {
console.error(err);
if (socket.readyState !== WebSocket.CLOSED) {
socket.close();
}
};
socket.onopen = () => {
sendMessage(
{
type: 'init',
},
socket
);
if (scriptType === 'script') {
Function(script)();
} else if (scriptType === 'import') {
import(script);
} else {
throw new Error('Unsupported scrypt type: ' + scriptType);
}
};
}
async function sendMessage(message: any, socket: WebSocket) {
if (socket.readyState !== WebSocket.OPEN) {
return;
}
const structured = serializeStructure(message);
const json = JSON.stringify(structured);
return socket.send(json);
}
function patchGlobalThis(send: (json: string) => void) {
(<any>globalThis).postMessage = (data: any, transfer?: Transferrable[]) =>
postMessage(null, data, transfer);
if (typeof (<any>globalThis).MessageChannel !== 'undefined') {
(<any>globalThis).BuiltinMessageChannel = (<any>(
globalThis
)).MessageChannel;
}
if (typeof (<any>globalThis).MessagePort !== 'undefined') {
(<any>globalThis).BuiltinMessagePort = (<any>globalThis).MessagePort;
}
(<any>globalThis).MessageChannel = MessageChannelShim;
(<any>globalThis).MessagePort = MessagePortShim;
return function onmessage(message: string) {
if (typeof message === 'string') {
const structuredData = JSON.parse(message) as
| Structure
| StructureWithRefs;
const channel = structuredData.channel;
const deserialized = deserializeStructure(structuredData);
const data = deserialized.data;
if (deserialized.transferred) {
handleTransfers(deserialized.transferred);
}
if (typeof channel === 'number' || typeof channel === 'string') {
const portData = ports.get(channel);
if (portData) {
portData.recieveData(data);
} else {
console.log('No Port!');
}
} else {
const event = new MessageEvent('message', {
data,
});
if (typeof (<any>globalThis).onmessage === 'function') {
(<any>globalThis).onmessage(event);
}
globalThis.dispatchEvent(event);
}
}
};
function postMessage(
channel: number | string | null,
data: any,
transfer?: Transferrable[]
): void {
if (transfer) {
handleTransfers(transfer);
}
const structuredData = serializeStructure(data, transfer);
if (typeof channel === 'number' || typeof channel === 'string') {
structuredData.channel = channel;
}
const json = JSON.stringify(structuredData);
send(json);
}
function handleTransfers(transfer?: Transferrable[]) {
if (transfer) {
for (let t of transfer) {
if (t instanceof MessagePortShim) {
const channel = t.channelID;
ports.set(t.channelID, {
port: t,
recieveData: t.transfer((data, list) => {
postMessage(channel, data, list);
}),
});
}
}
}
}
}
interface MessagePortData {
port: MessagePortShim;
recieveData: (data: any) => void;
}