etherpad-lite/src/static/js/pluginfw/shared.ts

106 lines
3.4 KiB
TypeScript
Raw Normal View History

2021-01-19 16:37:12 +00:00
'use strict';
2021-02-03 18:31:42 -05:00
2024-07-22 14:53:37 +02:00
import {Part, pluginDefs, PluginHook} from './plugin_defs';
import {MapArrayType} from "../../../node/types/MapType";
2024-07-22 14:53:37 +02:00
const disabledHookReasons: MapArrayType<any> = {
hooks: {
indexCustomInlineScripts: 'The hook makes it impossible to use a Content Security Policy ' +
'that prohibits inline code. Permitting inline code makes XSS vulnerabilities more likely',
},
};
2024-07-22 14:53:37 +02:00
const loadFn = (path: string, hookName: string, modules: Function) => {
2020-11-23 13:24:19 -05:00
let functionName;
const parts = path.split(':');
// on windows: C:\foo\bar:xyz
2021-01-19 16:37:12 +00:00
if (parts[0].length === 1) {
if (parts.length === 3) {
functionName = parts.pop();
}
2020-11-23 13:24:19 -05:00
path = parts.join(':');
} else {
path = parts[0];
functionName = parts[1];
}
let fn
if (modules === undefined || !("get" in modules)) {
fn = require(/* webpackIgnore: true */ path);
} else {
2024-07-22 14:53:37 +02:00
// @ts-ignore
fn = modules.get(path);
}
functionName = functionName ? functionName : hookName;
2021-02-03 18:31:42 -05:00
for (const name of functionName.split('.')) {
fn = fn[name];
2021-02-03 18:31:42 -05:00
}
return fn;
2021-01-19 16:37:12 +00:00
};
2024-07-22 14:53:37 +02:00
export const extractHooks = (parts: Part[], hookSetName: string, normalizer: Function|null, modules: Function) => {
const hooks: MapArrayType<PluginHook[]> = {};
2021-02-03 18:31:42 -05:00
for (const part of parts) {
2024-07-22 14:53:37 +02:00
// @ts-ignore
2021-02-03 18:31:42 -05:00
for (const [hookName, regHookFnName] of Object.entries(part[hookSetName] || {})) {
/* On the server side, you can't just
* require("pluginname/whatever") if the plugin is installed as
* a dependency of another plugin! Bah, pesky little details of
* npm... */
2021-02-03 18:31:42 -05:00
const hookFnName = normalizer ? normalizer(part, regHookFnName, hookName) : regHookFnName;
2021-02-03 18:31:42 -05:00
const disabledReason = (disabledHookReasons[hookSetName] || {})[hookName];
if (disabledReason) {
console.error(`Hook ${hookSetName}/${hookName} is disabled. Reason: ${disabledReason}`);
console.error(`The hook function ${hookFnName} from plugin ${part.plugin} ` +
'will never be called, which may cause the plugin to fail');
console.error(`Please update the ${part.plugin} plugin to not use the ${hookName} hook`);
return;
}
let hookFn;
try {
hookFn = loadFn(hookFnName, hookName, modules);
2021-02-03 18:31:42 -05:00
if (!hookFn) throw new Error('Not a function');
} catch (err) {
console.error(`Failed to load hook function "${hookFnName}" for plugin "${part.plugin}" ` +
`part "${part.name}" hook set "${hookSetName}" hook "${hookName}": ` +
2024-07-22 14:53:37 +02:00
// @ts-ignore
`${err.stack || err}`);
2021-02-03 18:31:42 -05:00
}
if (hookFn) {
if (hooks[hookName] == null) hooks[hookName] = [];
hooks[hookName].push({
hook_name: hookName,
hook_fn: hookFn,
hook_fn_name: hookFnName,
part,
2020-11-23 13:24:19 -05:00
});
2021-02-03 18:31:42 -05:00
}
}
}
return hooks;
2021-01-19 16:37:12 +00:00
};
/*
* Returns an array containing the names of the installed client-side plugins
*
* If no client-side plugins are installed, returns an empty array.
* Duplicate names are always discarded.
*
* A client-side plugin is a plugin that implements at least one client_hook
*
* EXAMPLE:
* No plugins: []
* Some plugins: [ 'ep_adminpads', 'ep_add_buttons', 'ep_activepads' ]
*/
2024-07-22 14:53:37 +02:00
export const clientPluginNames = () => {
const clientPluginNames = pluginDefs.getParts()
2021-02-03 18:31:42 -05:00
.filter((part) => Object.prototype.hasOwnProperty.call(part, 'client_hooks'))
.map((part) => `plugin-${part.plugin}`);
return [...new Set(clientPluginNames)];
2020-11-23 13:24:19 -05:00
};