1
0
silverbullet/packages/plugos/system.ts

149 lines
3.8 KiB
TypeScript
Raw Normal View History

2022-04-05 15:02:17 +00:00
import { Hook, Manifest, RuntimeEnvironment } from "./types";
2022-04-25 08:33:38 +00:00
import { EventEmitter } from "./event";
2022-04-05 15:02:17 +00:00
import { SandboxFactory } from "./sandbox";
import { Plug } from "./plug";
2022-03-23 14:41:12 +00:00
2022-03-24 09:48:56 +00:00
export interface SysCallMapping {
2022-03-25 11:03:06 +00:00
[key: string]: (ctx: SyscallContext, ...args: any) => Promise<any> | any;
2022-03-23 14:41:12 +00:00
}
2022-04-26 17:04:36 +00:00
export type SystemJSON<HookT> = Manifest<HookT>[];
2022-03-24 09:48:56 +00:00
2022-03-23 14:41:12 +00:00
export type SystemEvents<HookT> = {
2022-04-26 17:04:36 +00:00
plugLoaded: (plug: Plug<HookT>) => void;
plugUnloaded: (name: string) => void;
2022-03-23 14:41:12 +00:00
};
export type SyscallContext = {
plug: Plug<any>;
2022-03-25 11:03:06 +00:00
};
type SyscallSignature = (
ctx: SyscallContext,
...args: any[]
) => Promise<any> | any;
type Syscall = {
requiredPermissions: string[];
callback: SyscallSignature;
};
2022-03-23 14:41:12 +00:00
export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
readonly runtimeEnv: RuntimeEnvironment;
2022-03-23 14:41:12 +00:00
protected plugs = new Map<string, Plug<HookT>>();
2022-03-25 11:03:06 +00:00
protected registeredSyscalls = new Map<string, Syscall>();
protected enabledHooks = new Set<Hook<HookT>>();
2022-03-23 14:41:12 +00:00
constructor(env: RuntimeEnvironment) {
super();
this.runtimeEnv = env;
}
get loadedPlugs(): Map<string, Plug<HookT>> {
return this.plugs;
}
addHook(feature: Hook<HookT>) {
this.enabledHooks.add(feature);
2022-03-23 14:41:12 +00:00
feature.apply(this);
}
2022-03-25 11:03:06 +00:00
registerSyscalls(
requiredCapabilities: string[],
...registrationObjects: SysCallMapping[]
) {
2022-03-23 14:41:12 +00:00
for (const registrationObject of registrationObjects) {
2022-03-25 11:03:06 +00:00
for (let [name, callback] of Object.entries(registrationObject)) {
this.registeredSyscalls.set(name, {
2022-03-25 11:03:06 +00:00
requiredPermissions: requiredCapabilities,
callback,
});
2022-03-23 14:41:12 +00:00
}
}
}
2022-03-25 11:03:06 +00:00
async syscallWithContext(
ctx: SyscallContext,
name: string,
args: any[]
): Promise<any> {
const syscall = this.registeredSyscalls.get(name);
if (!syscall) {
2022-03-23 14:41:12 +00:00
throw Error(`Unregistered syscall ${name}`);
}
2022-03-25 11:03:06 +00:00
for (const permission of syscall.requiredPermissions) {
if (!ctx.plug) {
throw Error(`Syscall ${name} requires permission and no plug is set`);
}
if (!ctx.plug.grantedPermissions.includes(permission)) {
throw Error(`Missing permission '${permission}' for syscall ${name}`);
}
2022-03-23 14:41:12 +00:00
}
2022-03-25 11:03:06 +00:00
return Promise.resolve(syscall.callback(ctx, ...args));
2022-03-23 14:41:12 +00:00
}
async load(
manifest: Manifest<HookT>,
2022-03-25 11:03:06 +00:00
sandboxFactory: SandboxFactory<HookT>
2022-03-23 14:41:12 +00:00
): Promise<Plug<HookT>> {
2022-04-26 17:04:36 +00:00
const name = manifest.name;
2022-03-23 14:41:12 +00:00
if (this.plugs.has(name)) {
await this.unload(name);
}
// Validate
let errors: string[] = [];
for (const feature of this.enabledHooks) {
2022-03-23 14:41:12 +00:00
errors = [...errors, ...feature.validateManifest(manifest)];
}
if (errors.length > 0) {
throw new Error(`Invalid manifest: ${errors.join(", ")}`);
}
// Ok, let's load this thing!
2022-03-25 11:03:06 +00:00
const plug = new Plug(this, name, sandboxFactory);
2022-04-26 17:04:36 +00:00
console.log("Loading", name);
2022-03-23 14:41:12 +00:00
await plug.load(manifest);
this.plugs.set(name, plug);
2022-04-26 17:04:36 +00:00
this.emit("plugLoaded", plug);
2022-03-23 14:41:12 +00:00
return plug;
}
async unload(name: string) {
2022-04-26 18:31:31 +00:00
// console.log("Unloading", name);
2022-03-23 14:41:12 +00:00
const plug = this.plugs.get(name);
if (!plug) {
throw Error(`Plug ${name} not found`);
}
await plug.stop();
2022-04-26 17:04:36 +00:00
this.emit("plugUnloaded", name);
2022-03-23 14:41:12 +00:00
this.plugs.delete(name);
}
toJSON(): SystemJSON<HookT> {
2022-04-26 17:04:36 +00:00
let plugJSON: Manifest<HookT>[] = [];
2022-03-23 14:41:12 +00:00
for (let [name, plug] of this.plugs) {
if (!plug.manifest) {
continue;
}
2022-04-26 17:04:36 +00:00
plugJSON.push(plug.manifest);
2022-03-23 14:41:12 +00:00
}
return plugJSON;
}
async replaceAllFromJSON(
json: SystemJSON<HookT>,
2022-03-25 11:03:06 +00:00
sandboxFactory: SandboxFactory<HookT>
2022-03-23 14:41:12 +00:00
) {
await this.unloadAll();
2022-04-26 17:04:36 +00:00
for (let manifest of json) {
console.log("Loading plug", manifest.name);
await this.load(manifest, sandboxFactory);
2022-03-23 14:41:12 +00:00
}
}
async unloadAll(): Promise<void[]> {
return Promise.all(
Array.from(this.plugs.keys()).map(this.unload.bind(this))
);
}
}