const Log = require("@node-red/util").log; // Flags for what hooks have handlers registered let states = { } // Hooks by id let hooks = { } // Hooks by label let labelledHooks = { } /** * Runtime hooks engine * @mixin @node-red/runtime_hooks */ /** * Register a handler to a named hook * @memberof @node-red/runtime_hooks * @param {String} hookId - the name of the hook to attach to * @param {Function} callback - the callback function for the hook */ function add(hookId, callback) { let [id, label] = hookId.split("."); if (label) { if (labelledHooks[label] && labelledHooks[label][id]) { throw new Error("Hook "+hookId+" already registered") } labelledHooks[label] = labelledHooks[label]||{}; labelledHooks[label][id] = callback; } // Get location of calling code const stack = new Error().stack; const callModule = stack.split("\n")[2].split("(")[1].slice(0,-1); Log.debug(`Adding hook '${hookId}' from ${callModule}`); hooks[id] = hooks[id] || []; hooks[id].push({cb:callback,location:callModule}); states[id] = true; } /** * Remove a handled from a named hook * @memberof @node-red/runtime_hooks * @param {String} hookId - the name of the hook event to remove - must be `name.label` */ function remove(hookId) { let [id,label] = hookId.split("."); if ( !label) { throw new Error("Cannot remove hook without label: ",hookId) } Log.debug(`Removing hook '${hookId}'`); if (labelledHooks[label]) { if (id === "*") { // Remove all hooks for this label let hookList = Object.keys(labelledHooks[label]); for (let i=0;i hook.cb === callback); if (i !== -1) { hooks[id].splice(i,1); if (hooks[id].length === 0) { delete hooks[id]; delete states[id]; } } } function trigger(hookId, payload, done) { const hookStack = hooks[hookId]; if (!hookStack || hookStack.length === 0) { done(); return; } let i = 0; function callNextHook(err) { if (i === hookStack.length || err) { done(err); return; } const hook = hookStack[i++]; const callback = hook.cb; if (callback.length === 1) { try { let result = callback(payload); if (result === false) { // Halting the flow done(false); return } if (result && typeof result.then === 'function') { result.then(handleResolve, callNextHook) return; } callNextHook(); } catch(err) { done(err); return; } } else { try { callback(payload,handleResolve) } catch(err) { done(err); return; } } } callNextHook(); function handleResolve(result) { if (result === undefined) { callNextHook(); } else { done(result); } } } // add("preSend", function(sendEvents) { // console.log("preSend",JSON.stringify(sendEvents)); // }) // add("preRoute", function(sendEvent) { // console.log("preRoute",JSON.stringify(sendEvent.msg)); // }) // add("onSend", function(sendEvent) { // console.log("onSend",JSON.stringify(sendEvent.msg)); // }) // add("postSend", function(sendEvent) { // console.log("postSend",JSON.stringify(sendEvent.msg)); // }) // add("onReceive", function(recEvent) { // console.log("onReceive",recEvent.destination.id,JSON.stringify(recEvent.msg)) // }) // add("postReceive", function(recEvent) { // console.log("postReceive",recEvent.destination.id,JSON.stringify(recEvent.msg)) // }) function clear() { hooks = {} labelledHooks = {} states = {} } module.exports = { get states() { return states }, clear, add, remove, trigger }