mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge pull request #2665 from node-red/msg-router
Pluggable Message Routing
This commit is contained in:
@@ -57,6 +57,7 @@ function createNodeApi(node) {
|
||||
log: {},
|
||||
settings: {},
|
||||
events: runtime.events,
|
||||
hooks: runtime.hooks,
|
||||
util: runtime.util,
|
||||
version: runtime.version,
|
||||
require: requireModule,
|
||||
|
@@ -49,11 +49,9 @@ var api = module.exports = {
|
||||
* @return {Promise<Flows>} - the active flow configuration
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
getFlows: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
runtime.log.audit({event: "flows.get"}, opts.req);
|
||||
return resolve(runtime.nodes.getFlows());
|
||||
});
|
||||
getFlows: async function(opts) {
|
||||
runtime.log.audit({event: "flows.get"}, opts.req);
|
||||
return runtime.flows.getFlows();
|
||||
},
|
||||
/**
|
||||
* Sets the current flow configuration
|
||||
@@ -65,38 +63,35 @@ var api = module.exports = {
|
||||
* @return {Promise<Flows>} - the active flow configuration
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
setFlows: function(opts) {
|
||||
return mutex.runExclusive(function() {
|
||||
return new Promise(function(resolve,reject) {
|
||||
setFlows: async function(opts) {
|
||||
return mutex.runExclusive(async function() {
|
||||
var flows = opts.flows;
|
||||
var deploymentType = opts.deploymentType||"full";
|
||||
runtime.log.audit({event: "flows.set",type:deploymentType}, opts.req);
|
||||
|
||||
var flows = opts.flows;
|
||||
var deploymentType = opts.deploymentType||"full";
|
||||
runtime.log.audit({event: "flows.set",type:deploymentType}, opts.req);
|
||||
|
||||
var apiPromise;
|
||||
if (deploymentType === 'reload') {
|
||||
apiPromise = runtime.nodes.loadFlows(true);
|
||||
} else {
|
||||
if (flows.hasOwnProperty('rev')) {
|
||||
var currentVersion = runtime.nodes.getFlows().rev;
|
||||
if (currentVersion !== flows.rev) {
|
||||
var err;
|
||||
err = new Error();
|
||||
err.code = "version_mismatch";
|
||||
err.status = 409;
|
||||
//TODO: log warning
|
||||
return reject(err);
|
||||
}
|
||||
var apiPromise;
|
||||
if (deploymentType === 'reload') {
|
||||
apiPromise = runtime.flows.loadFlows(true);
|
||||
} else {
|
||||
if (flows.hasOwnProperty('rev')) {
|
||||
var currentVersion = runtime.flows.getFlows().rev;
|
||||
if (currentVersion !== flows.rev) {
|
||||
var err;
|
||||
err = new Error();
|
||||
err.code = "version_mismatch";
|
||||
err.status = 409;
|
||||
//TODO: log warning
|
||||
throw err;
|
||||
}
|
||||
apiPromise = runtime.nodes.setFlows(flows.flows,flows.credentials,deploymentType,null,null,opts.user);
|
||||
}
|
||||
apiPromise.then(function(flowId) {
|
||||
return resolve({rev:flowId});
|
||||
}).catch(function(err) {
|
||||
runtime.log.warn(runtime.log._("api.flows.error-"+(deploymentType === 'reload'?'reload':'save'),{message:err.message}));
|
||||
runtime.log.warn(err.stack);
|
||||
return reject(err);
|
||||
});
|
||||
apiPromise = runtime.flows.setFlows(flows.flows,flows.credentials,deploymentType,null,null,opts.user);
|
||||
}
|
||||
return apiPromise.then(function(flowId) {
|
||||
return {rev:flowId};
|
||||
}).catch(function(err) {
|
||||
runtime.log.warn(runtime.log._("api.flows.error-"+(deploymentType === 'reload'?'reload':'save'),{message:err.message}));
|
||||
runtime.log.warn(err.stack);
|
||||
throw err
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -110,22 +105,20 @@ var api = module.exports = {
|
||||
* @return {Promise<String>} - the id of the added flow
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
addFlow: function(opts) {
|
||||
return mutex.runExclusive(function() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var flow = opts.flow;
|
||||
runtime.nodes.addFlow(flow,opts.user).then(function (id) {
|
||||
runtime.log.audit({event: "flow.add", id: id}, opts.req);
|
||||
return resolve(id);
|
||||
}).catch(function (err) {
|
||||
runtime.log.audit({
|
||||
event: "flow.add",
|
||||
error: err.code || "unexpected_error",
|
||||
message: err.toString()
|
||||
}, opts.req);
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
})
|
||||
addFlow: async function(opts) {
|
||||
return mutex.runExclusive(async function() {
|
||||
var flow = opts.flow;
|
||||
return runtime.flows.addFlow(flow, opts.user).then(function (id) {
|
||||
runtime.log.audit({event: "flow.add", id: id}, opts.req);
|
||||
return id;
|
||||
}).catch(function (err) {
|
||||
runtime.log.audit({
|
||||
event: "flow.add",
|
||||
error: err.code || "unexpected_error",
|
||||
message: err.toString()
|
||||
}, opts.req);
|
||||
err.status = 400;
|
||||
throw err;
|
||||
})
|
||||
});
|
||||
},
|
||||
@@ -139,20 +132,18 @@ var api = module.exports = {
|
||||
* @return {Promise<Flow>} - the active flow configuration
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
getFlow: function(opts) {
|
||||
return new Promise(function (resolve,reject) {
|
||||
var flow = runtime.nodes.getFlow(opts.id);
|
||||
if (flow) {
|
||||
runtime.log.audit({event: "flow.get",id:opts.id}, opts.req);
|
||||
return resolve(flow);
|
||||
} else {
|
||||
runtime.log.audit({event: "flow.get",id:opts.id,error:"not_found"}, opts.req);
|
||||
var err = new Error();
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
return reject(err);
|
||||
}
|
||||
})
|
||||
getFlow: async function(opts) {
|
||||
var flow = runtime.flows.getFlow(opts.id);
|
||||
if (flow) {
|
||||
runtime.log.audit({event: "flow.get",id:opts.id}, opts.req);
|
||||
return flow;
|
||||
} else {
|
||||
runtime.log.audit({event: "flow.get",id:opts.id,error:"not_found"}, opts.req);
|
||||
var err = new Error();
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Updates an existing flow configuration
|
||||
@@ -164,42 +155,29 @@ var api = module.exports = {
|
||||
* @return {Promise<String>} - the id of the updated flow
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
updateFlow: function(opts) {
|
||||
return mutex.runExclusive(function() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var flow = opts.flow;
|
||||
var id = opts.id;
|
||||
try {
|
||||
runtime.nodes.updateFlow(id, flow, opts.user).then(function () {
|
||||
runtime.log.audit({event: "flow.update", id: id}, opts.req);
|
||||
return resolve(id);
|
||||
}).catch(function (err) {
|
||||
runtime.log.audit({
|
||||
event: "flow.update",
|
||||
error: err.code || "unexpected_error",
|
||||
message: err.toString()
|
||||
}, opts.req);
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
})
|
||||
} catch (err) {
|
||||
if (err.code === 404) {
|
||||
runtime.log.audit({event: "flow.update", id: id, error: "not_found"}, opts.req);
|
||||
// TODO: this swap around of .code and .status isn't ideal
|
||||
err.status = 404;
|
||||
err.code = "not_found";
|
||||
return reject(err);
|
||||
} else {
|
||||
runtime.log.audit({
|
||||
event: "flow.update",
|
||||
error: err.code || "unexpected_error",
|
||||
message: err.toString()
|
||||
}, opts.req);
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
updateFlow: async function(opts) {
|
||||
return mutex.runExclusive(async function() {
|
||||
var flow = opts.flow;
|
||||
var id = opts.id;
|
||||
return runtime.flows.updateFlow(id, flow, opts.user).then(function () {
|
||||
runtime.log.audit({event: "flow.update", id: id}, opts.req);
|
||||
return id;
|
||||
}).catch(function (err) {
|
||||
if (err.code === 404) {
|
||||
runtime.log.audit({event: "flow.update", id: id, error: "not_found"}, opts.req);
|
||||
// TODO: this swap around of .code and .status isn't ideal
|
||||
err.status = 404;
|
||||
err.code = "not_found";
|
||||
} else {
|
||||
runtime.log.audit({
|
||||
event: "flow.update",
|
||||
error: err.code || "unexpected_error",
|
||||
message: err.toString()
|
||||
}, opts.req);
|
||||
err.status = 400;
|
||||
}
|
||||
});
|
||||
throw err;
|
||||
})
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -211,42 +189,28 @@ var api = module.exports = {
|
||||
* @return {Promise} - resolves if successful
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
deleteFlow: function(opts) {
|
||||
deleteFlow: async function(opts) {
|
||||
return mutex.runExclusive(function() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var id = opts.id;
|
||||
try {
|
||||
runtime.nodes.removeFlow(id, opts.user).then(function () {
|
||||
runtime.log.audit({event: "flow.remove", id: id}, opts.req);
|
||||
return resolve();
|
||||
}).catch(function (err) {
|
||||
runtime.log.audit({
|
||||
event: "flow.remove",
|
||||
id: id,
|
||||
error: err.code || "unexpected_error",
|
||||
message: err.toString()
|
||||
}, opts.req);
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === 404) {
|
||||
runtime.log.audit({event: "flow.remove", id: id, error: "not_found"}, opts.req);
|
||||
// TODO: this swap around of .code and .status isn't ideal
|
||||
err.status = 404;
|
||||
err.code = "not_found";
|
||||
return reject(err);
|
||||
} else {
|
||||
runtime.log.audit({
|
||||
event: "flow.remove",
|
||||
id: id,
|
||||
error: err.code || "unexpected_error",
|
||||
message: err.toString()
|
||||
}, opts.req);
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
var id = opts.id;
|
||||
return runtime.flows.removeFlow(id, opts.user).then(function () {
|
||||
runtime.log.audit({event: "flow.remove", id: id}, opts.req);
|
||||
return;
|
||||
}).catch(function (err) {
|
||||
if (err.code === 404) {
|
||||
runtime.log.audit({event: "flow.remove", id: id, error: "not_found"}, opts.req);
|
||||
// TODO: this swap around of .code and .status isn't ideal
|
||||
err.status = 404;
|
||||
err.code = "not_found";
|
||||
} else {
|
||||
runtime.log.audit({
|
||||
event: "flow.remove",
|
||||
id: id,
|
||||
error: err.code || "unexpected_error",
|
||||
message: err.toString()
|
||||
}, opts.req);
|
||||
err.status = 400;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -261,35 +225,33 @@ var api = module.exports = {
|
||||
* @return {Promise<Object>} - the safe credentials
|
||||
* @memberof @node-red/runtime_flows
|
||||
*/
|
||||
getNodeCredentials: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
runtime.log.audit({event: "credentials.get",type:opts.type,id:opts.id}, opts.req);
|
||||
var credentials = runtime.nodes.getCredentials(opts.id);
|
||||
if (!credentials) {
|
||||
return resolve({});
|
||||
}
|
||||
var sendCredentials = {};
|
||||
var cred;
|
||||
if (/^subflow(:|$)/.test(opts.type)) {
|
||||
for (cred in credentials) {
|
||||
if (credentials.hasOwnProperty(cred)) {
|
||||
sendCredentials['has_'+cred] = credentials[cred] != null && credentials[cred] !== '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var definition = runtime.nodes.getCredentialDefinition(opts.type) || {};
|
||||
for (cred in definition) {
|
||||
if (definition.hasOwnProperty(cred)) {
|
||||
if (definition[cred].type == "password") {
|
||||
var key = 'has_' + cred;
|
||||
sendCredentials[key] = credentials[cred] != null && credentials[cred] !== '';
|
||||
continue;
|
||||
}
|
||||
sendCredentials[cred] = credentials[cred] || '';
|
||||
}
|
||||
getNodeCredentials: async function(opts) {
|
||||
runtime.log.audit({event: "credentials.get",type:opts.type,id:opts.id}, opts.req);
|
||||
var credentials = runtime.nodes.getCredentials(opts.id);
|
||||
if (!credentials) {
|
||||
return {};
|
||||
}
|
||||
var sendCredentials = {};
|
||||
var cred;
|
||||
if (/^subflow(:|$)/.test(opts.type)) {
|
||||
for (cred in credentials) {
|
||||
if (credentials.hasOwnProperty(cred)) {
|
||||
sendCredentials['has_'+cred] = credentials[cred] != null && credentials[cred] !== '';
|
||||
}
|
||||
}
|
||||
resolve(sendCredentials);
|
||||
})
|
||||
} else {
|
||||
var definition = runtime.nodes.getCredentialDefinition(opts.type) || {};
|
||||
for (cred in definition) {
|
||||
if (definition.hasOwnProperty(cred)) {
|
||||
if (definition[cred].type == "password") {
|
||||
var key = 'has_' + cred;
|
||||
sendCredentials[key] = credentials[cred] != null && credentials[cred] !== '';
|
||||
continue;
|
||||
}
|
||||
sendCredentials[cred] = credentials[cred] || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
return sendCredentials;
|
||||
}
|
||||
}
|
||||
|
@@ -14,9 +14,34 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var events = require("events");
|
||||
const events = new (require("events")).EventEmitter();
|
||||
|
||||
module.exports = new events.EventEmitter();
|
||||
|
||||
const deprecatedEvents = {
|
||||
"nodes-stopped": "flows:stopped",
|
||||
"nodes-started": "flows:started"
|
||||
}
|
||||
|
||||
function wrapEventFunction(obj,func) {
|
||||
events["_"+func] = events[func];
|
||||
return function(eventName, listener) {
|
||||
if (deprecatedEvents.hasOwnProperty(eventName)) {
|
||||
const log = require("@node-red/util").log;
|
||||
const stack = (new Error().stack).split("\n")[2].split("(")[1].slice(0,-1);
|
||||
log.warn(`[RED.events] Deprecated use of "${eventName}" event from "${stack}". Use "${deprecatedEvents[eventName]}" instead.`)
|
||||
}
|
||||
return events["_"+func].call(events,eventName,listener)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
events.on = wrapEventFunction(events,"on");
|
||||
events.once = wrapEventFunction(events,"once");
|
||||
events.addListener = events.on;
|
||||
|
||||
|
||||
|
||||
module.exports = events;
|
||||
|
||||
/**
|
||||
* Runtime events emitter
|
||||
|
@@ -17,8 +17,9 @@
|
||||
var clone = require("clone");
|
||||
var redUtil = require("@node-red/util").util;
|
||||
var flowUtil = require("./util");
|
||||
var events = require("../../events");
|
||||
const context = require('../context');
|
||||
var events = require("../events");
|
||||
const context = require('../nodes/context');
|
||||
const hooks = require("../hooks");
|
||||
|
||||
var Subflow;
|
||||
var Log;
|
||||
@@ -539,8 +540,25 @@ class Flow {
|
||||
}
|
||||
}
|
||||
|
||||
get asyncMessageDelivery() {
|
||||
return asyncMessageDelivery
|
||||
send(sendEvents) {
|
||||
// onSend - passed an array of SendEvent objects. The messages inside these objects are exactly what the node has passed to node.send - meaning there could be duplicate references to the same message object.
|
||||
// preRoute - called once for each SendEvent object in turn
|
||||
// preDeliver - the local router has identified the node it is going to send to. At this point, the message has been cloned if needed.
|
||||
// postDeliver - the message has been dispatched to be delivered asynchronously (unless the sync delivery flag is set, in which case it would be continue as synchronous delivery)
|
||||
// onReceive - a node is about to receive a message
|
||||
// postReceive - the message has been passed to the node's input handler
|
||||
// onDone, onError - the node has completed with a message or logged an error
|
||||
handleOnSend(this,sendEvents, (err, eventData) => {
|
||||
if (err) {
|
||||
let srcNode;
|
||||
if (Array.isArray(eventData)) {
|
||||
srcNode = eventData[0].source.node;
|
||||
} else {
|
||||
srcNode = eventData.source.node;
|
||||
}
|
||||
srcNode.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dump() {
|
||||
@@ -589,6 +607,67 @@ function stopNode(node,removed) {
|
||||
}
|
||||
|
||||
|
||||
function handleOnSend(flow,sendEvents, reportError) {
|
||||
// onSend - passed an array of SendEvent objects. The messages inside these objects are exactly what the node has passed to node.send - meaning there could be duplicate references to the same message object.
|
||||
hooks.trigger("onSend",sendEvents,(err) => {
|
||||
if (err) {
|
||||
reportError(err,sendEvents);
|
||||
return
|
||||
} else if (err !== false) {
|
||||
for (var i=0;i<sendEvents.length;i++) {
|
||||
handlePreRoute(flow,sendEvents[i],reportError)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handlePreRoute(flow, sendEvent, reportError) {
|
||||
// preRoute - called once for each SendEvent object in turn
|
||||
hooks.trigger("preRoute",sendEvent,(err) => {
|
||||
if (err) {
|
||||
reportError(err,sendEvent);
|
||||
return;
|
||||
} else if (err !== false) {
|
||||
sendEvent.destination.node = flow.getNode(sendEvent.destination.id);
|
||||
if (sendEvent.destination.node) {
|
||||
if (sendEvent.cloneMessage) {
|
||||
sendEvent.msg = redUtil.cloneMessage(sendEvent.msg);
|
||||
}
|
||||
handlePreDeliver(flow,sendEvent,reportError);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handlePreDeliver(flow,sendEvent, reportError) {
|
||||
// preDeliver - the local router has identified the node it is going to send to. At this point, the message has been cloned if needed.
|
||||
hooks.trigger("preDeliver",sendEvent,(err) => {
|
||||
if (err) {
|
||||
reportError(err,sendEvent);
|
||||
return;
|
||||
} else if (err !== false) {
|
||||
if (asyncMessageDelivery) {
|
||||
setImmediate(function() {
|
||||
if (sendEvent.destination.node) {
|
||||
sendEvent.destination.node.receive(sendEvent.msg);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (sendEvent.destination.node) {
|
||||
sendEvent.destination.node.receive(sendEvent.msg);
|
||||
|
||||
}
|
||||
}
|
||||
// postDeliver - the message has been dispatched to be delivered asynchronously (unless the sync delivery flag is set, in which case it would be continue as synchronous delivery)
|
||||
hooks.trigger("postDeliver", sendEvent, function(err) {
|
||||
if (err) {
|
||||
reportError(err,sendEvent);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
nodeCloseTimeout = runtime.settings.nodeCloseTimeout || 15000;
|
@@ -16,14 +16,14 @@
|
||||
|
||||
const clone = require("clone");
|
||||
const Flow = require('./Flow').Flow;
|
||||
const context = require('../context');
|
||||
const context = require('../nodes/context');
|
||||
const util = require("util");
|
||||
|
||||
const redUtil = require("@node-red/util").util;
|
||||
const flowUtil = require("./util");
|
||||
|
||||
|
||||
const credentials = require("../credentials");
|
||||
const credentials = require("../nodes/credentials");
|
||||
|
||||
var Log;
|
||||
|
||||
@@ -159,7 +159,7 @@ class Subflow extends Flow {
|
||||
start(diff) {
|
||||
var self = this;
|
||||
// Create a subflow node to accept inbound messages and route appropriately
|
||||
var Node = require("../Node");
|
||||
var Node = require("../nodes/Node");
|
||||
|
||||
if (this.subflowDef.status) {
|
||||
var subflowStatusConfig = {
|
@@ -15,21 +15,19 @@
|
||||
**/
|
||||
|
||||
var clone = require("clone");
|
||||
var when = require("when");
|
||||
|
||||
var Flow = require('./Flow');
|
||||
|
||||
var typeRegistry = require("@node-red/registry");
|
||||
var deprecated = typeRegistry.deprecated;
|
||||
|
||||
|
||||
var context = require("../context")
|
||||
var credentials = require("../credentials");
|
||||
|
||||
var context = require("../nodes/context")
|
||||
var credentials = require("../nodes/credentials");
|
||||
var flowUtil = require("./util");
|
||||
var log;
|
||||
var events = require("../../events");
|
||||
var events = require("../events");
|
||||
var redUtil = require("@node-red/util").util;
|
||||
const hooks = require("../hooks");
|
||||
|
||||
var storage = null;
|
||||
var settings = null;
|
||||
@@ -294,6 +292,8 @@ function start(type,diff,muteLog) {
|
||||
}
|
||||
}
|
||||
|
||||
events.emit("flows:starting", {config: activeConfig, type: type, diff: diff})
|
||||
|
||||
var id;
|
||||
if (type === "full") {
|
||||
// A full start means everything should
|
||||
@@ -354,6 +354,8 @@ function start(type,diff,muteLog) {
|
||||
}
|
||||
}
|
||||
}
|
||||
events.emit("flows:started", {config: activeConfig, type: type, diff: diff});
|
||||
// Deprecated event
|
||||
events.emit("nodes-started");
|
||||
|
||||
if (credentialsPendingReset === true) {
|
||||
@@ -401,6 +403,8 @@ function stop(type,diff,muteLog) {
|
||||
stopList = diff.changed.concat(diff.removed).concat(diff.linked);
|
||||
}
|
||||
|
||||
events.emit("flows:stopping",{config: activeConfig, type: type, diff: diff})
|
||||
|
||||
for (var id in activeFlows) {
|
||||
if (activeFlows.hasOwnProperty(id)) {
|
||||
var flowStateChanged = diff && (diff.added.indexOf(id) !== -1 || diff.removed.indexOf(id) !== -1);
|
||||
@@ -432,6 +436,8 @@ function stop(type,diff,muteLog) {
|
||||
log.info(log._("nodes.flows.stopped-flows"));
|
||||
}
|
||||
}
|
||||
events.emit("flows:stopped",{config: activeConfig, type: type, diff: diff});
|
||||
// Deprecated event
|
||||
events.emit("nodes-stopped");
|
||||
});
|
||||
}
|
||||
@@ -481,7 +487,7 @@ function updateMissingTypes() {
|
||||
}
|
||||
}
|
||||
|
||||
function addFlow(flow, user) {
|
||||
async function addFlow(flow, user) {
|
||||
var i,node;
|
||||
if (!flow.hasOwnProperty('nodes')) {
|
||||
throw new Error('missing nodes property');
|
||||
@@ -506,10 +512,10 @@ function addFlow(flow, user) {
|
||||
node = flow.nodes[i];
|
||||
if (activeFlowConfig.allNodes[node.id]) {
|
||||
// TODO nls
|
||||
return when.reject(new Error('duplicate id'));
|
||||
throw new Error('duplicate id');
|
||||
}
|
||||
if (node.type === 'tab' || node.type === 'subflow') {
|
||||
return when.reject(new Error('invalid node type: '+node.type));
|
||||
throw new Error('invalid node type: '+node.type);
|
||||
}
|
||||
node.z = flow.id;
|
||||
nodes.push(node);
|
||||
@@ -519,10 +525,10 @@ function addFlow(flow, user) {
|
||||
node = flow.configs[i];
|
||||
if (activeFlowConfig.allNodes[node.id]) {
|
||||
// TODO nls
|
||||
return when.reject(new Error('duplicate id'));
|
||||
throw new Error('duplicate id');
|
||||
}
|
||||
if (node.type === 'tab' || node.type === 'subflow') {
|
||||
return when.reject(new Error('invalid node type: '+node.type));
|
||||
throw new Error('invalid node type: '+node.type);
|
||||
}
|
||||
node.z = flow.id;
|
||||
nodes.push(node);
|
||||
@@ -607,7 +613,7 @@ function getFlow(id) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function updateFlow(id,newFlow, user) {
|
||||
async function updateFlow(id,newFlow, user) {
|
||||
var label = id;
|
||||
if (id !== 'global') {
|
||||
if (!activeFlowConfig.flows[id]) {
|
||||
@@ -667,7 +673,7 @@ function updateFlow(id,newFlow, user) {
|
||||
})
|
||||
}
|
||||
|
||||
function removeFlow(id, user) {
|
||||
async function removeFlow(id, user) {
|
||||
if (id === 'global') {
|
||||
// TODO: nls + error code
|
||||
throw new Error('not allowed to remove global');
|
182
packages/node_modules/@node-red/runtime/lib/hooks.js
vendored
Normal file
182
packages/node_modules/@node-red/runtime/lib/hooks.js
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
const Log = require("@node-red/util").log;
|
||||
|
||||
const VALID_HOOKS = [
|
||||
// Message Routing Path
|
||||
"onSend",
|
||||
"preRoute",
|
||||
"preDeliver",
|
||||
"postDeliver",
|
||||
"onReceive",
|
||||
"postReceive",
|
||||
"onComplete"
|
||||
]
|
||||
|
||||
|
||||
// Flags for what hooks have handlers registered
|
||||
let states = { }
|
||||
|
||||
// Hooks by id
|
||||
let hooks = { }
|
||||
|
||||
// Hooks by label
|
||||
let labelledHooks = { }
|
||||
|
||||
/**
|
||||
* Runtime hooks engine
|
||||
*
|
||||
* The following hooks can be used:
|
||||
*
|
||||
* Message sending
|
||||
* - `onSend` - passed an array of `SendEvent` objects. The messages inside these objects are exactly what the node has passed to `node.send` - meaning there could be duplicate references to the same message object.
|
||||
* - `preRoute` - passed a `SendEvent`
|
||||
* - `preDeliver` - passed a `SendEvent`. The local router has identified the node it is going to send to. At this point, the message has been cloned if needed.
|
||||
* - `postDeliver` - passed a `SendEvent`. The message has been dispatched to be delivered asynchronously (unless the sync delivery flag is set, in which case it would be continue as synchronous delivery)
|
||||
* - `onReceive` - passed a `ReceiveEvent` when a node is about to receive a message
|
||||
* - `postReceive` - passed a `ReceiveEvent` when the message has been given to the node's `input` handler(s)
|
||||
* - `onComplete` - passed a `CompleteEvent` when the node has completed with a message or logged an error
|
||||
*
|
||||
* @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 (VALID_HOOKS.indexOf(id) === -1) {
|
||||
throw new Error(`Invalid hook '${id}'`);
|
||||
}
|
||||
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<hookList.length;i++) {
|
||||
removeHook(hookList[i],labelledHooks[label][hookList[i]])
|
||||
}
|
||||
delete labelledHooks[label];
|
||||
} else if (labelledHooks[label][id]) {
|
||||
removeHook(id,labelledHooks[label][id])
|
||||
delete labelledHooks[label][id];
|
||||
if (Object.keys(labelledHooks[label]).length === 0){
|
||||
delete labelledHooks[label];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeHook(id,callback) {
|
||||
let i = hooks[id].findIndex(hook => 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
hooks = {}
|
||||
labelledHooks = {}
|
||||
states = {}
|
||||
}
|
||||
|
||||
function has(hookId) {
|
||||
let [id, label] = hookId.split(".");
|
||||
if (label) {
|
||||
return !!(labelledHooks[label] && labelledHooks[label][id])
|
||||
}
|
||||
return !!states[id]
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
has,
|
||||
clear,
|
||||
add,
|
||||
remove,
|
||||
trigger
|
||||
}
|
@@ -19,9 +19,11 @@ var when = require('when');
|
||||
var externalAPI = require("./api");
|
||||
|
||||
var redNodes = require("./nodes");
|
||||
var flows = require("./flows");
|
||||
var storage = require("./storage");
|
||||
var library = require("./library");
|
||||
var events = require("./events");
|
||||
var hooks = require("./hooks");
|
||||
var settings = require("./settings");
|
||||
var exec = require("./exec");
|
||||
|
||||
@@ -272,7 +274,9 @@ var runtime = {
|
||||
settings: settings,
|
||||
storage: storage,
|
||||
events: events,
|
||||
hooks: hooks,
|
||||
nodes: redNodes,
|
||||
flows: flows,
|
||||
library: library,
|
||||
exec: exec,
|
||||
util: require("@node-red/util").util,
|
||||
@@ -355,6 +359,7 @@ module.exports = {
|
||||
|
||||
storage: storage,
|
||||
events: events,
|
||||
hooks: hooks,
|
||||
util: require("@node-red/util").util,
|
||||
get httpNode() { return nodeApp },
|
||||
get httpAdmin() { return adminApp },
|
||||
|
@@ -20,7 +20,9 @@ var EventEmitter = require("events").EventEmitter;
|
||||
var redUtil = require("@node-red/util").util;
|
||||
var Log = require("@node-red/util").log;
|
||||
var context = require("./context");
|
||||
var flows = require("./flows");
|
||||
var flows = require("../flows");
|
||||
const hooks = require("../hooks");
|
||||
|
||||
|
||||
const NOOP_SEND = function() {}
|
||||
|
||||
@@ -55,10 +57,6 @@ function Node(n) {
|
||||
// as part of its constructor - config._flow will overwrite this._flow
|
||||
// which we can tolerate as they are the same object.
|
||||
Object.defineProperty(this,'_flow', {value: n._flow, enumerable: false, writable: true })
|
||||
this._asyncDelivery = n._flow.asyncMessageDelivery;
|
||||
}
|
||||
if (this._asyncDelivery === undefined) {
|
||||
this._asyncDelivery = true;
|
||||
}
|
||||
this.updateWires(n.wires);
|
||||
}
|
||||
@@ -125,6 +123,11 @@ Node.prototype.context = function() {
|
||||
*/
|
||||
Node.prototype._complete = function(msg,error) {
|
||||
this.metric("done",msg);
|
||||
hooks.trigger("onComplete",{ msg: msg, error: error, node: { id: this.id, node: this }}, err => {
|
||||
if (err) {
|
||||
this.error(err);
|
||||
}
|
||||
})
|
||||
if (error) {
|
||||
// For now, delegate this to this.error
|
||||
// But at some point, the timeout handling will need to know about
|
||||
@@ -173,15 +176,7 @@ Node.prototype._emit = Node.prototype.emit;
|
||||
Node.prototype.emit = function(event, ...args) {
|
||||
var node = this;
|
||||
if (event === "input") {
|
||||
// When Pluggable Message Routing arrives, this will be called from
|
||||
// that and will already be sync/async depending on the router.
|
||||
if (this._asyncDelivery) {
|
||||
setImmediate(function() {
|
||||
node._emitInput.apply(node,args);
|
||||
});
|
||||
} else {
|
||||
this._emitInput.apply(this,args);
|
||||
}
|
||||
this._emitInput.apply(this,args);
|
||||
} else {
|
||||
this._emit.apply(this,arguments);
|
||||
}
|
||||
@@ -195,42 +190,53 @@ Node.prototype.emit = function(event, ...args) {
|
||||
Node.prototype._emitInput = function(arg) {
|
||||
var node = this;
|
||||
this.metric("receive", arg);
|
||||
if (node._inputCallback) {
|
||||
// Just one callback registered.
|
||||
try {
|
||||
node._inputCallback(
|
||||
arg,
|
||||
function() { node.send.apply(node,arguments) },
|
||||
function(err) { node._complete(arg,err); }
|
||||
);
|
||||
} catch(err) {
|
||||
node.error(err,arg);
|
||||
}
|
||||
} else if (node._inputCallbacks) {
|
||||
// Multiple callbacks registered. Call each one, tracking eventual completion
|
||||
var c = node._inputCallbacks.length;
|
||||
for (var i=0;i<c;i++) {
|
||||
var cb = node._inputCallbacks[i];
|
||||
if (cb.length === 2) {
|
||||
c++;
|
||||
}
|
||||
try {
|
||||
cb.call(
|
||||
node,
|
||||
arg,
|
||||
function() { node.send.apply(node,arguments) },
|
||||
function(err) {
|
||||
c--;
|
||||
if (c === 0) {
|
||||
node._complete(arg,err);
|
||||
}
|
||||
let receiveEvent = { msg:arg, destination: { id: this.id, node: this } }
|
||||
// onReceive - a node is about to receive a message
|
||||
hooks.trigger("onReceive",receiveEvent,(err) => {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
return
|
||||
} else if (err !== false) {
|
||||
if (node._inputCallback) {
|
||||
// Just one callback registered.
|
||||
try {
|
||||
node._inputCallback(
|
||||
arg,
|
||||
function() { node.send.apply(node,arguments) },
|
||||
function(err) { node._complete(arg,err); }
|
||||
);
|
||||
} catch(err) {
|
||||
node.error(err,arg);
|
||||
}
|
||||
} else if (node._inputCallbacks) {
|
||||
// Multiple callbacks registered. Call each one, tracking eventual completion
|
||||
var c = node._inputCallbacks.length;
|
||||
for (var i=0;i<c;i++) {
|
||||
var cb = node._inputCallbacks[i];
|
||||
if (cb.length === 2) {
|
||||
c++;
|
||||
}
|
||||
);
|
||||
} catch(err) {
|
||||
node.error(err,arg);
|
||||
try {
|
||||
cb.call(
|
||||
node,
|
||||
arg,
|
||||
function() { node.send.apply(node,arguments) },
|
||||
function(err) {
|
||||
c--;
|
||||
if (c === 0) {
|
||||
node._complete(arg,err);
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch(err) {
|
||||
node.error(err,arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
// postReceive - the message has been passed to the node's input handler
|
||||
hooks.trigger("postReceive",receiveEvent,(err) => {if (err) { node.error(err) }});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -366,11 +372,19 @@ Node.prototype.send = function(msg) {
|
||||
msg._msgid = redUtil.generateId();
|
||||
}
|
||||
this.metric("send",msg);
|
||||
node = this._flow.getNode(this._wire);
|
||||
/* istanbul ignore else */
|
||||
if (node) {
|
||||
node.receive(msg);
|
||||
}
|
||||
this._flow.send([{
|
||||
msg: msg,
|
||||
source: {
|
||||
id: this.id,
|
||||
node: this,
|
||||
port: 0
|
||||
},
|
||||
destination: {
|
||||
id: this._wire,
|
||||
node: undefined
|
||||
},
|
||||
cloneMessage: false
|
||||
}]);
|
||||
return;
|
||||
} else {
|
||||
msg = [msg];
|
||||
@@ -384,7 +398,7 @@ Node.prototype.send = function(msg) {
|
||||
var sendEvents = [];
|
||||
|
||||
var sentMessageId = null;
|
||||
|
||||
var hasMissingIds = false;
|
||||
// for each output of node eg. [msgs to output 0, msgs to output 1, ...]
|
||||
for (var i = 0; i < numOutputs; i++) {
|
||||
var wires = this.wires[i]; // wires leaving output i
|
||||
@@ -398,24 +412,31 @@ Node.prototype.send = function(msg) {
|
||||
var k = 0;
|
||||
// for each recipent node of that output
|
||||
for (var j = 0; j < wires.length; j++) {
|
||||
node = this._flow.getNode(wires[j]); // node at end of wire j
|
||||
if (node) {
|
||||
// for each msg to send eg. [[m1, m2, ...], ...]
|
||||
for (k = 0; k < msgs.length; k++) {
|
||||
var m = msgs[k];
|
||||
if (m !== null && m !== undefined) {
|
||||
/* istanbul ignore else */
|
||||
if (!sentMessageId) {
|
||||
sentMessageId = m._msgid;
|
||||
}
|
||||
if (msgSent) {
|
||||
var clonedmsg = redUtil.cloneMessage(m);
|
||||
sendEvents.push({n:node,m:clonedmsg});
|
||||
} else {
|
||||
sendEvents.push({n:node,m:m});
|
||||
msgSent = true;
|
||||
}
|
||||
// for each msg to send eg. [[m1, m2, ...], ...]
|
||||
for (k = 0; k < msgs.length; k++) {
|
||||
var m = msgs[k];
|
||||
if (m !== null && m !== undefined) {
|
||||
if (!m._msgid) {
|
||||
hasMissingIds = true;
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (!sentMessageId) {
|
||||
sentMessageId = m._msgid;
|
||||
}
|
||||
sendEvents.push({
|
||||
msg: m,
|
||||
source: {
|
||||
id: this.id,
|
||||
node: this,
|
||||
port: i
|
||||
},
|
||||
destination: {
|
||||
id: wires[j],
|
||||
node: undefined
|
||||
},
|
||||
cloneMessage: msgSent
|
||||
});
|
||||
msgSent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,21 +449,22 @@ Node.prototype.send = function(msg) {
|
||||
}
|
||||
this.metric("send",{_msgid:sentMessageId});
|
||||
|
||||
for (i=0;i<sendEvents.length;i++) {
|
||||
var ev = sendEvents[i];
|
||||
/* istanbul ignore else */
|
||||
if (!ev.m._msgid) {
|
||||
ev.m._msgid = sentMessageId;
|
||||
if (hasMissingIds) {
|
||||
for (i=0;i<sendEvents.length;i++) {
|
||||
var ev = sendEvents[i];
|
||||
/* istanbul ignore else */
|
||||
if (!ev.msg._msgid) {
|
||||
ev.msg._msgid = sentMessageId;
|
||||
}
|
||||
}
|
||||
ev.n.receive(ev.m);
|
||||
}
|
||||
this._flow.send(sendEvents);
|
||||
};
|
||||
|
||||
/**
|
||||
* Receive a message.
|
||||
*
|
||||
* This will emit the `input` event with the provided message.
|
||||
* As of 1.0, this will return *before* any 'input' callback handler is invoked.
|
||||
*/
|
||||
Node.prototype.receive = function(msg) {
|
||||
if (!msg) {
|
||||
|
@@ -18,7 +18,6 @@ var clone = require("clone");
|
||||
var log = require("@node-red/util").log;
|
||||
var util = require("@node-red/util").util;
|
||||
var memory = require("./memory");
|
||||
var flows;
|
||||
|
||||
var settings;
|
||||
|
||||
@@ -48,7 +47,6 @@ function logUnknownStore(name) {
|
||||
}
|
||||
|
||||
function init(_settings) {
|
||||
flows = require("../flows");
|
||||
settings = _settings;
|
||||
contexts = {};
|
||||
stores = {};
|
||||
@@ -513,39 +511,6 @@ function getContext(nodeId, flowId) {
|
||||
return newContext;
|
||||
}
|
||||
|
||||
//
|
||||
// function getContext(localId,flowId,parent) {
|
||||
// var contextId = localId;
|
||||
// if (flowId) {
|
||||
// contextId = localId+":"+flowId;
|
||||
// }
|
||||
// console.log("getContext",localId,flowId,"known?",contexts.hasOwnProperty(contextId));
|
||||
// if (contexts.hasOwnProperty(contextId)) {
|
||||
// return contexts[contextId];
|
||||
// }
|
||||
// var newContext = createContext(contextId,undefined,parent);
|
||||
// if (flowId) {
|
||||
// var node = flows.get(flowId);
|
||||
// console.log("flows,get",flowId,node&&node.type)
|
||||
// var parent = undefined;
|
||||
// if (node && node.type.startsWith("subflow:")) {
|
||||
// parent = node.context().flow;
|
||||
// }
|
||||
// else {
|
||||
// parent = createRootContext();
|
||||
// }
|
||||
// var flowContext = getContext(flowId,undefined,parent);
|
||||
// Object.defineProperty(newContext, 'flow', {
|
||||
// value: flowContext
|
||||
// });
|
||||
// }
|
||||
// Object.defineProperty(newContext, 'global', {
|
||||
// value: contexts['global']
|
||||
// })
|
||||
// contexts[contextId] = newContext;
|
||||
// return newContext;
|
||||
// }
|
||||
|
||||
function deleteContext(id,flowId) {
|
||||
if(!hasConfiguredStore){
|
||||
// only delete context if there's no configured storage.
|
||||
|
@@ -23,8 +23,8 @@ var util = require("util");
|
||||
var registry = require("@node-red/registry");
|
||||
|
||||
var credentials = require("./credentials");
|
||||
var flows = require("./flows");
|
||||
var flowUtil = require("./flows/util")
|
||||
var flows = require("../flows");
|
||||
var flowUtil = require("../flows/util")
|
||||
var context = require("./context");
|
||||
var Node = require("./Node");
|
||||
var log;
|
||||
|
8
packages/node_modules/node-red/lib/red.js
vendored
8
packages/node_modules/node-red/lib/red.js
vendored
@@ -145,6 +145,14 @@ module.exports = {
|
||||
*/
|
||||
events: runtime.events,
|
||||
|
||||
/**
|
||||
* Runtime hooks engine
|
||||
* @see @node-red/runtime_hooks
|
||||
* @memberof node-red
|
||||
*/
|
||||
hooks: runtime.hooks,
|
||||
|
||||
|
||||
/**
|
||||
* This provides access to the internal settings module of the
|
||||
* runtime.
|
||||
|
Reference in New Issue
Block a user