import { Manifest } from "./types.ts"; import { Sandbox } from "./sandbox.ts"; import { System } from "./system.ts"; import { AssetBundle, AssetJson } from "./asset_bundle/bundle.ts"; export class Plug { readonly runtimeEnv?: string; public grantedPermissions: string[] = []; public sandbox: Sandbox; // Resolves once the plug's manifest is available ready: Promise; // Only available after ready resolves public manifest?: Manifest; public assets?: AssetBundle; // Time of last function invocation unloadTimeout?: number; constructor( private system: System, public workerUrl: URL, readonly name: string, private hash: number, private sandboxFactory: (plug: Plug) => Sandbox, ) { this.runtimeEnv = system.env; this.scheduleUnloadTimeout(); this.sandbox = this.sandboxFactory(this); // Retrieve the manifest asynchonously, which may either come from a cache or be loaded from the worker this.ready = system.options.manifestCache!.getManifest(this, this.hash) .then( (manifest) => { this.manifest = manifest; this.assets = new AssetBundle( manifest.assets ? manifest.assets as AssetJson : {}, ); // TODO: These need to be explicitly granted, not just taken this.grantedPermissions = manifest.requiredPermissions || []; }, ); } // Invoke a syscall syscall(name: string, args: any[]): Promise { return this.system.syscallWithContext({ plug: this }, name, args); } // Checks if a function can be invoked (it may be restricted on its execution environment) async canInvoke(name: string) { await this.ready; 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; } scheduleUnloadTimeout() { if (!this.system.options.plugFlushTimeout) { return; } // Reset the unload timeout, if set if (this.unloadTimeout) { clearTimeout(this.unloadTimeout); } this.unloadTimeout = setTimeout(() => { this.stop(); }, this.system.options.plugFlushTimeout); } // Invoke a function async invoke(name: string, args: any[]): Promise { // Ensure the worker is fully up and running await this.ready; this.scheduleUnloadTimeout(); // 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 // deno-lint-ignore no-this-alias let plug: Plug | 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.name); this.sandbox.stop(); } }