// This is the runtime imported from the compiled plug worker code import type { ControllerMessage, WorkerMessage } from "./protocol.ts"; import type { ProxyFetchRequest, ProxyFetchResponse, } from "../common/proxy_fetch.ts"; declare global { function syscall(name: string, ...args: any[]): Promise; } // Are we running in a (web) worker? // Determines if we're running in a web worker environment (Deno or browser) // - in a browser's main threads, typeof window is "object" // - in a browser's worker threads, typeof window === "undefined" // - in Deno's main thread typeof window === "object" // - in Deno's workers typeof window === "undefined // - in Cloudflare workers typeof window === "undefined", but typeof globalThis.WebSocketPair is defined const runningAsWebWorker = typeof window === "undefined" && // @ts-ignore: globalThis typeof globalThis.WebSocketPair === "undefined"; // console.log("Running as web worker:", runningAsWebWorker); if (typeof Deno === "undefined") { // @ts-ignore: Deno hack self.Deno = { args: [], // @ts-ignore: Deno hack build: { arch: "x86_64", }, env: { // @ts-ignore: Deno hack get() { }, }, }; } const pendingRequests = new Map< number, { resolve: (result: unknown) => void; reject: (e: any) => void; } >(); let syscallReqId = 0; function workerPostMessage(msg: ControllerMessage) { self.postMessage(msg); } if (runningAsWebWorker) { globalThis.syscall = async (name: string, ...args: any[]) => { return await new Promise((resolve, reject) => { syscallReqId++; pendingRequests.set(syscallReqId, { resolve, reject }); workerPostMessage({ type: "sys", id: syscallReqId, name, args, }); }); }; } export function setupMessageListener( // deno-lint-ignore ban-types functionMapping: Record, manifest: any, ) { if (!runningAsWebWorker) { // Don't do any of this stuff if this is not a web worker // This caters to the NoSandbox run mode return; } self.addEventListener("message", (event: { data: WorkerMessage }) => { (async () => { const data = event.data; switch (data.type) { case "inv": { const fn = functionMapping[data.name!]; if (!fn) { throw new Error(`Function not loaded: ${data.name}`); } try { const result = await Promise.resolve(fn(...(data.args || []))); workerPostMessage({ type: "invr", id: data.id, result: result, } as ControllerMessage); } catch (e: any) { console.error( "An exception was thrown as a result of invoking function", data.name, "error:", e, ); workerPostMessage({ type: "invr", id: data.id!, error: e.message, }); } } break; case "sysr": { const syscallId = data.id; const lookup = pendingRequests.get(syscallId); if (!lookup) { throw Error("Invalid request id"); } pendingRequests.delete(syscallId); if (data.error) { lookup.reject(new Error(data.error)); } else { lookup.resolve(data.result); } } break; } })().catch(console.error); }); // Signal initialization with manifest workerPostMessage({ type: "manifest", manifest, }); } export function base64Decode(s: string): Uint8Array { const binString = atob(s); const len = binString.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binString.charCodeAt(i); } return bytes; } export function base64Encode(buffer: Uint8Array | string): string { if (typeof buffer === "string") { buffer = new TextEncoder().encode(buffer); } let binary = ""; const len = buffer.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(buffer[i]); } return btoa(binary); } export async function sandboxFetch( reqInfo: RequestInfo, options?: ProxyFetchRequest, ): Promise { if (typeof reqInfo !== "string") { const body = new Uint8Array(await reqInfo.arrayBuffer()); const encodedBody = body.length > 0 ? base64Encode(body) : undefined; options = { method: reqInfo.method, headers: Object.fromEntries(reqInfo.headers.entries()), base64Body: encodedBody, }; reqInfo = reqInfo.url; } return syscall("sandboxFetch.fetch", reqInfo, options); } // @ts-ignore: monkey patching fetch globalThis.nativeFetch = globalThis.fetch; // Monkey patch fetch() export function monkeyPatchFetch() { // @ts-ignore: monkey patching fetch globalThis.fetch = async function ( reqInfo: RequestInfo, init?: RequestInit, ): Promise { const encodedBody = init && init.body ? base64Encode( new Uint8Array(await (new Response(init.body)).arrayBuffer()), ) : undefined; const r = await sandboxFetch( reqInfo, init && { method: init.method, headers: init.headers as Record, base64Body: encodedBody, }, ); return new Response(r.base64Body ? base64Decode(r.base64Body) : null, { status: r.status, headers: r.headers, }); }; } if (runningAsWebWorker) { monkeyPatchFetch(); }