Track the source path on each Plug and rename the sandbox-factory "cacheKey" to "path" to reflect what it actually is. Fold the one-liner web_worker_sandbox helper into a WorkerSandbox.forPath factory and add system.loadPlug / system.unloadPlug syscalls so individual plugs can be (re)loaded by path — foundation for the upcoming configuration-manager libraries UI.
99 lines
2.9 KiB
TypeScript
99 lines
2.9 KiB
TypeScript
import type { Manifest } from "./types.ts";
|
|
import type { System } from "./system.ts";
|
|
import type { AssetBundle } from "../asset_bundle/bundle.ts";
|
|
import type { Sandbox, SandboxFactory } from "./sandboxes/sandbox.ts";
|
|
|
|
export class Plug<HookT> {
|
|
readonly runtimeEnv?: string;
|
|
|
|
public grantedPermissions: string[] = [];
|
|
public sandbox: Sandbox<HookT>;
|
|
|
|
public manifest!: Manifest<HookT>;
|
|
public assets?: AssetBundle;
|
|
// File path the plug was loaded from; also used as the manifest cache key.
|
|
public path!: string;
|
|
|
|
constructor(
|
|
readonly system: System<HookT>,
|
|
sandboxFactory: SandboxFactory<HookT>,
|
|
) {
|
|
this.runtimeEnv = system.env;
|
|
this.sandbox = sandboxFactory(this);
|
|
}
|
|
|
|
static async createLazily<HookT>(
|
|
system: System<HookT>,
|
|
path: string,
|
|
cacheHash: number,
|
|
sandboxFactory: SandboxFactory<HookT>,
|
|
): Promise<Plug<HookT>> {
|
|
const plug = new Plug(system, sandboxFactory);
|
|
plug.path = path;
|
|
|
|
// Retrieve the manifest, which may either come from a cache or be loaded from the worker
|
|
plug.manifest = await system.options.manifestCache!.getManifest(
|
|
plug,
|
|
path,
|
|
cacheHash,
|
|
);
|
|
|
|
// TODO: These need to be explicitly granted, not just taken
|
|
plug.grantedPermissions = plug.manifest.requiredPermissions || [];
|
|
|
|
return plug;
|
|
}
|
|
|
|
// Invoke a syscall
|
|
syscall(name: string, args: any[]): Promise<any> {
|
|
return this.system.syscall({ plug: this.manifest.name }, name, args);
|
|
}
|
|
|
|
/**
|
|
* Checks if a function can be invoked (it may be restricted on its execution environment)
|
|
*/
|
|
canInvoke(name: string) {
|
|
const funDef = this.manifest!.functions[name];
|
|
if (!funDef) {
|
|
throw new Error(`Function ${name} not found in manifest`);
|
|
}
|
|
return !funDef.env || !this.runtimeEnv || funDef.env === this.runtimeEnv;
|
|
}
|
|
|
|
// Invoke a function
|
|
async invoke(name: string, args: any[]): Promise<any> {
|
|
// Before we access the manifest
|
|
const funDef = this.manifest!.functions[name];
|
|
if (!funDef) {
|
|
throw new Error(`Function ${name} not found in manifest`);
|
|
}
|
|
const sandbox = this.sandbox!;
|
|
if (funDef.redirect) {
|
|
// Function redirect, look up
|
|
let plug: Plug<HookT> | undefined = this;
|
|
if (funDef.redirect.indexOf(".") !== -1) {
|
|
const [plugName, functionName] = funDef.redirect.split(".");
|
|
plug = this.system.loadedPlugs.get(plugName);
|
|
if (!plug) {
|
|
throw Error(`Plug ${plugName} redirected to not found`);
|
|
}
|
|
name = functionName;
|
|
} else {
|
|
name = funDef.redirect;
|
|
}
|
|
return plug.invoke(name, args);
|
|
}
|
|
if (!(await this.canInvoke(name))) {
|
|
throw new Error(
|
|
`Function ${name} is not available in ${this.runtimeEnv}`,
|
|
);
|
|
}
|
|
return sandbox.invoke(name, args);
|
|
}
|
|
|
|
stop() {
|
|
console.log("Stopping sandbox for", this.manifest.name);
|
|
this.sandbox.stop();
|
|
}
|
|
}
|