a2dbf7b3db
* Prep for in-process plug loading (e.g. for CF workers, Deno Deploy) * Prototype of fixed in-process loading plugs * Fix: buttons not to scroll with content * Better positioning of modal especially on mobile * Move query caching outside query * Fix annoying mouse behavior when filter box appears * Page navigator search tweaks
210 lines
5.5 KiB
TypeScript
210 lines
5.5 KiB
TypeScript
// 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<any>;
|
|
}
|
|
|
|
// 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<string, Function>,
|
|
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<ProxyFetchResponse> {
|
|
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<Response> {
|
|
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<string, string>,
|
|
base64Body: encodedBody,
|
|
},
|
|
);
|
|
return new Response(r.base64Body ? base64Decode(r.base64Body) : null, {
|
|
status: r.status,
|
|
headers: r.headers,
|
|
});
|
|
};
|
|
}
|
|
|
|
if (runningAsWebWorker) {
|
|
monkeyPatchFetch();
|
|
}
|