1
0
silverbullet/plugos/sandbox.ts

162 lines
4.4 KiB
TypeScript
Raw Normal View History

import type { LogLevel } from "./environments/custom_logger.ts";
2022-05-09 12:59:12 +00:00
import {
ControllerMessage,
WorkerLike,
WorkerMessage,
} from "./environments/worker.ts";
import { Plug } from "./plug.ts";
2022-03-25 11:03:06 +00:00
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>();
2022-05-13 12:36:26 +00:00
protected outstandingDependencyInits = new Map<string, () => void>();
2022-03-23 14:41:12 +00:00
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-10-15 17:02:56 +00:00
const outstandingInit = this.outstandingInits.get(name);
2022-03-28 06:51:24 +00:00
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
});
}
loadDependency(name: string, code: string): Promise<void> {
2022-05-13 12:36:26 +00:00
// console.log("Loading dependency", name);
this.worker.postMessage({
type: "load-dependency",
name: name,
code: code,
} as WorkerMessage);
return new Promise((resolve) => {
// console.log("Loaded dependency", name);
this.outstandingDependencyInits.set(name, () => {
this.outstandingDependencyInits.delete(name);
resolve();
});
});
}
2022-03-23 14:41:12 +00:00
async onMessage(data: ControllerMessage) {
switch (data.type) {
2022-10-15 17:02:56 +00:00
case "inited": {
const initCb = this.outstandingInits.get(data.name!);
2022-03-23 14:41:12 +00:00
initCb && initCb();
this.outstandingInits.delete(data.name!);
break;
2022-10-15 17:02:56 +00:00
}
case "dependency-inited": {
const depInitCb = this.outstandingDependencyInits.get(data.name!);
2022-05-13 12:36:26 +00:00
depInitCb && depInitCb();
this.outstandingDependencyInits.delete(data.name!);
break;
2022-10-15 17:02:56 +00:00
}
2022-03-23 14:41:12 +00:00
case "syscall":
try {
2022-10-15 17:02:56 +00:00
const 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;
2022-10-15 17:02:56 +00:00
case "result": {
const resultCbs = this.outstandingInvocations.get(data.id!);
2022-03-23 14:41:12 +00:00
this.outstandingInvocations.delete(data.id!);
if (data.error) {
2022-05-13 15:05:52 +00:00
resultCbs &&
resultCbs.reject(
new Error(`${data.error}\nStack trace: ${data.stack}`),
2022-05-13 15:05:52 +00:00
);
2022-03-23 14:41:12 +00:00
} else {
resultCbs && resultCbs.resolve(data.result);
}
break;
2022-10-15 17:02:56 +00:00
}
case "log": {
2022-05-09 12:59:12 +00:00
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-10-15 17:02:56 +00:00
}
2022-03-23 14:41:12 +00:00
default:
console.error("Unknown message type", data);
}
}
2022-10-15 17:02:56 +00:00
invoke(name: string, args: any[]): Promise<any> {
2022-03-23 14:41:12 +00:00
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();
}
}