Files
NodeBB/src/plugins/hooks.js

211 lines
6.1 KiB
JavaScript
Raw Normal View History

2014-12-26 18:54:20 -05:00
'use strict';
const winston = require('winston');
const async = require('async');
const utils = require('../utils');
2014-12-26 18:54:20 -05:00
module.exports = function (Plugins) {
Plugins.deprecatedHooks = {
};
2018-04-05 14:35:49 -04:00
Plugins.internals = {
2019-07-22 00:30:47 -04:00
_register: function (data) {
2018-04-05 14:35:49 -04:00
Plugins.loadedHooks[data.hook] = Plugins.loadedHooks[data.hook] || [];
Plugins.loadedHooks[data.hook].push(data);
},
};
2019-07-22 00:30:47 -04:00
const hookTypeToMethod = {
filter: fireFilterHook,
action: fireActionHook,
static: fireStaticHook,
response: fireResponseHook,
};
2014-12-26 18:55:06 -05:00
/*
`data` is an object consisting of (* is required):
`data.hook`*, the name of the NodeBB hook
2018-04-05 14:35:49 -04:00
`data.method`*, the method called in that plugin (can be an array of functions)
2014-12-26 18:55:06 -05:00
`data.priority`, the relative priority of the method when it is eventually called (default: 10)
*/
2019-07-22 00:30:47 -04:00
Plugins.registerHook = function (id, data) {
if (!data.hook || !data.method) {
winston.warn('[plugins/' + id + '] registerHook called with invalid data.hook/method', data);
return;
}
if (Plugins.deprecatedHooks[data.hook]) {
winston.warn('[plugins/' + id + '] Hook `' + data.hook + '` is deprecated, ' +
(Plugins.deprecatedHooks[data.hook] ?
'please use `' + Plugins.deprecatedHooks[data.hook] + '` instead.' :
'there is no alternative.'
));
}
2019-07-22 00:30:47 -04:00
data.id = id;
if (!data.priority) {
data.priority = 10;
}
2014-12-26 18:54:20 -05:00
2019-07-22 00:30:47 -04:00
if (Array.isArray(data.method) && data.method.every(method => typeof method === 'function' || typeof method === 'string')) {
// Go go gadget recursion!
data.method.forEach(function (method) {
fix(style): updated code to follow new eslint recommendations Squashed commit of the following: commit f9ce878b269b3568f0d649309aae1af4dcfdfeef Author: Julian Lam <julian@nodebb.org> Date: Tue Aug 13 14:30:46 2019 -0400 fix(style): updated code to follow new eslint recommendations commit 80dd370e413f22badb96ff2138e7991dfff6d836 Author: Julian Lam <julian@nodebb.org> Date: Tue Aug 13 14:14:58 2019 -0400 fix(deps): update dependency sitemap to v4 Squashed commit of the following: commit f4dd9cabb21e26fdc21f8413be822ea7c64251f8 Author: Julian Lam <julian@nodebb.org> Date: Tue Aug 13 11:33:05 2019 -0400 fix: resolved breaking changes from sitemap v4 upgrade commit 9043415ee16dcc27a8dcc2e4479d1bc5e2d1b60e Merge: e3352b272 72590b346 Author: Julian Lam <julian@nodebb.org> Date: Tue Aug 13 11:09:55 2019 -0400 Merge branch 'master' into renovate/sitemap-4.x commit e3352b272eb9400bdb00774973181397803765e4 Author: Renovate Bot <bot@renovateapp.com> Date: Mon Aug 12 07:59:05 2019 +0000 fix(deps): update dependency sitemap to v4 commit 8e3c0cdcae22acc32d352be8bb72d60e7502dbc5 Author: Renovate Bot <bot@renovateapp.com> Date: Fri Aug 9 00:49:51 2019 +0000 fix(deps): update dependency commander to v3 commit 2104449d38818f2fa4d44b3a58a0a168781acbfb Author: Renovate Bot <bot@renovateapp.com> Date: Tue Aug 13 15:00:27 2019 +0000 fix(deps): update dependency mongodb to v3.3.0 commit d2937f446a21131c070ae5d0ff33d67cfe465b8c Author: Barış Soner Uşaklı <barisusakli@gmail.com> Date: Tue Aug 13 10:36:48 2019 -0400 feat: async/await admin/controllers commit 1b97e8b199f960dc24e5722702f27499ae049914 Author: Misty (Bot) <deploy@nodebb.org> Date: Tue Aug 13 09:28:39 2019 +0000 Latest translations and fallbacks commit 69a48957a2f0d23c4d194b664bda3a0431179c01 Author: Barış Soner Uşaklı <barisusakli@gmail.com> Date: Mon Aug 12 21:56:09 2019 -0400 feat: async/await commit b9b2a7e593a452de4bef6d0ab6abe368a3bdb8dd Author: Barış Soner Uşaklı <barisusakli@gmail.com> Date: Mon Aug 12 20:58:29 2019 -0400 feat: async/await refactor controllers/accounts commit a8d43a175974a0c8ae3dc132bf51a7ed9a4c6305 Author: Baris Usakli <barisusakli@gmail.com> Date: Mon Aug 12 14:49:40 2019 -0400 feat: async/await controllers/accounts commit 2f25aae57bf9dbe98d655276770e56bed9ec023b Author: Barış Soner Uşaklı <barisusakli@gmail.com> Date: Sun Aug 11 23:09:50 2019 -0400 fix: #7831, fix pagination convert to async/await commit c9e83f2374572264855a04156278eef256b0a20c Author: Barış Soner Uşaklı <barisusakli@gmail.com> Date: Sun Aug 11 00:14:35 2019 -0400 fix: remove empty line commit 30be91b26c4dd7583412c4e8d56e9c1688e48a44 Author: Barış Soner Uşaklı <barisusakli@gmail.com> Date: Sun Aug 11 00:13:41 2019 -0400 fix: remove useless catchs and empty line commit 2e4a71c0b6104738f15ffbfe3246105b922fdfb3 Author: Renovate Bot <bot@renovateapp.com> Date: Sat Aug 10 06:51:50 2019 +0000 chore(deps): update dependency eslint-config-airbnb-base to v14
2019-08-13 14:33:37 -04:00
const singularData = { ...data, method: method };
2019-07-22 00:30:47 -04:00
Plugins.registerHook(id, singularData);
});
} else if (typeof data.method === 'string' && data.method.length > 0) {
const method = data.method.split('.').reduce(function (memo, prop) {
if (memo && memo[prop]) {
return memo[prop];
}
// Couldn't find method by path, aborting
return null;
}, Plugins.libraries[data.id]);
// Write the actual method reference to the hookObj
data.method = method;
Plugins.internals._register(data);
} else if (typeof data.method === 'function') {
Plugins.internals._register(data);
} else {
winston.warn('[plugins/' + id + '] Hook method mismatch: ' + data.hook + ' => ' + data.method);
2014-12-26 18:54:20 -05:00
}
};
2017-02-28 16:42:10 +03:00
Plugins.unregisterHook = function (id, hook, method) {
var hooks = Plugins.loadedHooks[hook] || [];
Plugins.loadedHooks[hook] = hooks.filter(function (hookData) {
return hookData && hookData.id !== id && hookData.method !== method;
});
};
2019-07-22 00:30:47 -04:00
Plugins.fireHook = async function (hook, params) {
const hookList = Plugins.loadedHooks[hook];
const hookType = hook.split(':')[0];
2019-07-04 11:30:21 -04:00
if (hook !== 'action:plugins.firehook') {
2019-06-17 17:06:47 -04:00
winston.verbose('[plugins/fireHook] ' + hook);
}
2019-07-22 00:30:47 -04:00
if (!hookTypeToMethod[hookType]) {
2018-02-15 14:52:49 -05:00
winston.warn('[plugins] Unknown hookType: ' + hookType + ', hook : ' + hook);
2019-07-22 00:30:47 -04:00
return;
}
const result = await hookTypeToMethod[hookType](hook, hookList, params);
if (hook !== 'action:plugins.firehook') {
Plugins.fireHook('action:plugins.firehook', { hook: hook, params: params });
}
if (result !== undefined) {
return result;
2014-12-26 18:54:20 -05:00
}
};
2019-07-22 00:30:47 -04:00
async function fireFilterHook(hook, hookList, params) {
2015-04-28 15:09:17 -04:00
if (!Array.isArray(hookList) || !hookList.length) {
2019-07-22 00:30:47 -04:00
return params;
2015-04-28 15:09:17 -04:00
}
2019-07-22 00:30:47 -04:00
return await async.reduce(hookList, params, function (params, hookObj, next) {
2014-12-26 18:54:20 -05:00
if (typeof hookObj.method !== 'function') {
if (global.env === 'development') {
winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
}
return next(null, params);
}
const returned = hookObj.method(params, next);
if (utils.isPromise(returned)) {
returned.then(
payload => setImmediate(next, null, payload),
err => setImmediate(next, err)
);
}
2019-07-22 00:30:47 -04:00
});
2014-12-26 18:54:20 -05:00
}
2019-07-22 00:30:47 -04:00
async function fireActionHook(hook, hookList, params) {
2015-04-28 15:09:17 -04:00
if (!Array.isArray(hookList) || !hookList.length) {
2019-07-22 00:30:47 -04:00
return;
2015-04-28 15:09:17 -04:00
}
for (const hookObj of hookList) {
2014-12-26 18:54:20 -05:00
if (typeof hookObj.method !== 'function') {
if (global.env === 'development') {
winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
}
} else {
/* eslint-disable no-await-in-loop */
await hookObj.method(params);
2014-12-26 18:54:20 -05:00
}
}
2014-12-26 18:54:20 -05:00
}
2019-07-22 00:30:47 -04:00
async function fireStaticHook(hook, hookList, params) {
2015-04-28 15:09:17 -04:00
if (!Array.isArray(hookList) || !hookList.length) {
2019-07-22 00:30:47 -04:00
return;
2015-04-28 15:09:17 -04:00
}
// don't bubble errors from these hooks, so bad plugins don't stop startup
const noErrorHooks = ['static:app.load', 'static:assets.prepare', 'static:app.preload'];
2019-07-22 00:30:47 -04:00
await async.each(hookList, function (hookObj, next) {
if (typeof hookObj.method !== 'function') {
return next();
}
let timedOut = false;
const timeoutId = setTimeout(function () {
winston.warn('[plugins] Callback timed out, hook \'' + hook + '\' in plugin \'' + hookObj.id + '\'');
timedOut = true;
next();
}, 5000);
const callback = (err) => {
clearTimeout(timeoutId);
if (err) {
2015-01-30 12:25:07 -05:00
winston.error('[plugins] Error executing \'' + hook + '\' in plugin \'' + hookObj.id + '\'');
winston.error(err.stack);
2015-01-30 12:25:07 -05:00
}
if (!timedOut) {
next(noErrorHooks.includes(hook) ? null : err);
}
};
try {
const returned = hookObj.method(params, callback);
if (utils.isPromise(returned)) {
returned.then(
payload => setImmediate(callback, null, payload),
err => setImmediate(callback, err)
);
}
} catch (err) {
callback(err);
2014-12-26 18:54:20 -05:00
}
2019-07-22 00:30:47 -04:00
});
2014-12-26 18:54:20 -05:00
}
2019-07-22 00:30:47 -04:00
async function fireResponseHook(hook, hookList, params) {
if (!Array.isArray(hookList) || !hookList.length) {
2019-07-22 00:30:47 -04:00
return;
}
2019-07-22 00:30:47 -04:00
await async.eachSeries(hookList, function (hookObj, next) {
if (typeof hookObj.method !== 'function') {
if (global.env === 'development') {
winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
}
return next();
}
// Skip remaining hooks if headers have been sent
if (params.res.headersSent) {
return next();
}
hookObj.method(params);
next();
2019-07-22 00:30:47 -04:00
});
}
Plugins.hasListeners = function (hook) {
2014-12-26 18:54:20 -05:00
return !!(Plugins.loadedHooks[hook] && Plugins.loadedHooks[hook].length > 0);
};
};