1
0
silverbullet/packages/plugos/sandbox.ts

133 lines
3.5 KiB
TypeScript
Raw Normal View History

2022-05-09 12:59:12 +00:00
import type { LogLevel } from "./environments/custom_logger";
import {
ControllerMessage,
WorkerLike,
WorkerMessage,
} from "./environments/worker";
2022-03-25 11:03:06 +00:00
import { Plug } from "./plug";
export type SandboxFactory<HookT> = (plug: Plug<HookT>) => Sandbox;
2022-03-23 14:41:12 +00:00
2022-05-09 12:59:12 +00:00
export type LogEntry = {
level: LogLevel;
message: string;
date: number;
};
2022-03-23 14:41:12 +00:00
export class Sandbox {
protected worker: WorkerLike;
protected reqId = 0;
protected outstandingInits = new Map<string, () => void>();
protected outstandingInvocations = new Map<
number,
{ resolve: (result: any) => void; reject: (e: any) => void }
>();
protected loadedFunctions = new Set<string>();
2022-03-25 11:03:06 +00:00
protected plug: Plug<any>;
2022-05-09 12:59:12 +00:00
public logBuffer: LogEntry[] = [];
public maxLogBufferSize = 100;
2022-03-23 14:41:12 +00:00
2022-03-25 11:03:06 +00:00
constructor(plug: Plug<any>, worker: WorkerLike) {
2022-03-23 14:41:12 +00:00
worker.onMessage = this.onMessage.bind(this);
this.worker = worker;
2022-03-25 11:03:06 +00:00
this.plug = plug;
2022-03-23 14:41:12 +00:00
}
isLoaded(name: string) {
return this.loadedFunctions.has(name);
}
async load(name: string, code: string): Promise<void> {
await this.worker.ready;
2022-03-28 06:51:24 +00:00
let outstandingInit = this.outstandingInits.get(name);
if (outstandingInit) {
// Load already in progress, let's wait for it...
return new Promise((resolve) => {
this.outstandingInits.set(name, () => {
outstandingInit!();
resolve();
});
});
}
2022-03-23 14:41:12 +00:00
this.worker.postMessage({
type: "load",
name: name,
code: code,
} as WorkerMessage);
return new Promise((resolve) => {
2022-03-28 06:51:24 +00:00
this.outstandingInits.set(name, () => {
this.loadedFunctions.add(name);
this.outstandingInits.delete(name);
resolve();
});
2022-03-23 14:41:12 +00:00
});
}
async onMessage(data: ControllerMessage) {
switch (data.type) {
case "inited":
let initCb = this.outstandingInits.get(data.name!);
initCb && initCb();
this.outstandingInits.delete(data.name!);
break;
case "syscall":
try {
2022-03-25 11:03:06 +00:00
let result = await this.plug.syscall(data.name!, data.args!);
2022-03-23 14:41:12 +00:00
this.worker.postMessage({
type: "syscall-response",
id: data.id,
result: result,
} as WorkerMessage);
} catch (e: any) {
2022-03-28 13:25:05 +00:00
// console.error("Syscall fail", e);
2022-03-23 14:41:12 +00:00
this.worker.postMessage({
type: "syscall-response",
id: data.id,
error: e.message,
} as WorkerMessage);
}
break;
case "result":
let resultCbs = this.outstandingInvocations.get(data.id!);
this.outstandingInvocations.delete(data.id!);
if (data.error) {
resultCbs && resultCbs.reject(new Error(data.error));
} else {
resultCbs && resultCbs.resolve(data.result);
}
break;
2022-05-09 12:59:12 +00:00
case "log":
this.logBuffer.push({
level: data.level!,
message: data.message!,
date: Date.now(),
});
if (this.logBuffer.length > this.maxLogBufferSize) {
this.logBuffer.shift();
}
console.log(`[Sandbox ${data.level}]`, data.message);
break;
2022-03-23 14:41:12 +00:00
default:
console.error("Unknown message type", data);
}
}
async invoke(name: string, args: any[]): Promise<any> {
this.reqId++;
this.worker.postMessage({
type: "invoke",
id: this.reqId,
name,
args,
});
return new Promise((resolve, reject) => {
this.outstandingInvocations.set(this.reqId, { resolve, reject });
});
}
stop() {
this.worker.terminate();
}
}