Plugin cleanup and validation fixes

This commit is contained in:
Cohee 2023-12-23 19:03:13 +02:00
parent 437a6c1f40
commit ea85cfcbdd
2 changed files with 22 additions and 11 deletions

View File

@ -612,8 +612,10 @@ const setupTasks = async function () {
const exitProcess = () => { const exitProcess = () => {
statsEndpoint.onExit(); statsEndpoint.onExit();
if (typeof cleanupPlugins === 'function') {
cleanupPlugins();
}
process.exit(); process.exit();
cleanupPlugins();
}; };
// Set up event listeners for a graceful shutdown // Set up event listeners for a graceful shutdown
@ -636,13 +638,19 @@ const setupTasks = async function () {
} }
}; };
/**
* Loads server plugins from a directory.
* @returns {Promise<Function>} Function to be run on server exit
*/
async function loadPlugins() { async function loadPlugins() {
try { try {
const pluginDirectory = path.join(serverDirectory, 'plugins'); const pluginDirectory = path.join(serverDirectory, 'plugins');
const loader = require('./src/plugin-loader'); const loader = require('./src/plugin-loader');
await loader.loadPlugins(app, pluginDirectory); const cleanupPlugins = await loader.loadPlugins(app, pluginDirectory);
return cleanupPlugins;
} catch { } catch {
console.log('Plugin loading failed.'); console.log('Plugin loading failed.');
return () => {};
} }
} }

View File

@ -27,22 +27,23 @@ const isESModule = (file) => path.extname(file) === '.mjs';
*/ */
async function loadPlugins(app, pluginsPath) { async function loadPlugins(app, pluginsPath) {
const exitHooks = []; const exitHooks = [];
const emptyFn = () => {};
// Server plugins are disabled. // Server plugins are disabled.
if (!enableServerPlugins) { if (!enableServerPlugins) {
return; return emptyFn;
} }
// Plugins directory does not exist. // Plugins directory does not exist.
if (!fs.existsSync(pluginsPath)) { if (!fs.existsSync(pluginsPath)) {
return; return emptyFn;
} }
const files = fs.readdirSync(pluginsPath); const files = fs.readdirSync(pluginsPath);
// No plugins to load. // No plugins to load.
if (files.length === 0) { if (files.length === 0) {
return; return emptyFn;
} }
for (const file of files) { for (const file of files) {
@ -144,7 +145,7 @@ async function loadFromFile(app, pluginFilePath, exitHooks) {
* @returns {boolean} True if the plugin ID is valid. * @returns {boolean} True if the plugin ID is valid.
*/ */
function isValidPluginID(id) { function isValidPluginID(id) {
return /^[a-z0-9_-]$/.test(id); return /^[a-z0-9_-]+$/.test(id);
} }
/** /**
@ -156,7 +157,8 @@ function isValidPluginID(id) {
* @returns {Promise<boolean>} Promise that resolves to true if plugin was initialized successfully * @returns {Promise<boolean>} Promise that resolves to true if plugin was initialized successfully
*/ */
async function initPlugin(app, plugin, exitHooks) { async function initPlugin(app, plugin, exitHooks) {
if (typeof plugin.info !== 'object') { const info = plugin.info || plugin.default?.info;
if (typeof info !== 'object') {
console.error('Failed to load plugin module; plugin info not found'); console.error('Failed to load plugin module; plugin info not found');
return false; return false;
} }
@ -164,7 +166,7 @@ async function initPlugin(app, plugin, exitHooks) {
// We don't currently use "name" or "description" but it would be nice to have a UI for listing server plugins, so // 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 // require them now just to be safe
for (const field of ['id', 'name', 'description']) { for (const field of ['id', 'name', 'description']) {
if (typeof plugin.info[field] !== 'string') { if (typeof info[field] !== 'string') {
console.error(`Failed to load plugin module; plugin info missing field '${field}'`); console.error(`Failed to load plugin module; plugin info missing field '${field}'`);
return false; return false;
} }
@ -175,20 +177,21 @@ async function initPlugin(app, plugin, exitHooks) {
return false; return false;
} }
const { id } = plugin.info; const { id } = info;
if (!isValidPluginID(id)) { if (!isValidPluginID(id)) {
console.error(`Failed to load plugin module; invalid plugin ID '${id}'`); console.error(`Failed to load plugin module; invalid plugin ID '${id}'`);
return false;
} }
// Allow the plugin to register API routes under /plugins/[plugin ID] via a router // Allow the plugin to register API routes under /api/plugins/[plugin ID] via a router
const router = express.Router(); const router = express.Router();
await plugin.init(router); await plugin.init(router);
// Add API routes to the app if the plugin registered any // Add API routes to the app if the plugin registered any
if (router.stack.length > 0) { if (router.stack.length > 0) {
app.use(`/plugins/${id}`, router); app.use(`/api/plugins/${id}`, router);
} }
if (typeof plugin.exit === 'function') { if (typeof plugin.exit === 'function') {