plugins: Move plugin definitions to avoid monkey patching

Also document the plugin data structures.
This commit is contained in:
Richard Hansen 2020-09-06 15:27:18 -04:00 committed by John McLear
parent dcbf876d03
commit da459888dc
15 changed files with 104 additions and 92 deletions

View file

@ -3,15 +3,12 @@ $ = jQuery = require("ep_etherpad-lite/static/js/rjquery").$;
var _ = require("underscore");
var pluginUtils = require('./shared');
var defs = require('./plugin_defs');
exports.loaded = false;
exports.plugins = {};
exports.parts = [];
exports.hooks = {};
exports.baseURL = '';
exports.ensure = function (cb) {
if (!exports.loaded)
if (!defs.loaded)
exports.update(cb);
else
cb();
@ -24,10 +21,10 @@ exports.update = function (cb) {
var callback = function () {setTimeout(cb, 0);};
$.ajaxSetup({ cache: false });
jQuery.getJSON(exports.baseURL + 'pluginfw/plugin-definitions.json').done(function(data) {
exports.plugins = data.plugins;
exports.parts = data.parts;
exports.hooks = pluginUtils.extractHooks(exports.parts, "client_hooks");
exports.loaded = true;
defs.plugins = data.plugins;
defs.parts = data.parts;
defs.hooks = pluginUtils.extractHooks(defs.parts, "client_hooks");
defs.loaded = true;
callback();
}).fail(function(e){
console.error("Failed to load plugin-definitions: " + err);
@ -35,16 +32,6 @@ exports.update = function (cb) {
});
};
function adoptPlugins(plugins) {
var keys = [
'loaded', 'plugins', 'parts', 'hooks', 'baseURL', 'ensure', 'update'];
for (var i = 0, ii = keys.length; i < ii; i++) {
var key = keys[i];
exports[key] = plugins[key];
}
}
function adoptPluginsFromAncestorsOf(frame) {
// Bind plugins with parent;
var parentRequire = null;
@ -59,12 +46,18 @@ function adoptPluginsFromAncestorsOf(frame) {
// Silence (this can only be a XDomain issue).
}
if (parentRequire) {
var ancestorPluginDefs = parentRequire("ep_etherpad-lite/static/js/pluginfw/plugin_defs");
defs.hooks = ancestorPluginDefs.hooks;
defs.loaded = ancestorPluginDefs.loaded;
defs.parts = ancestorPluginDefs.parts;
defs.plugins = ancestorPluginDefs.plugins;
var ancestorPlugins = parentRequire("ep_etherpad-lite/static/js/pluginfw/client_plugins");
exports.adoptPlugins(ancestorPlugins);
exports.baseURL = ancestorPlugins.baseURL;
exports.ensure = ancestorPlugins.ensure;
exports.update = ancestorPlugins.update;
} else {
throw new Error("Parent plugins could not be found.")
}
}
exports.adoptPlugins = adoptPlugins;
exports.adoptPluginsFromAncestorsOf = adoptPluginsFromAncestorsOf;

View file

@ -1,4 +1,5 @@
var _ = require("underscore");
var pluginDefs = require('./plugin_defs');
// Maps the name of a server-side hook to a string explaining the deprecation
// (e.g., 'use the foo hook instead').
@ -92,20 +93,18 @@ exports.flatten = function (lst) {
exports.callAll = function (hook_name, args) {
if (!args) args = {};
if (exports.plugins){
if (exports.plugins.hooks[hook_name] === undefined) return [];
return _.flatten(_.map(exports.plugins.hooks[hook_name], function (hook) {
return hookCallWrapper(hook, hook_name, args);
}), true);
}
if (pluginDefs.hooks[hook_name] === undefined) return [];
return _.flatten(_.map(pluginDefs.hooks[hook_name], function(hook) {
return hookCallWrapper(hook, hook_name, args);
}), true);
}
async function aCallAll(hook_name, args, cb) {
if (!args) args = {};
if (!cb) cb = function () {};
if (exports.plugins.hooks[hook_name] === undefined) return cb(null, []);
if (pluginDefs.hooks[hook_name] === undefined) return cb(null, []);
var hooksPromises = exports.plugins.hooks[hook_name].map(async function(hook, index){
var hooksPromises = pluginDefs.hooks[hook_name].map(async function(hook, index) {
return await hookCallWrapper(hook, hook_name, args, function (res) {
return Promise.resolve(res);
});
@ -137,8 +136,8 @@ exports.aCallAll = function (hook_name, args, cb) {
exports.callFirst = function (hook_name, args) {
if (!args) args = {};
if (exports.plugins.hooks[hook_name] === undefined) return [];
return exports.syncMapFirst(exports.plugins.hooks[hook_name], function (hook) {
if (pluginDefs.hooks[hook_name] === undefined) return [];
return exports.syncMapFirst(pluginDefs.hooks[hook_name], function(hook) {
return hookCallWrapper(hook, hook_name, args);
});
}
@ -146,9 +145,9 @@ exports.callFirst = function (hook_name, args) {
function aCallFirst(hook_name, args, cb) {
if (!args) args = {};
if (!cb) cb = function () {};
if (exports.plugins.hooks[hook_name] === undefined) return cb(null, []);
if (pluginDefs.hooks[hook_name] === undefined) return cb(null, []);
exports.mapFirst(
exports.plugins.hooks[hook_name],
pluginDefs.hooks[hook_name],
function (hook, cb) {
hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); });
},
@ -180,29 +179,3 @@ exports.callAllStr = function(hook_name, args, sep, pre, post) {
}
return newCallhooks.join(sep || "");
}
/*
* 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' ]
*/
exports.clientPluginNames = function() {
if (!(exports.plugins)) {
return [];
}
var client_plugin_names = _.uniq(
exports.plugins.parts
.filter(function(part) { return part.hasOwnProperty('client_hooks'); })
.map(function(part) { return 'plugin-' + part['plugin']; })
);
return client_plugin_names;
}

View file

@ -0,0 +1,24 @@
// This module contains processed plugin definitions. The data structures in this file are set by
// plugins.js (server) or client_plugins.js (client).
// Maps a hook name to a list of hook objects. Each hook object has the following properties:
// * hook_name: Name of the hook.
// * hook_fn: Plugin-supplied hook function.
// * hook_fn_name: Name of the hook function, with the form <filename>:<functionName>.
// * part: The ep.json part object that declared the hook. See exports.plugins.
exports.hooks = {};
// Whether the plugins have been loaded.
exports.loaded = false;
// Topologically sorted list of parts from exports.plugins.
exports.parts = [];
// Maps the name of a plugin to the plugin's definition provided in ep.json. The ep.json object is
// augmented with additional metadata:
// * parts: Each part from the ep.json object is augmented with the following properties:
// - plugin: The name of the plugin.
// - full_name: Equal to <plugin>/<name>.
// * package (server-side only): Object containing details about the plugin package (version,
// path).
exports.plugins = {};

View file

@ -8,28 +8,25 @@ var _ = require("underscore");
var settings = require('../../../node/utils/Settings');
var pluginUtils = require('./shared');
var defs = require('./plugin_defs');
exports.prefix = 'ep_';
exports.loaded = false;
exports.plugins = {};
exports.parts = [];
exports.hooks = {};
// @TODO RPB this appears to be unused
exports.ensure = function (cb) {
if (!exports.loaded)
if (!defs.loaded)
exports.update(cb);
else
cb();
};
exports.formatPlugins = function () {
return _.keys(exports.plugins).join(", ");
return _.keys(defs.plugins).join(", ");
};
exports.formatPluginsWithVersion = function () {
var plugins = [];
_.forEach(exports.plugins, function(plugin){
_.forEach(defs.plugins, function(plugin) {
if(plugin.package.name !== "ep_etherpad-lite"){
var pluginStr = plugin.package.name + "@" + plugin.package.version;
plugins.push(pluginStr);
@ -39,12 +36,12 @@ exports.formatPluginsWithVersion = function () {
};
exports.formatParts = function () {
return _.map(exports.parts, function (part) { return part.full_name; }).join("\n");
return _.map(defs.parts, function(part) { return part.full_name; }).join('\n');
};
exports.formatHooks = function (hook_set_name) {
var res = [];
var hooks = pluginUtils.extractHooks(exports.parts, hook_set_name || "hooks");
var hooks = pluginUtils.extractHooks(defs.parts, hook_set_name || 'hooks');
_.chain(hooks).keys().forEach(function (hook_name) {
_.forEach(hooks[hook_name], function (hook) {
@ -60,8 +57,8 @@ exports.callInit = function () {
var hooks = require("./hooks");
let p = Object.keys(exports.plugins).map(function (plugin_name) {
let plugin = exports.plugins[plugin_name];
let p = Object.keys(defs.plugins).map(function(plugin_name) {
let plugin = defs.plugins[plugin_name];
let ep_init = path.normalize(path.join(plugin.package.path, ".ep_initialized"));
return fsp_stat(ep_init).catch(async function() {
await fsp_writeFile(ep_init, "done");
@ -75,7 +72,7 @@ exports.callInit = function () {
exports.pathNormalization = function (part, hook_fn_name, hook_name) {
const parts = hook_fn_name.split(':');
const functionName = (parts.length > 1) ? parts.pop() : hook_name;
const packageDir = path.dirname(exports.plugins[part.plugin].package.path);
const packageDir = path.dirname(defs.plugins[part.plugin].package.path);
const fileName = path.normalize(path.join(packageDir, parts.join(':')));
return fileName + ((functionName == null) ? '' : (':' + functionName));
}
@ -91,10 +88,10 @@ exports.update = async function () {
});
return Promise.all(p).then(function() {
exports.plugins = plugins;
exports.parts = sortParts(parts);
exports.hooks = pluginUtils.extractHooks(exports.parts, "hooks", exports.pathNormalization);
exports.loaded = true;
defs.plugins = plugins;
defs.parts = sortParts(parts);
defs.hooks = pluginUtils.extractHooks(defs.parts, 'hooks', exports.pathNormalization);
defs.loaded = true;
}).then(exports.callInit);
}

View file

@ -1,4 +1,5 @@
var _ = require("underscore");
var defs = require('./plugin_defs');
function loadFn(path, hookName) {
var functionName
@ -59,3 +60,25 @@ function extractHooks(parts, hook_set_name, normalizer) {
};
exports.extractHooks = extractHooks;
/*
* 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' ]
*/
exports.clientPluginNames = function() {
var client_plugin_names = _.uniq(
defs.parts
.filter(function(part) { return part.hasOwnProperty('client_hooks'); })
.map(function(part) { return 'plugin-' + part['plugin']; })
);
return client_plugin_names;
}