Rework plugin loading

Plugins must now provide an `info` export which includes their ID, name,
and a description (I'm flexible on the last two). The ID is used for the
API route--all plugin-registered API routes will be accessed through
`plugins/[plugin ID]`.
This commit is contained in:
valadaptive 2023-12-17 12:21:05 -05:00
parent a88cf1552a
commit 4fcb7b5ea4

View File

@ -1,5 +1,6 @@
const fs = require('fs');
const path = require('path');
const express = require('express');
const { getConfigValue } = require('./util');
const enableServerPlugins = getConfigValue('enableServerPlugins', false);
@ -127,6 +128,15 @@ async function loadFromFile(app, pluginFilePath) {
}
}
/**
* Check whether a plugin ID is valid (only lowercase alphanumeric, hyphens, and underscores).
* @param {string} id The plugin ID to check
* @returns {boolean} True if the plugin ID is valid.
*/
function isValidPluginID(id) {
return /^[a-z0-9_-]$/.test(id);
}
/**
* Initializes a plugin module.
* @param {import('express').Express} app Express app
@ -134,12 +144,42 @@ async function loadFromFile(app, pluginFilePath) {
* @returns {Promise<boolean>} Promise that resolves to true if plugin was initialized successfully
*/
async function initPlugin(app, plugin) {
if (typeof plugin.init === 'function') {
await plugin.init(app);
return true;
if (typeof plugin.info !== 'object') {
console.error('Failed to load plugin module; plugin info not found');
return false;
}
return false;
// We don't currently use "name" or "description" but it would be nice to have a UI for listing server plugins, so
// require them now just to be safe
for (const field of ['id', 'name', 'description']) {
if (typeof plugin.info[field] !== 'string') {
console.error(`Failed to load plugin module; plugin info missing field '${field}'`);
return false;
}
}
if (typeof plugin.init !== 'function') {
console.error('Failed to load plugin module; no init function');
return false;
}
const { id } = plugin.info;
if (!isValidPluginID(id)) {
console.error(`Failed to load plugin module; invalid plugin ID '${id}'`);
}
// Allow the plugin to register API routes under /plugins/[plugin ID] via a router
const router = express.Router();
await plugin.init(router);
// Add API routes to the app if the plugin registered any
if (router.stack.length > 0) {
app.use(`/plugins/${id}`, router);
}
return true;
}
/**