2021-01-19 16:37:12 +00:00
|
|
|
'use strict';
|
2020-10-08 01:42:49 -04:00
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
const pluginDefs = require('./plugin_defs');
|
2012-03-01 18:45:02 +01:00
|
|
|
|
2020-08-27 16:24:43 -04:00
|
|
|
// Maps the name of a server-side hook to a string explaining the deprecation
|
|
|
|
// (e.g., 'use the foo hook instead').
|
|
|
|
//
|
|
|
|
// If you want to deprecate the fooBar hook, do the following:
|
|
|
|
//
|
|
|
|
// const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
|
|
|
// hooks.deprecationNotices.fooBar = 'use the newSpiffy hook instead';
|
|
|
|
//
|
|
|
|
exports.deprecationNotices = {};
|
|
|
|
|
|
|
|
const deprecationWarned = {};
|
|
|
|
|
2021-01-19 16:37:12 +00:00
|
|
|
const checkDeprecation = (hook) => {
|
2020-08-27 16:24:43 -04:00
|
|
|
const notice = exports.deprecationNotices[hook.hook_name];
|
|
|
|
if (notice == null) return;
|
|
|
|
if (deprecationWarned[hook.hook_fn_name]) return;
|
2020-11-13 13:29:09 -05:00
|
|
|
console.warn(`${hook.hook_name} hook used by the ${hook.part.plugin} plugin ` +
|
2020-10-08 01:42:49 -04:00
|
|
|
`(${hook.hook_fn_name}) is deprecated: ${notice}`);
|
2020-08-27 16:24:43 -04:00
|
|
|
deprecationWarned[hook.hook_fn_name] = true;
|
2021-01-19 16:37:12 +00:00
|
|
|
};
|
2020-08-27 16:24:43 -04:00
|
|
|
|
2021-01-31 15:23:44 -05:00
|
|
|
// Flattens the array one level.
|
|
|
|
const flatten1 = (array) => array.reduce((a, b) => a.concat(b), []);
|
|
|
|
|
|
|
|
const hookCallWrapper = (hook, hookName, args, cb) => {
|
2021-01-19 16:37:12 +00:00
|
|
|
if (cb === undefined) cb = (x) => x;
|
2012-04-19 16:03:42 +02:00
|
|
|
|
2020-08-27 16:24:43 -04:00
|
|
|
checkDeprecation(hook);
|
|
|
|
|
2012-04-19 16:03:42 +02:00
|
|
|
// Normalize output to list for both sync and async cases
|
2021-01-19 16:37:12 +00:00
|
|
|
const normalize = (x) => {
|
2014-06-12 13:33:17 +02:00
|
|
|
if (x === undefined) return [];
|
2012-04-19 16:03:42 +02:00
|
|
|
return x;
|
2020-11-23 13:24:19 -05:00
|
|
|
};
|
2021-01-31 15:28:06 -05:00
|
|
|
return () => normalize(hook.hook_fn(hookName, args, (x) => cb(normalize(x))));
|
2020-11-23 13:24:19 -05:00
|
|
|
};
|
2012-03-01 18:45:02 +01:00
|
|
|
|
2021-01-19 16:37:12 +00:00
|
|
|
exports.syncMapFirst = (lst, fn) => {
|
2020-11-23 13:24:19 -05:00
|
|
|
let i;
|
|
|
|
let result;
|
2012-04-19 16:03:42 +02:00
|
|
|
for (i = 0; i < lst.length; i++) {
|
2020-11-23 13:24:19 -05:00
|
|
|
result = fn(lst[i]);
|
2012-04-19 16:03:42 +02:00
|
|
|
if (result.length) return result;
|
|
|
|
}
|
2020-09-05 15:11:06 -04:00
|
|
|
return [];
|
2020-11-23 13:24:19 -05:00
|
|
|
};
|
2012-04-19 16:03:42 +02:00
|
|
|
|
2021-01-19 16:37:12 +00:00
|
|
|
exports.mapFirst = (lst, fn, cb, predicate) => {
|
2020-08-23 16:56:28 -04:00
|
|
|
if (predicate == null) predicate = (x) => (x != null && x.length > 0);
|
2020-11-23 13:24:19 -05:00
|
|
|
let i = 0;
|
2012-04-19 16:03:42 +02:00
|
|
|
|
2021-01-19 16:37:12 +00:00
|
|
|
const next = () => {
|
2020-09-05 15:11:06 -04:00
|
|
|
if (i >= lst.length) return cb(null, []);
|
2020-11-23 13:24:19 -05:00
|
|
|
fn(lst[i++], (err, result) => {
|
2012-04-19 16:03:42 +02:00
|
|
|
if (err) return cb(err);
|
2020-08-23 16:56:28 -04:00
|
|
|
if (predicate(result)) return cb(null, result);
|
2012-04-19 16:03:42 +02:00
|
|
|
next();
|
|
|
|
});
|
2020-11-23 13:24:19 -05:00
|
|
|
};
|
2012-04-19 16:03:42 +02:00
|
|
|
next();
|
2020-11-23 13:24:19 -05:00
|
|
|
};
|
2012-04-19 16:03:42 +02:00
|
|
|
|
2020-10-08 01:42:49 -04:00
|
|
|
// Calls the hook function synchronously and returns the value provided by the hook function (via
|
|
|
|
// callback or return value).
|
|
|
|
//
|
|
|
|
// A synchronous hook function can provide a value in these ways:
|
|
|
|
//
|
|
|
|
// * Call the callback, passing the desired value (which may be `undefined`) directly as the first
|
|
|
|
// argument, then return `undefined`.
|
|
|
|
// * For hook functions with three (or more) parameters: Directly return the desired value, which
|
|
|
|
// must not be `undefined`. Note: If a three-parameter hook function directly returns
|
|
|
|
// `undefined` and it has not already called the callback then it is indicating that it is not
|
|
|
|
// yet done and will eventually call the callback. This behavior is not supported by synchronous
|
|
|
|
// hooks.
|
|
|
|
// * For hook functions with two (or fewer) parameters: Directly return the desired value (which
|
|
|
|
// may be `undefined`).
|
|
|
|
//
|
|
|
|
// The callback passed to a hook function is guaranteed to return `undefined`, so it is safe for
|
|
|
|
// hook functions to do `return cb(value);`.
|
|
|
|
//
|
|
|
|
// A hook function can signal an error by throwing.
|
|
|
|
//
|
|
|
|
// A hook function settles when it provides a value (via callback or return) or throws. If a hook
|
|
|
|
// function attempts to settle again (e.g., call the callback again, or call the callback and also
|
|
|
|
// return a value) then the second attempt has no effect except either an error message is logged or
|
|
|
|
// there will be an unhandled promise rejection depending on whether the the subsequent attempt is a
|
|
|
|
// duplicate (same value or error) or different, respectively.
|
|
|
|
//
|
|
|
|
// See the tests in tests/backend/specs/hooks.js for examples of supported and prohibited behaviors.
|
|
|
|
//
|
2021-01-19 16:37:12 +00:00
|
|
|
const callHookFnSync = (hook, context) => {
|
2020-10-08 01:42:49 -04:00
|
|
|
checkDeprecation(hook);
|
2012-03-01 18:45:02 +01:00
|
|
|
|
2020-10-08 01:42:49 -04:00
|
|
|
// This var is used to keep track of whether the hook function already settled.
|
|
|
|
let outcome;
|
2020-07-17 10:08:40 +01:00
|
|
|
|
2020-10-08 01:42:49 -04:00
|
|
|
// This is used to prevent recursion.
|
|
|
|
let doubleSettleErr;
|
2020-07-17 10:08:40 +01:00
|
|
|
|
2020-10-08 01:42:49 -04:00
|
|
|
const settle = (err, val, how) => {
|
|
|
|
doubleSettleErr = null;
|
|
|
|
const state = err == null ? 'resolved' : 'rejected';
|
|
|
|
if (outcome != null) {
|
|
|
|
// It was already settled, which indicates a bug.
|
|
|
|
const action = err == null ? 'resolve' : 'reject';
|
2020-11-13 13:29:09 -05:00
|
|
|
const msg = (`DOUBLE SETTLE BUG IN HOOK FUNCTION (plugin: ${hook.part.plugin}, ` +
|
2020-10-08 01:42:49 -04:00
|
|
|
`function name: ${hook.hook_fn_name}, hook: ${hook.hook_name}): ` +
|
|
|
|
`Attempt to ${action} via ${how} but it already ${outcome.state} ` +
|
|
|
|
`via ${outcome.how}. Ignoring this attempt to ${action}.`);
|
|
|
|
console.error(msg);
|
|
|
|
if (state !== outcome.state || (err == null ? val !== outcome.val : err !== outcome.err)) {
|
|
|
|
// The second settle attempt differs from the first, which might indicate a serious bug.
|
|
|
|
doubleSettleErr = new Error(msg);
|
|
|
|
throw doubleSettleErr;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
outcome = {state, err, val, how};
|
|
|
|
if (val && typeof val.then === 'function') {
|
2020-11-13 13:29:09 -05:00
|
|
|
console.error(`PROHIBITED PROMISE BUG IN HOOK FUNCTION (plugin: ${hook.part.plugin}, ` +
|
2020-10-08 01:42:49 -04:00
|
|
|
`function name: ${hook.hook_fn_name}, hook: ${hook.hook_name}): ` +
|
|
|
|
'The hook function provided a "thenable" (e.g., a Promise) which is ' +
|
|
|
|
'prohibited because the hook expects to get the value synchronously.');
|
|
|
|
}
|
|
|
|
};
|
2020-07-23 16:47:59 -03:00
|
|
|
|
2020-10-08 01:42:49 -04:00
|
|
|
// IMPORTANT: This callback must return `undefined` so that a hook function can safely do
|
|
|
|
// `return callback(value);` for backwards compatibility.
|
|
|
|
const callback = (ret) => {
|
|
|
|
settle(null, ret, 'callback');
|
|
|
|
};
|
2012-03-01 18:45:02 +01:00
|
|
|
|
2020-10-08 01:42:49 -04:00
|
|
|
let val;
|
|
|
|
try {
|
|
|
|
val = hook.hook_fn(hook.hook_name, context, callback);
|
|
|
|
} catch (err) {
|
|
|
|
if (err === doubleSettleErr) throw err; // Avoid recursion.
|
|
|
|
try {
|
|
|
|
settle(err, null, 'thrown exception');
|
|
|
|
} catch (doubleSettleErr) {
|
|
|
|
// Schedule the throw of the double settle error on the event loop via
|
|
|
|
// Promise.resolve().then() (which will result in an unhandled Promise rejection) so that the
|
|
|
|
// original error is the error that is seen by the caller. Fixing the original error will
|
|
|
|
// likely fix the double settle bug, so the original error should get priority.
|
|
|
|
Promise.resolve().then(() => { throw doubleSettleErr; });
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
|
|
|
|
// IMPORTANT: This MUST check for undefined -- not nullish -- because some hooks intentionally use
|
|
|
|
// null as a special value.
|
|
|
|
if (val === undefined) {
|
|
|
|
if (outcome != null) return outcome.val; // Already settled via callback.
|
|
|
|
if (hook.hook_fn.length >= 3) {
|
2020-11-13 13:29:09 -05:00
|
|
|
console.error(`UNSETTLED FUNCTION BUG IN HOOK FUNCTION (plugin: ${hook.part.plugin}, ` +
|
2020-10-08 01:42:49 -04:00
|
|
|
`function name: ${hook.hook_fn_name}, hook: ${hook.hook_name}): ` +
|
|
|
|
'The hook function neither called the callback nor returned a non-undefined ' +
|
|
|
|
'value. This is prohibited because it will result in freezes when a future ' +
|
|
|
|
'version of Etherpad updates the hook to support asynchronous behavior.');
|
|
|
|
} else {
|
|
|
|
// The hook function is assumed to not have a callback parameter, so fall through and accept
|
|
|
|
// `undefined` as the resolved value.
|
|
|
|
//
|
|
|
|
// IMPORTANT: "Rest" parameters and default parameters are not counted in`Function.length`, so
|
|
|
|
// the assumption does not hold for wrappers like `(...args) => { real(...args); }`. Such
|
|
|
|
// functions will still work properly without any logged warnings or errors for now, but:
|
|
|
|
// * Once the hook is upgraded to support asynchronous hook functions, calling the callback
|
|
|
|
// will (eventually) cause a double settle error, and the function might prematurely
|
|
|
|
// resolve to `undefined` instead of the desired value.
|
|
|
|
// * The above "unsettled function" warning is not logged if the function fails to call the
|
|
|
|
// callback like it is supposed to.
|
2020-07-16 09:31:35 +01:00
|
|
|
}
|
2019-01-18 13:49:17 +00:00
|
|
|
}
|
2020-10-08 01:42:49 -04:00
|
|
|
|
|
|
|
settle(null, val, 'returned value');
|
|
|
|
return outcome.val;
|
2021-01-19 16:37:12 +00:00
|
|
|
};
|
2020-10-08 01:42:49 -04:00
|
|
|
|
|
|
|
// Invokes all registered hook functions synchronously.
|
|
|
|
//
|
|
|
|
// Arguments:
|
|
|
|
// * hookName: Name of the hook to invoke.
|
|
|
|
// * context: Passed unmodified to the hook functions, except nullish becomes {}.
|
|
|
|
//
|
|
|
|
// Return value:
|
|
|
|
// A flattened array of hook results. Specifically, it is equivalent to doing the following:
|
|
|
|
// 1. Collect all values returned by the hook functions into an array.
|
|
|
|
// 2. Convert each `undefined` entry into `[]`.
|
|
|
|
// 3. Flatten one level.
|
2021-01-19 16:37:12 +00:00
|
|
|
exports.callAll = (hookName, context) => {
|
2020-10-08 01:42:49 -04:00
|
|
|
if (context == null) context = {};
|
|
|
|
const hooks = pluginDefs.hooks[hookName] || [];
|
2021-01-31 15:23:44 -05:00
|
|
|
return flatten1(hooks.map((hook) => {
|
2020-10-08 01:42:49 -04:00
|
|
|
const ret = callHookFnSync(hook, context);
|
|
|
|
// `undefined` (but not `null`!) is treated the same as [].
|
|
|
|
if (ret === undefined) return [];
|
|
|
|
return ret;
|
2021-01-31 15:23:44 -05:00
|
|
|
}));
|
2020-10-08 01:42:49 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
// Calls the hook function asynchronously and returns a Promise that either resolves to the hook
|
|
|
|
// function's provided value or rejects with an error generated by the hook function.
|
|
|
|
//
|
|
|
|
// An asynchronous hook function can provide a value in these ways:
|
|
|
|
//
|
|
|
|
// * Call the callback, passing a Promise (or thenable) that resolves to the desired value (which
|
|
|
|
// may be `undefined`) as the first argument.
|
|
|
|
// * Call the callback, passing the desired value (which may be `undefined`) directly as the first
|
|
|
|
// argument.
|
|
|
|
// * Return a Promise (or thenable) that resolves to the desired value (which may be `undefined`).
|
|
|
|
// * For hook functions with three (or more) parameters: Directly return the desired value, which
|
|
|
|
// must not be `undefined`. Note: If a hook function directly returns `undefined` and it has not
|
|
|
|
// already called the callback then it is indicating that it is not yet done and will eventually
|
|
|
|
// call the callback.
|
|
|
|
// * For hook functions with two (or fewer) parameters: Directly return the desired value (which
|
|
|
|
// may be `undefined`).
|
|
|
|
//
|
|
|
|
// The callback passed to a hook function is guaranteed to return `undefined`, so it is safe for
|
|
|
|
// hook functions to do `return cb(valueOrPromise);`.
|
|
|
|
//
|
|
|
|
// A hook function can signal an error in these ways:
|
|
|
|
//
|
|
|
|
// * Throw.
|
|
|
|
// * Return a Promise that rejects.
|
|
|
|
// * Pass a Promise that rejects as the first argument to the provided callback.
|
|
|
|
//
|
|
|
|
// A hook function settles when it directly provides a value, when it throws, or when the Promise it
|
|
|
|
// provides settles (resolves or rejects). If a hook function attempts to settle again (e.g., call
|
|
|
|
// the callback again, or return a value and also call the callback) then the second attempt has no
|
|
|
|
// effect except either an error message is logged or an Error object is thrown depending on whether
|
|
|
|
// the the subsequent attempt is a duplicate (same value or error) or different, respectively.
|
|
|
|
//
|
|
|
|
// See the tests in tests/backend/specs/hooks.js for examples of supported and prohibited behaviors.
|
|
|
|
//
|
2021-01-19 16:37:12 +00:00
|
|
|
const callHookFnAsync = async (hook, context) => {
|
2020-10-08 01:42:49 -04:00
|
|
|
checkDeprecation(hook);
|
|
|
|
return await new Promise((resolve, reject) => {
|
|
|
|
// This var is used to keep track of whether the hook function already settled.
|
|
|
|
let outcome;
|
|
|
|
|
|
|
|
const settle = (err, val, how) => {
|
|
|
|
const state = err == null ? 'resolved' : 'rejected';
|
|
|
|
if (outcome != null) {
|
|
|
|
// It was already settled, which indicates a bug.
|
|
|
|
const action = err == null ? 'resolve' : 'reject';
|
2020-11-13 13:29:09 -05:00
|
|
|
const msg = (`DOUBLE SETTLE BUG IN HOOK FUNCTION (plugin: ${hook.part.plugin}, ` +
|
2020-10-08 01:42:49 -04:00
|
|
|
`function name: ${hook.hook_fn_name}, hook: ${hook.hook_name}): ` +
|
|
|
|
`Attempt to ${action} via ${how} but it already ${outcome.state} ` +
|
|
|
|
`via ${outcome.how}. Ignoring this attempt to ${action}.`);
|
|
|
|
console.error(msg);
|
|
|
|
if (state !== outcome.state || (err == null ? val !== outcome.val : err !== outcome.err)) {
|
|
|
|
// The second settle attempt differs from the first, which might indicate a serious bug.
|
|
|
|
throw new Error(msg);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
outcome = {state, err, val, how};
|
|
|
|
if (err == null) { resolve(val); } else { reject(err); }
|
|
|
|
};
|
|
|
|
|
|
|
|
// IMPORTANT: This callback must return `undefined` so that a hook function can safely do
|
|
|
|
// `return callback(value);` for backwards compatibility.
|
|
|
|
const callback = (ret) => {
|
|
|
|
// Wrap ret in a Promise so that a hook function can do `callback(asyncFunction());`. Note: If
|
|
|
|
// ret is a Promise (or other thenable), Promise.resolve() will flatten it into this new
|
|
|
|
// Promise.
|
|
|
|
Promise.resolve(ret).then(
|
|
|
|
(val) => settle(null, val, 'callback'),
|
|
|
|
(err) => settle(err, null, 'rejected Promise passed to callback'));
|
|
|
|
};
|
|
|
|
|
|
|
|
let ret;
|
|
|
|
try {
|
|
|
|
ret = hook.hook_fn(hook.hook_name, context, callback);
|
|
|
|
} catch (err) {
|
|
|
|
try {
|
|
|
|
settle(err, null, 'thrown exception');
|
|
|
|
} catch (doubleSettleErr) {
|
|
|
|
// Schedule the throw of the double settle error on the event loop via
|
|
|
|
// Promise.resolve().then() (which will result in an unhandled Promise rejection) so that
|
|
|
|
// the original error is the error that is seen by the caller. Fixing the original error
|
|
|
|
// will likely fix the double settle bug, so the original error should get priority.
|
|
|
|
Promise.resolve().then(() => { throw doubleSettleErr; });
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
|
|
|
|
// IMPORTANT: This MUST check for undefined -- not nullish -- because some hooks intentionally
|
|
|
|
// use null as a special value.
|
|
|
|
if (ret === undefined) {
|
|
|
|
if (hook.hook_fn.length >= 3) {
|
|
|
|
// The hook function has a callback parameter and it returned undefined, which means the
|
|
|
|
// hook function will settle (or has already settled) via the provided callback.
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
// The hook function is assumed to not have a callback parameter, so fall through and accept
|
|
|
|
// `undefined` as the resolved value.
|
|
|
|
//
|
|
|
|
// IMPORTANT: "Rest" parameters and default parameters are not counted in `Function.length`,
|
|
|
|
// so the assumption does not hold for wrappers like `(...args) => { real(...args); }`. For
|
|
|
|
// such functions, calling the callback will (eventually) cause a double settle error, and
|
|
|
|
// the function might prematurely resolve to `undefined` instead of the desired value.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wrap ret in a Promise so that hook functions can be async (or otherwise return a Promise).
|
|
|
|
// Note: If ret is a Promise (or other thenable), Promise.resolve() will flatten it into this
|
|
|
|
// new Promise.
|
|
|
|
Promise.resolve(ret).then(
|
|
|
|
(val) => settle(null, val, 'returned value'),
|
|
|
|
(err) => settle(err, null, 'Promise rejection'));
|
|
|
|
});
|
2021-01-19 16:37:12 +00:00
|
|
|
};
|
2019-01-18 13:49:17 +00:00
|
|
|
|
2020-10-08 01:42:49 -04:00
|
|
|
// Invokes all registered hook functions asynchronously.
|
|
|
|
//
|
|
|
|
// Arguments:
|
|
|
|
// * hookName: Name of the hook to invoke.
|
|
|
|
// * context: Passed unmodified to the hook functions, except nullish becomes {}.
|
|
|
|
// * cb: Deprecated callback. The following:
|
|
|
|
// const p1 = hooks.aCallAll('myHook', context, cb);
|
|
|
|
// is equivalent to:
|
|
|
|
// const p2 = hooks.aCallAll('myHook', context).then((val) => cb(null, val), cb);
|
|
|
|
//
|
|
|
|
// Return value:
|
|
|
|
// If cb is nullish, this function resolves to a flattened array of hook results. Specifically, it
|
|
|
|
// is equivalent to doing the following:
|
|
|
|
// 1. Collect all values returned by the hook functions into an array.
|
|
|
|
// 2. Convert each `undefined` entry into `[]`.
|
|
|
|
// 3. Flatten one level.
|
|
|
|
// If cb is non-null, this function resolves to the value returned by cb.
|
|
|
|
exports.aCallAll = async (hookName, context, cb) => {
|
|
|
|
if (context == null) context = {};
|
|
|
|
const hooks = pluginDefs.hooks[hookName] || [];
|
2020-11-23 13:24:19 -05:00
|
|
|
let resultsPromise = Promise.all(hooks.map((hook) => callHookFnAsync(hook, context)
|
|
|
|
// `undefined` (but not `null`!) is treated the same as [].
|
2021-01-19 16:37:12 +00:00
|
|
|
.then((result) => (result === undefined) ? [] : result)))
|
2021-01-31 15:23:44 -05:00
|
|
|
.then(flatten1);
|
2020-10-08 01:42:49 -04:00
|
|
|
if (cb != null) resultsPromise = resultsPromise.then((val) => cb(null, val), cb);
|
|
|
|
return await resultsPromise;
|
|
|
|
};
|
|
|
|
|
2021-01-31 15:23:44 -05:00
|
|
|
exports.callFirst = (hookName, args) => {
|
2012-03-27 22:23:28 +02:00
|
|
|
if (!args) args = {};
|
2021-01-31 15:23:44 -05:00
|
|
|
if (pluginDefs.hooks[hookName] === undefined) return [];
|
|
|
|
return exports.syncMapFirst(pluginDefs.hooks[hookName],
|
|
|
|
(hook) => hookCallWrapper(hook, hookName, args));
|
2020-11-23 13:24:19 -05:00
|
|
|
};
|
2012-03-01 18:45:02 +01:00
|
|
|
|
2021-01-31 15:23:44 -05:00
|
|
|
const aCallFirst = (hookName, args, cb, predicate) => {
|
2012-03-27 22:23:28 +02:00
|
|
|
if (!args) args = {};
|
2021-01-19 16:37:12 +00:00
|
|
|
if (!cb) cb = () => {};
|
2021-01-31 15:23:44 -05:00
|
|
|
if (pluginDefs.hooks[hookName] === undefined) return cb(null, []);
|
2012-04-19 16:03:42 +02:00
|
|
|
exports.mapFirst(
|
2021-01-31 15:23:44 -05:00
|
|
|
pluginDefs.hooks[hookName],
|
2020-11-23 13:24:19 -05:00
|
|
|
(hook, cb) => {
|
2021-01-31 15:23:44 -05:00
|
|
|
hookCallWrapper(hook, hookName, args, (res) => { cb(null, res); });
|
2020-11-23 13:24:19 -05:00
|
|
|
},
|
|
|
|
cb,
|
2020-12-16 16:51:43 -05:00
|
|
|
predicate
|
2012-04-19 16:03:42 +02:00
|
|
|
);
|
2021-01-19 16:37:12 +00:00
|
|
|
};
|
2012-03-01 19:22:02 +01:00
|
|
|
|
2019-01-18 13:49:17 +00:00
|
|
|
/* return a Promise if cb is not supplied */
|
2021-01-31 15:23:44 -05:00
|
|
|
exports.aCallFirst = (hookName, args, cb, predicate) => {
|
2019-01-18 13:49:17 +00:00
|
|
|
if (cb === undefined) {
|
2020-11-23 13:24:19 -05:00
|
|
|
return new Promise((resolve, reject) => {
|
2021-01-31 15:23:44 -05:00
|
|
|
aCallFirst(hookName, args, (err, res) => err ? reject(err) : resolve(res), predicate);
|
2019-01-18 13:49:17 +00:00
|
|
|
});
|
|
|
|
} else {
|
2021-01-31 15:23:44 -05:00
|
|
|
return aCallFirst(hookName, args, cb, predicate);
|
2019-01-18 13:49:17 +00:00
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
};
|
2019-01-18 13:49:17 +00:00
|
|
|
|
2020-10-08 01:42:49 -04:00
|
|
|
exports.exportedForTestingOnly = {
|
|
|
|
callHookFnAsync,
|
|
|
|
callHookFnSync,
|
|
|
|
deprecationWarned,
|
|
|
|
};
|