relay/src/modules/plugin.ts

259 lines
6.0 KiB
TypeScript
Raw Normal View History

2022-09-21 12:59:22 +00:00
import config from "../config.js";
import type { RPCServer } from "./rpc/server.js";
import { getRpcServer } from "./rpc/server.js";
import type { Plugin, RPCMethod } from "@lumeweb/relay-types";
import slugify from "slugify";
2022-08-28 04:26:24 +00:00
import * as fs from "fs";
import path from "path";
2022-12-19 16:47:09 +00:00
import type { Logger } from "pino";
import { getHDKey, getSeed } from "../lib/seed.js";
import type Config from "@lumeweb/cfg";
import EventEmitter2 from "eventemitter2";
2022-12-19 16:47:09 +00:00
import log from "../log.js";
import {
get as getSwarm,
getProtocolManager,
ProtocolManager,
} from "./swarm.js";
2023-04-19 02:47:09 +00:00
import { get as getApp } from "./app.js";
import type { HDKey } from "micro-ed25519-hdkey";
2023-01-08 04:02:13 +00:00
import corePlugins from "../plugins";
import Util from "./plugin/util";
let pluginAPIManager: PluginAPIManager;
let pluginAPI: PluginAPI;
const sanitizeName = (name: string) =>
slugify(name, { lower: true, strict: true });
class PluginAPI extends EventEmitter2 {
private _server: RPCServer;
constructor({
config,
server,
2022-12-18 20:01:27 +00:00
swarm,
}: {
config: Config;
server: RPCServer;
2022-12-18 20:01:27 +00:00
swarm: any;
}) {
super({
wildcard: true,
verboseMemoryLeak: true,
maxListeners: 0,
});
this._config = config;
this._server = server;
2022-12-18 20:01:27 +00:00
this._swarm = swarm;
}
private _util: Util = new Util();
get util(): Util {
return this._util;
}
2022-12-18 20:01:27 +00:00
private _swarm: any;
get swarm(): any {
return this._swarm;
}
private _config: Config;
get config(): Config {
return this._config;
}
get pluginConfig(): Config {
throw new Error("not implemented and should not be called");
}
get logger(): Logger {
throw new Error("not implemented and should not be called");
}
get rpcServer(): RPCServer {
return this._server;
}
2022-12-18 20:01:27 +00:00
get seed(): Uint8Array {
return getSeed();
}
get identity(): HDKey {
return getHDKey();
}
get protocols(): ProtocolManager {
return getProtocolManager();
}
2023-04-19 02:47:09 +00:00
get app() {
return getApp();
}
public loadPlugin(
moduleName: string
): (moduleName: string) => Promise<Plugin> {
return getPluginAPIManager().loadPlugin;
}
registerMethod(methodName: string, method: RPCMethod): void {
throw new Error("not implemented and should not be called");
}
}
export function getPluginAPI(): PluginAPI {
if (!pluginAPI) {
2022-12-18 20:01:27 +00:00
pluginAPI = new PluginAPI({
config,
server: getRpcServer(),
swarm: getSwarm(),
});
}
return pluginAPI as PluginAPI;
}
export class PluginAPIManager {
private registeredPlugins: Map<string, Plugin> = new Map<string, Plugin>();
public async loadPlugin(moduleName: string): Promise<Plugin> {
moduleName = sanitizeName(moduleName);
if (this.registeredPlugins.has(moduleName)) {
return this.registeredPlugins.get(moduleName) as Plugin;
}
2022-08-28 04:26:24 +00:00
const paths = [];
for (const modulePath of [`${moduleName}.js`, `${moduleName}.mjs`]) {
2023-04-19 00:12:01 +00:00
const fullPath = path.join(config.get("core.plugindir"), modulePath);
2022-08-28 04:26:24 +00:00
if (fs.existsSync(fullPath)) {
paths.push(fullPath);
break;
}
}
if (!paths.length) {
throw new Error(`Plugin ${moduleName} does not exist`);
}
let plugin: Plugin;
2023-04-19 04:01:14 +00:00
let pluginPath = paths.shift();
try {
2023-04-19 04:01:14 +00:00
plugin = require(pluginPath as string) as Plugin;
} catch (e) {
throw e;
}
2023-01-08 19:13:11 +00:00
log.debug("Loaded plugin %s", moduleName);
2023-04-19 04:01:14 +00:00
const instance = await this.loadPluginInstance(plugin);
if (!instance) {
throw new Error(`Corrupt plugin found at ${pluginPath}`);
}
return instance as Plugin;
}
2023-04-19 04:01:14 +00:00
public async loadPluginInstance(plugin: Plugin): Promise<Plugin | boolean> {
if ("default" in plugin) {
plugin = plugin?.default as Plugin;
}
2023-04-19 04:01:14 +00:00
if (!("name" in plugin)) {
return false;
}
plugin.name = sanitizeName(plugin.name);
this.registeredPlugins.set(plugin.name, plugin);
try {
2023-04-19 04:08:19 +00:00
await plugin.plugin(
// @ts-ignore
new Proxy<PluginAPI>(getPluginAPI(), {
get(target: PluginAPI, prop: string): any {
if (prop === "registerMethod") {
return (methodName: string, method: RPCMethod): void => {
return getRpcServer().registerMethod(
plugin.name,
methodName,
method
);
};
}
if (prop === "pluginConfig") {
return new Proxy<Config>(config, {
get(target: Config, prop: string): any {
if (prop === "set") {
return (key: string, value: any): void => {
target.set(`plugin.${plugin.name}.${key}`, value);
};
}
if (prop === "get") {
return (key: string, fallback = null): any => {
return target.get(
`plugin.${plugin.name}.${key}`,
fallback
);
};
}
if (prop === "has") {
return (key: string): any => {
return target.has(`plugin.${plugin.name}.${key}`);
};
}
return (target as any)[prop];
},
});
}
if (prop === "logger") {
2023-01-13 22:53:54 +00:00
return log.child({ plugin: plugin.name });
}
return (target as any)[prop];
},
})
);
} catch (e) {
throw e;
}
2023-01-08 19:13:11 +00:00
log.debug("Initialized plugin %s", plugin.name);
return plugin;
}
}
export function getPluginAPIManager(): PluginAPIManager {
if (!pluginAPIManager) {
pluginAPIManager = new PluginAPIManager();
}
return pluginAPIManager as PluginAPIManager;
}
export async function loadPlugins() {
2022-12-18 16:42:50 +00:00
const apiManager = getPluginAPIManager();
2023-01-08 04:02:13 +00:00
for (const plugin of corePlugins) {
await apiManager.loadPluginInstance(plugin);
}
2023-04-19 00:12:01 +00:00
for (const plugin of [...new Set(config.array("core.plugins", []))] as []) {
2022-12-18 16:42:50 +00:00
await apiManager.loadPlugin(plugin);
}
2022-12-18 16:42:50 +00:00
getPluginAPI().emit("core.pluginsLoaded");
}