@@ -0,0 +1,64 @@
|
||||
export type LogLevel = "info" | "warn" | "error" | "log";
|
||||
|
||||
export class ConsoleLogger {
|
||||
print: boolean;
|
||||
callback: (level: LogLevel, entry: string) => void;
|
||||
|
||||
constructor(
|
||||
callback: (level: LogLevel, entry: string) => void,
|
||||
print: boolean = true
|
||||
) {
|
||||
this.print = print;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
log(...args: any[]): void {
|
||||
this.push("log", args);
|
||||
}
|
||||
|
||||
warn(...args: any[]): void {
|
||||
this.push("warn", args);
|
||||
}
|
||||
|
||||
error(...args: any[]): void {
|
||||
this.push("error", args);
|
||||
}
|
||||
|
||||
info(...args: any[]): void {
|
||||
this.push("info", args);
|
||||
}
|
||||
|
||||
push(level: LogLevel, args: any[]) {
|
||||
this.callback(level, this.logMessage(args));
|
||||
if (this.print) {
|
||||
console[level](...args);
|
||||
}
|
||||
}
|
||||
|
||||
logMessage(values: any[]): string {
|
||||
let pieces: string[] = [];
|
||||
for (let val of values) {
|
||||
switch (typeof val) {
|
||||
case "string":
|
||||
case "number":
|
||||
pieces.push("" + val);
|
||||
break;
|
||||
case "undefined":
|
||||
pieces.push("undefined");
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
let s = JSON.stringify(val, null, 2);
|
||||
if (s.length > 500) {
|
||||
s = s.substring(0, 500) + "...";
|
||||
}
|
||||
pieces.push(s);
|
||||
} catch {
|
||||
// May be cyclical reference
|
||||
pieces.push("[circular object]");
|
||||
}
|
||||
}
|
||||
}
|
||||
return pieces.join(" ");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { safeRun } from "../util.ts";
|
||||
|
||||
// @ts-ignore
|
||||
// import workerCode from "bundle-text:./node_worker.ts";
|
||||
import { Sandbox } from "../sandbox.ts";
|
||||
import { WorkerLike } from "./worker.ts";
|
||||
import { Plug } from "../plug.ts";
|
||||
|
||||
class DenoWorkerWrapper implements WorkerLike {
|
||||
private worker: Worker;
|
||||
onMessage?: (message: any) => Promise<void>;
|
||||
ready: Promise<void>;
|
||||
|
||||
constructor(worker: Worker) {
|
||||
this.worker = worker;
|
||||
this.worker.addEventListener("message", (evt: any) => {
|
||||
let data = evt.data;
|
||||
if (!data) return;
|
||||
safeRun(async () => {
|
||||
await this.onMessage!(data);
|
||||
});
|
||||
});
|
||||
this.ready = Promise.resolve();
|
||||
}
|
||||
postMessage(message: any): void {
|
||||
this.worker.postMessage(message);
|
||||
}
|
||||
|
||||
terminate() {
|
||||
return this.worker.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
export function createSandbox(plug: Plug<any>) {
|
||||
let worker = new Worker(
|
||||
new URL("./sandbox_worker.ts", import.meta.url).href,
|
||||
{
|
||||
type: "module",
|
||||
}
|
||||
);
|
||||
return new Sandbox(plug, new DenoWorkerWrapper(worker));
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { safeRun } from "../util.ts";
|
||||
import { ConsoleLogger } from "./custom_logger.ts";
|
||||
import { ControllerMessage, WorkerMessage } from "./worker.ts";
|
||||
|
||||
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() {
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let loadedFunctions = new Map<string, Function>();
|
||||
let pendingRequests = new Map<
|
||||
number,
|
||||
{
|
||||
resolve: (result: unknown) => void;
|
||||
reject: (e: any) => void;
|
||||
}
|
||||
>();
|
||||
|
||||
function workerPostMessage(msg: ControllerMessage) {
|
||||
if (typeof window !== "undefined" && window.parent !== window) {
|
||||
window.parent.postMessage(msg, "*");
|
||||
} else {
|
||||
self.postMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
function syscall(name: string, ...args: any[]): Promise<any>;
|
||||
// function require(moduleName: string): any;
|
||||
}
|
||||
|
||||
let syscallReqId = 0;
|
||||
|
||||
self.syscall = async (name: string, ...args: any[]) => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
syscallReqId++;
|
||||
pendingRequests.set(syscallReqId, { resolve, reject });
|
||||
workerPostMessage({
|
||||
type: "syscall",
|
||||
id: syscallReqId,
|
||||
name,
|
||||
args,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let loadedModules = new Map<string, any>();
|
||||
|
||||
// @ts-ignore: global to load dynamic imports
|
||||
self.require = (moduleName: string): any => {
|
||||
// console.log("Requiring", moduleName, loadedModules.get(moduleName));
|
||||
const mod = loadedModules.get(moduleName);
|
||||
if (!mod) {
|
||||
throw new Error(
|
||||
`Dynamically importing non-preloaded library ${moduleName}`,
|
||||
);
|
||||
}
|
||||
return mod;
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
self.console = new ConsoleLogger((level, message) => {
|
||||
workerPostMessage({ type: "log", level, message });
|
||||
}, false);
|
||||
|
||||
function wrapScript(code: string) {
|
||||
return `return (${code})["default"]`;
|
||||
}
|
||||
|
||||
self.addEventListener("message", (event: { data: WorkerMessage }) => {
|
||||
safeRun(async () => {
|
||||
let data = event.data;
|
||||
switch (data.type) {
|
||||
case "load":
|
||||
{
|
||||
let fn2 = new Function(wrapScript(data.code!));
|
||||
loadedFunctions.set(data.name!, fn2());
|
||||
workerPostMessage({
|
||||
type: "inited",
|
||||
name: data.name,
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case "load-dependency":
|
||||
{
|
||||
// console.log("Received dep", data.name);
|
||||
let fn3 = new Function(`return ${data.code!}`);
|
||||
let v = fn3();
|
||||
loadedModules.set(data.name!, v);
|
||||
// console.log("Dep val", v);
|
||||
workerPostMessage({
|
||||
type: "dependency-inited",
|
||||
name: data.name,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "invoke":
|
||||
{
|
||||
let fn = loadedFunctions.get(data.name!);
|
||||
if (!fn) {
|
||||
throw new Error(`Function not loaded: ${data.name}`);
|
||||
}
|
||||
try {
|
||||
let result = await Promise.resolve(fn(...(data.args || [])));
|
||||
workerPostMessage({
|
||||
type: "result",
|
||||
id: data.id,
|
||||
result: result,
|
||||
} as ControllerMessage);
|
||||
} catch (e: any) {
|
||||
workerPostMessage({
|
||||
type: "result",
|
||||
id: data.id,
|
||||
error: e.message,
|
||||
stack: e.stack,
|
||||
});
|
||||
// console.error("Error invoking function", data.name, e.message);
|
||||
// throw e;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case "syscall-response":
|
||||
{
|
||||
let syscallId = data.id!;
|
||||
const lookup = pendingRequests.get(syscallId);
|
||||
if (!lookup) {
|
||||
console.log(
|
||||
"Current outstanding requests",
|
||||
pendingRequests,
|
||||
"looking up",
|
||||
syscallId,
|
||||
);
|
||||
throw Error("Invalid request id");
|
||||
}
|
||||
pendingRequests.delete(syscallId);
|
||||
if (data.error) {
|
||||
lookup.reject(new Error(data.error));
|
||||
} else {
|
||||
lookup.resolve(data.result);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { safeRun } from "../util.ts";
|
||||
import { Sandbox } from "../sandbox.ts";
|
||||
import { WorkerLike } from "./worker.ts";
|
||||
import type { Plug } from "../plug.ts";
|
||||
|
||||
class WebWorkerWrapper implements WorkerLike {
|
||||
private worker: Worker;
|
||||
onMessage?: (message: any) => Promise<void>;
|
||||
ready: Promise<void>;
|
||||
|
||||
constructor(worker: Worker) {
|
||||
this.worker = worker;
|
||||
this.worker.addEventListener("message", (evt: any) => {
|
||||
let data = evt.data;
|
||||
if (!data) return;
|
||||
safeRun(async () => {
|
||||
await this.onMessage!(data);
|
||||
});
|
||||
});
|
||||
this.ready = Promise.resolve();
|
||||
}
|
||||
postMessage(message: any): void {
|
||||
this.worker.postMessage(message);
|
||||
}
|
||||
|
||||
terminate() {
|
||||
return this.worker.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
export function createSandbox(plug: Plug<any>) {
|
||||
const worker = new Worker(
|
||||
import.meta.url
|
||||
? new URL("sandbox_worker.ts", import.meta.url)
|
||||
: new URL("worker.js", location.origin),
|
||||
{
|
||||
type: "module",
|
||||
},
|
||||
);
|
||||
return new Sandbox(plug, new WebWorkerWrapper(worker));
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { LogLevel } from "./custom_logger.ts";
|
||||
|
||||
export type ControllerMessageType =
|
||||
| "inited"
|
||||
| "dependency-inited"
|
||||
| "result"
|
||||
| "syscall"
|
||||
| "log";
|
||||
|
||||
export type ControllerMessage = {
|
||||
type: ControllerMessageType;
|
||||
id?: number;
|
||||
name?: string;
|
||||
args?: any[];
|
||||
error?: string;
|
||||
stack?: string;
|
||||
level?: LogLevel;
|
||||
message?: string;
|
||||
result?: any;
|
||||
};
|
||||
|
||||
export interface WorkerLike {
|
||||
ready: Promise<void>;
|
||||
onMessage?: (message: any) => Promise<void>;
|
||||
|
||||
postMessage(message: any): void;
|
||||
|
||||
terminate(): void;
|
||||
}
|
||||
|
||||
export type WorkerMessageType =
|
||||
| "load"
|
||||
| "load-dependency"
|
||||
| "invoke"
|
||||
| "syscall-response";
|
||||
|
||||
export type WorkerMessage = {
|
||||
type: WorkerMessageType;
|
||||
id?: number;
|
||||
name?: string;
|
||||
code?: string;
|
||||
args?: any[];
|
||||
result?: any;
|
||||
error?: any;
|
||||
};
|
||||
Reference in New Issue
Block a user