From 19cf43a10e8822c462f50639dbb60783f3b69d93 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Fri, 25 Feb 2022 16:13:39 +0000 Subject: [PATCH] Re-write link call targeting... - Remove msg.target by object - Remove :: scoping - Always try to locate matching link-in on same flow first - If not found, look on all flows - if 1 found, call it - If more than 1 link target found, raise error --- .../@node-red/nodes/core/common/60-link.js | 237 ++++++++---------- 1 file changed, 105 insertions(+), 132 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.js b/packages/node_modules/@node-red/nodes/core/common/60-link.js index a3fe82de7..86a0ebc6c 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.js +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.js @@ -14,106 +14,106 @@ * limitations under the License. **/ +/** + * @typedef LinkTarget + * @type {object} + * @property {string} id - ID of the target node. + * @property {string} name - Name of target Node + * @property {number} flowId - ID of flow where the target node exists + * @property {string} flowName - Name of flow where the target node exists + */ + + module.exports = function(RED) { "use strict"; const crypto = require("crypto"); const targetCache = (function() { - const name2id = {}; - let id2target = {}; + const registry = { ids: {}, named: {}}; + function getIndex(/** @type {[LinkTarget]}*/ targets, id) { + for (let index = 0; index < (targets || []).length; index++) { + const element = targets[index]; + if (element.id === id) { + return index; + } + } + return -1; + } + /** + * Generate a target object from a node + * @param {LinkInNode} node + * @returns {LinkTarget} a link target object + */ + function generateTarget(node) { + return { + id: node.id, + name: node.name || node.id, + flowId: node._flow.flow.id, + flowName: node._flow.flow.label, + isSubFlow: false + } + } return { - /** @type {((node: Node, [flowName]: string) => string) & ((nodeName: string, flowName: string) => string))} */ - generateLookupName(node, flowName) { - if(!flowName) { - flowName = node._flow.flow.label - } - if(node instanceof LinkInNode) { - return `${flowName}/${node.name}`; - } - return `${flowName}/${node}`; + /** + * Get a list of targets registerd to this name + * @param {string} name + * @returns {[LinkTarget]} Targets registerd to this name. + */ + getTargets(name) { + return registry.named[name] || []; }, - add(node) { - const id = node.id; - const nodeName = node.name; - if(!nodeName){ return null;} //node must be named - const flowName = node._flow.flow.label; - const lookupName = targetCache.generateLookupName(nodeName, flowName); - console.log(`Adding node '${lookupName}' (${id}) to cache`) - if(name2id[lookupName] && name2id[lookupName].id !== id) { - //TODO: reassignment? duplate lookupName? throw error or warning? - console.warn(`💣 '${lookupName}' is already assigned to node with ID: ${name2id[lookupName]} `) + /** + * Get a single target by registered name. + * To restrict to a single flow, include the `flowId` + * If there is no targets OR more than one target, null is returned + * @param {string} name Name of the node + * @param {string} [flowId] + * @returns {LinkTarget} target + */ + getTarget(name, flowId) { + let possibleTargets = this.getTargets(name); + /** @type {LinkTarget}*/ let target; + if(possibleTargets.length) { + if(flowId) { + possibleTargets = possibleTargets.filter(e => e.flowId == flowId); + } } - - name2id[lookupName] = id; - id2target[id] = { - lookupName, - nodeName, - flowName, - id - } - return id2target[id]; - }, - remove(node) { - if(node.name) console.log(`Removing node '${targetCache.generateLookupName(node)}' (${node.id})`) - const target = id2target[node.id]; - targetCache._removeById(node.id); - if(target) { - targetCache._removeById(target.id); - targetCache._removeByName(target.lookupName); - } - }, - getTarget(lookupName) { - const id = name2id[lookupName]; - const target = id2target[id]; + if(possibleTargets.length === 1) { + target = possibleTargets[0]; + } return target; }, - verify(cachedTarget, targetNode, nodeName, flowName) { - cachedTarget = cachedTarget || {}; - targetNode = targetNode || {}; - const _lookupName = targetCache.generateLookupName(nodeName, flowName); - const cachedId = name2id[_lookupName]; - - const idOK = cachedTarget.id === targetNode.id && cachedId === targetNode.id; - const nodeNameOK = nodeName === cachedTarget.nodeName && cachedTarget.nodeName == targetNode.name; - const flowNameOK = flowName === cachedTarget.flowName && cachedTarget.flowName == targetNode._flow.flow.label; - const lookupNameOK = _lookupName === cachedTarget.lookupName; - if(idOK && nodeNameOK && flowNameOK && lookupNameOK) { - return true; + /** + * Get a target by node ID + * @param {string} nodeId ID of the node + * @returns {LinkTarget} target + */ + getTargetById(nodeId) { + return registry.ids[nodeId]; + }, + register(/** @type {LinkInNode} */ node) { + const target = generateTarget(node); + const tByName = this.getTarget(target.name, target.flowId); + if(!tByName) { + registry.named[target.name] = registry.named[target.name] || []; + registry.named[target.name].push(target) } - console.warn(`verify(cachedTarget, targetNode, nodeName, flowName) failed: `, {cachedTarget, targetNode, nodeName, flowName}) - console.warn(`> idOK:${idOK}, nodeNameOK:${nodeNameOK}, flowNameOK:${flowNameOK}, lookupNameOK:${lookupNameOK}`) - targetCache._removeById(targetNode.id); - targetCache._removeById(cachedId); - targetCache._removeByName(_lookupName); - targetCache._removeByName(cachedTarget.lookupName); - return false; + registry.ids[target.id] = target; + return target; }, - _removeById(id) { - if(!id) { - return; + remove(node) { + const target = generateTarget(node); + const tn = this.getTarget(target.name, target.flowId); + if(tn) { + const targs = this.getTargets(tn.name); + const idx = getIndex(targs, tn.id); + if(idx > -1) { + targs.splice(idx,1); + } } - const target = id2target[id]; - if(target && target.lookupName) { - delete name2id[target.lookupName]; - } - delete id2target[id]; - }, - _removeByName(lookupName) { - if(!lookupName) { - return; - } - var id = name2id[lookupName]; - var target = id2target[id]; - if(id) { - delete id2target[id]; - } - if(target && target.lookupName) { - delete name2id[target.lookupName]; - } - delete name2id[lookupName]; + delete registry.ids[target.id]; }, clear() { - name2id = {}; - id2target = {}; + registry = { ids: {}, named: {}}; } } })(); @@ -126,20 +126,7 @@ module.exports = function(RED) { msg._event = n.event; node.receive(msg); } - //update target cache for link calls - function updateCache(o) { - console.log(node.id, node.name, o.config.rev, o) - const changed = o.diff.changed.includes(node.id); - const added = o.diff.added.includes(node.id); - if(changed) { - targetCache.remove(node); - } - if((changed || added) && node.name) { - targetCache.add(node); - } - } - - RED.events.on("flows:started", updateCache); + targetCache.register(node); RED.events.on(event,handler); this.on("input", function(msg, send, done) { send(msg); @@ -197,41 +184,27 @@ module.exports = function(RED) { timeout = 30000; } function findNode(target) { - let foundNode = null; - switch (typeof target) { - case "string": - const nameParts = target.split("/"); - switch (nameParts.length) { - case 1: - target = { nodeName: nameParts[0], flowName: node._flow.flow.label } - break; - case 2: - target = { nodeName: nameParts[1], flowName: nameParts[0] } - break; - default: - target = { nodeName: target, flowName: node._flow.flow.label } + let foundNode = RED.nodes.getNode(target); //1st see if the target is a direct node id + if(!foundNode) { + //first look in this flow for the node + let cachedTarget = targetCache.getTarget(target, node._flow.flow.id); + if(!cachedTarget) { + //single target node not found in registry! get all possible targets + const possibleTargets = targetCache.getTargets(target); + if(possibleTargets.length === 1) { + cachedTarget = possibleTargets[0]; + } else if (possibleTargets.length > 1) { + throw new Error(`Multiple link-in nodes named '${target}' found`); } - case "object": - break; - default: - throw new Error("Invalid target"); - } - if(!target.nodeName || typeof target.nodeName !== "string") { - throw new Error("Link call target name invalid") - } - if(!target.flowName || typeof target.flowName !== "string") { - throw new Error("Link call target flow name invalid") - } - const lookupName = targetCache.generateLookupName(target.nodeName, target.flowName); - //const targetNode = targetCache.getNode(lookupName); - const cachedTarget = targetCache.getTarget(lookupName); - if (cachedTarget) { - foundNode = RED.nodes.getNode(cachedTarget.id); - if (targetCache.verify(cachedTarget, foundNode, target.nodeName, target.flowName)) { - return foundNode; + } + if (cachedTarget) { + foundNode = RED.nodes.getNode(cachedTarget.id); } } - throw new Error(`Link in node not found: ${lookupName}`); + if(foundNode instanceof LinkInNode) { + return foundNode; + } + throw new Error(`link-in node '${target}' not found`); } this.on("input", function(msg, send, done) { try {