import config from "../config.js"; import { getRpcServer } from "./rpc/server.js"; import type { PluginAPI, RPCMethod, Plugin } from "@lumeweb/relay-types"; import slugify from "slugify"; import * as fs from "fs"; import path from "path"; import { getSavedSsl, getSsl, getSslContext, saveSSl, setSsl, setSSlCheck, setSslContext, } from "./ssl.js"; import log from "loglevel"; import { getSeed } from "../lib/util.js"; import { getRouter, resetRouter, setRouter } from "./app.js"; import { createIndependentFileSmall, openIndependentFileSmall, overwriteIndependentFileSmall, } from "../lib/file"; import { setDnsProvider } from "./dns"; import pluginRpc from "./plugins/rpc"; import pluginCore from "./plugins/core"; let pluginApi: PluginApiManager; const sanitizeName = (name: string) => slugify(name, { lower: true, strict: true }); export class PluginApiManager { private registeredPlugins: Map = new Map(); public async loadPlugin(moduleName: string): Promise { moduleName = sanitizeName(moduleName); if (this.registeredPlugins.has(moduleName)) { return this.registeredPlugins.get(moduleName) as Plugin; } const paths = []; for (const modulePath of [`${moduleName}.js`, `${moduleName}.mjs`]) { const fullPath = path.join(config.get("plugindir"), modulePath); if (fs.existsSync(fullPath)) { paths.push(fullPath); break; } } if (!paths.length) { throw new Error(`Plugin ${moduleName} does not exist`); } let plugin: Plugin; try { plugin = require(paths.shift() as string) as Plugin; } catch (e) { throw e; } return this.loadPluginInstance(plugin); } public async loadPluginInstance(plugin: Plugin): Promise { if ("default" in plugin) { plugin = plugin?.default as Plugin; } plugin.name = sanitizeName(plugin.name); this.registeredPlugins.set(plugin.name, plugin); try { plugin.plugin(this.getPluginAPI(plugin.name)); } catch (e) { throw e; } return plugin; } private getPluginAPI(pluginName: string): PluginAPI { return { config, registerMethod: (methodName: string, method: RPCMethod): void => { getRpcServer().registerMethod(pluginName, methodName, method); }, loadPlugin: getPluginAPI().loadPlugin.bind(getPluginAPI()), getRpcServer, ssl: { setContext: setSslContext, getContext: getSslContext, getSaved: getSavedSsl, set: setSsl, get: getSsl, save: saveSSl, setCheck: setSSlCheck, }, files: { createIndependentFileSmall, openIndependentFileSmall, overwriteIndependentFileSmall, }, dns: { setProvider: setDnsProvider, }, logger: log, getSeed, appRouter: { get: getRouter, set: setRouter, reset: resetRouter, }, }; } } export function getPluginAPI(): PluginApiManager { if (!pluginApi) { pluginApi = new PluginApiManager(); } return pluginApi as PluginApiManager; } export async function loadPlugins() { const api = await getPluginAPI(); api.loadPluginInstance(pluginCore); api.loadPluginInstance(pluginRpc); for (const plugin of [...new Set(config.array("plugins", []))] as []) { api.loadPlugin(plugin); } }