mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	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
			
			
This commit is contained in:
		| @@ -14,106 +14,106 @@ | |||||||
|  * limitations under the License. |  * 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) { | module.exports = function(RED) { | ||||||
|     "use strict"; |     "use strict"; | ||||||
|     const crypto = require("crypto"); |     const crypto = require("crypto"); | ||||||
|     const targetCache = (function() { |     const targetCache = (function() { | ||||||
|         const name2id = {}; |         const registry = { ids: {}, named: {}}; | ||||||
|         let id2target = {}; |         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 { |         return { | ||||||
|             /** @type {((node: Node, [flowName]: string) => string) & ((nodeName: string, flowName: string) => string))} */ |             /** | ||||||
|             generateLookupName(node, flowName) { |              * Get a list of targets registerd to this name | ||||||
|                 if(!flowName) { |              * @param {string} name  | ||||||
|                     flowName = node._flow.flow.label |              * @returns {[LinkTarget]} Targets registerd to this name. | ||||||
|                 } |              */ | ||||||
|                 if(node instanceof LinkInNode) { |             getTargets(name) { | ||||||
|                     return `${flowName}/${node.name}`; |                 return registry.named[name] || []; | ||||||
|                 } |  | ||||||
|                 return `${flowName}/${node}`; |  | ||||||
|             }, |             }, | ||||||
|             add(node) { |             /** | ||||||
|                 const id = node.id; |              * Get a single target by registered name. | ||||||
|                 const nodeName = node.name; |              * To restrict to a single flow, include the `flowId` | ||||||
|                 if(!nodeName){ return null;} //node must be named |              * If there is no targets OR more than one target, null is returned | ||||||
|                 const flowName = node._flow.flow.label; |              * @param {string} name Name of the node | ||||||
|                 const lookupName = targetCache.generateLookupName(nodeName, flowName); |              * @param {string} [flowId]  | ||||||
|                 console.log(`Adding node '${lookupName}' (${id}) to cache`) |              * @returns {LinkTarget} target | ||||||
|                 if(name2id[lookupName] && name2id[lookupName].id !== id) { |              */ | ||||||
|                     //TODO: reassignment? duplate lookupName?  throw error or warning? |              getTarget(name, flowId) { | ||||||
|                     console.warn(`💣 '${lookupName}' is already assigned to node with ID: ${name2id[lookupName]} `) |                 let possibleTargets = this.getTargets(name); | ||||||
|  |                 /** @type {LinkTarget}*/ let target; | ||||||
|  |                 if(possibleTargets.length) { | ||||||
|  |                     if(flowId) { | ||||||
|  |                         possibleTargets = possibleTargets.filter(e => e.flowId == flowId); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|                  |                 if(possibleTargets.length === 1) { | ||||||
|                 name2id[lookupName] = id; |                     target = possibleTargets[0]; | ||||||
|                 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]; |  | ||||||
|                 return target; |                 return target; | ||||||
|             }, |             }, | ||||||
|             verify(cachedTarget, targetNode, nodeName, flowName) { |             /** | ||||||
|                 cachedTarget = cachedTarget || {}; |              * Get a target by node ID | ||||||
|                 targetNode = targetNode || {}; |              * @param {string} nodeId ID of the node | ||||||
|                 const _lookupName = targetCache.generateLookupName(nodeName, flowName); |              * @returns {LinkTarget} target | ||||||
|                 const cachedId = name2id[_lookupName]; |              */ | ||||||
|     |             getTargetById(nodeId) { | ||||||
|                 const idOK = cachedTarget.id === targetNode.id && cachedId === targetNode.id; |                 return registry.ids[nodeId]; | ||||||
|                 const nodeNameOK = nodeName === cachedTarget.nodeName && cachedTarget.nodeName == targetNode.name; |             },             | ||||||
|                 const flowNameOK = flowName === cachedTarget.flowName && cachedTarget.flowName == targetNode._flow.flow.label; |             register(/** @type {LinkInNode} */ node) { | ||||||
|                 const lookupNameOK = _lookupName === cachedTarget.lookupName; |                 const target = generateTarget(node); | ||||||
|                 if(idOK && nodeNameOK && flowNameOK && lookupNameOK) { |                 const tByName = this.getTarget(target.name, target.flowId); | ||||||
|                     return true; |                 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}) |                 registry.ids[target.id] = target; | ||||||
|                 console.warn(`> idOK:${idOK}, nodeNameOK:${nodeNameOK}, flowNameOK:${flowNameOK}, lookupNameOK:${lookupNameOK}`) |                 return target; | ||||||
|                 targetCache._removeById(targetNode.id); |  | ||||||
|                 targetCache._removeById(cachedId); |  | ||||||
|                 targetCache._removeByName(_lookupName); |  | ||||||
|                 targetCache._removeByName(cachedTarget.lookupName); |  | ||||||
|                 return false; |  | ||||||
|             }, |             }, | ||||||
|             _removeById(id) { |             remove(node) { | ||||||
|                 if(!id) { |                 const target = generateTarget(node); | ||||||
|                     return; |                 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]; |                 delete registry.ids[target.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]; |  | ||||||
|             }, |             }, | ||||||
|             clear() { |             clear() { | ||||||
|                 name2id = {}; |                 registry = { ids: {}, named: {}}; | ||||||
|                 id2target = {}; |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     })(); |     })(); | ||||||
| @@ -126,20 +126,7 @@ module.exports = function(RED) { | |||||||
|             msg._event = n.event; |             msg._event = n.event; | ||||||
|             node.receive(msg); |             node.receive(msg); | ||||||
|         } |         } | ||||||
|         //update target cache for link calls |         targetCache.register(node); | ||||||
|         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); |  | ||||||
|         RED.events.on(event,handler); |         RED.events.on(event,handler); | ||||||
|         this.on("input", function(msg, send, done) { |         this.on("input", function(msg, send, done) { | ||||||
|             send(msg); |             send(msg); | ||||||
| @@ -197,41 +184,27 @@ module.exports = function(RED) { | |||||||
|             timeout = 30000; |             timeout = 30000; | ||||||
|         } |         } | ||||||
|         function findNode(target) { |         function findNode(target) { | ||||||
|             let foundNode = null; |             let foundNode = RED.nodes.getNode(target); //1st see if the target is a direct node id | ||||||
|             switch (typeof target) { |             if(!foundNode) { | ||||||
|                 case "string": |                 //first look in this flow for the node | ||||||
|                     const nameParts = target.split("/"); |                 let cachedTarget = targetCache.getTarget(target, node._flow.flow.id); | ||||||
|                     switch (nameParts.length) { |                 if(!cachedTarget) { | ||||||
|                         case 1: |                     //single target node not found in registry! get all possible targets | ||||||
|                             target = { nodeName: nameParts[0], flowName: node._flow.flow.label } |                     const possibleTargets = targetCache.getTargets(target); | ||||||
|                             break; |                     if(possibleTargets.length === 1) { | ||||||
|                         case 2: |                         cachedTarget = possibleTargets[0]; | ||||||
|                             target = { nodeName: nameParts[1], flowName: nameParts[0] } |                     } else if (possibleTargets.length > 1) { | ||||||
|                             break; |                         throw new Error(`Multiple link-in nodes named '${target}' found`); | ||||||
|                         default: |  | ||||||
|                             target = { nodeName: target, flowName: node._flow.flow.label } |  | ||||||
|                     } |                     } | ||||||
|                 case "object": |                 } | ||||||
|                     break; |                 if (cachedTarget) { | ||||||
|                 default: |                     foundNode = RED.nodes.getNode(cachedTarget.id); | ||||||
|                     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; |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             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) { |         this.on("input", function(msg, send, done) { | ||||||
|             try { |             try { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user