import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import _ from 'underscore'; /* * Callback hooks provide an easy way to add extra steps to common operations. * @namespace RocketChat.callbacks */ RocketChat.callbacks = {}; if (Meteor.isServer) { RocketChat.callbacks.showTime = true; RocketChat.callbacks.showTotalTime = true; } else { RocketChat.callbacks.showTime = false; RocketChat.callbacks.showTotalTime = false; } /* * Callback priorities */ RocketChat.callbacks.priority = { HIGH: -1000, MEDIUM: 0, LOW: 1000, }; const getHooks = (hookName) => RocketChat.callbacks[hookName] || []; /* * Add a callback function to a hook * @param {String} hook - The name of the hook * @param {Function} callback - The callback function */ RocketChat.callbacks.add = function(hook, callback, priority, id = Random.id()) { if (!_.isNumber(priority)) { priority = RocketChat.callbacks.priority.MEDIUM; } callback.priority = priority; callback.id = id; RocketChat.callbacks[hook] = getHooks(hook); if (RocketChat.callbacks.showTime === true) { const err = new Error; callback.stack = err.stack; } if (RocketChat.callbacks[hook].find((cb) => cb.id === callback.id)) { return; } RocketChat.callbacks[hook].push(callback); RocketChat.callbacks[hook] = _.sortBy(RocketChat.callbacks[hook], function(callback) { return callback.priority || RocketChat.callbacks.priority.MEDIUM; }); }; /* * Remove a callback from a hook * @param {string} hook - The name of the hook * @param {string} id - The callback's id */ RocketChat.callbacks.remove = function(hook, id) { RocketChat.callbacks[hook] = getHooks(hook).filter((callback) => callback.id !== id); }; /* * Successively run all of a hook's callbacks on an item * @param {String} hook - The name of the hook * @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks * @param {Object} [constant] - An optional constant that will be passed along to each callback * @returns {Object} Returns the item after it's been through all the callbacks for this hook */ RocketChat.callbacks.run = function(hook, item, constant) { const callbacks = RocketChat.callbacks[hook]; if (!callbacks || !callbacks.length) { return item; } let rocketchatHooksEnd; if (Meteor.isServer) { rocketchatHooksEnd = RocketChat.metrics.rocketchatHooks.startTimer({ hook, callbacks_length: callbacks.length }); } let totalTime = 0; const result = callbacks.reduce(function(result, callback) { let rocketchatCallbacksEnd; if (Meteor.isServer) { rocketchatCallbacksEnd = RocketChat.metrics.rocketchatCallbacks.startTimer({ hook, callback: callback.id }); } const time = RocketChat.callbacks.showTime === true || RocketChat.callbacks.showTotalTime === true ? Date.now() : 0; const callbackResult = callback(result, constant); if (RocketChat.callbacks.showTime === true || RocketChat.callbacks.showTotalTime === true) { const currentTime = Date.now() - time; totalTime += currentTime; if (RocketChat.callbacks.showTime === true) { if (Meteor.isServer) { rocketchatCallbacksEnd(); RocketChat.statsTracker.timing('callbacks.time', currentTime, [`hook:${ hook }`, `callback:${ callback.id }`]); } else { let stack = callback.stack && typeof callback.stack.split === 'function' && callback.stack.split('\n'); stack = stack && stack[2] && (stack[2].match(/\(.+\)/) || [])[0]; console.log(String(currentTime), hook, callback.id, stack); } } } return (typeof callbackResult === 'undefined') ? result : callbackResult; }, item); if (Meteor.isServer) { rocketchatHooksEnd(); } if (RocketChat.callbacks.showTotalTime === true) { if (Meteor.isServer) { RocketChat.statsTracker.timing('callbacks.totalTime', totalTime, [`hook:${ hook }`]); } else { console.log(`${ hook }:`, totalTime); } } return result; }; /* * Successively run all of a hook's callbacks on an item, in async mode (only works on server) * @param {String} hook - The name of the hook * @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks * @param {Object} [constant] - An optional constant that will be passed along to each callback */ RocketChat.callbacks.runAsync = function(hook, item, constant) { const callbacks = RocketChat.callbacks[hook]; if (Meteor.isServer && callbacks && callbacks.length) { Meteor.defer(function() { callbacks.forEach((callback) => callback(item, constant)); }); } return item; };