node-red/packages/node_modules/@node-red/nodes/core/function/10-function.js

317 lines
12 KiB
JavaScript
Raw Normal View History

2013-09-05 16:02:48 +02:00
/**
* Copyright JS Foundation and other contributors, http://js.foundation
2013-09-05 16:02:48 +02:00
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
2014-05-04 00:32:04 +02:00
module.exports = function(RED) {
2014-07-08 13:27:09 +02:00
"use strict";
2014-05-04 00:32:04 +02:00
var util = require("util");
var vm = require("vm");
2014-05-05 09:58:53 +02:00
function sendResults(node,send,_msgid,msgs,cloneFirstMessage) {
2015-03-17 14:40:12 +01:00
if (msgs == null) {
return;
} else if (!util.isArray(msgs)) {
msgs = [msgs];
}
var msgCount = 0;
for (var m=0; m<msgs.length; m++) {
2015-03-17 14:40:12 +01:00
if (msgs[m]) {
if (!util.isArray(msgs[m])) {
msgs[m] = [msgs[m]];
}
for (var n=0; n < msgs[m].length; n++) {
var msg = msgs[m][n];
if (msg !== null && msg !== undefined) {
if (typeof msg === 'object' && !Buffer.isBuffer(msg) && !util.isArray(msg)) {
if (msgCount === 0 && cloneFirstMessage !== false) {
msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
msg = msgs[m][n];
}
msg._msgid = _msgid;
msgCount++;
} else {
var type = typeof msg;
if (type === 'object') {
type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
}
node.error(RED._("function.error.non-message-returned",{ type: type }));
}
2015-03-17 14:40:12 +01:00
}
}
}
}
if (msgCount>0) {
2019-08-06 15:27:56 +02:00
send(msgs);
2015-03-17 14:40:12 +01:00
}
}
2014-05-04 00:32:04 +02:00
function FunctionNode(n) {
RED.nodes.createNode(this,n);
var node = this;
2014-05-04 00:32:04 +02:00
this.name = n.name;
this.func = n.func;
2019-08-06 15:27:56 +02:00
var handleNodeDoneCall = true;
// Check to see if the Function appears to call `node.done()`. If so,
// we will assume it is well written and does actually call node.done().
// Otherwise, we will call node.done() after the function returns regardless.
if (/node\.done\s*\(\s*\)/.test(this.func)) {
handleNodeDoneCall = false;
}
2015-03-17 14:40:12 +01:00
var functionText = "var results = null;"+
2019-08-06 15:27:56 +02:00
"results = (function(msg,__send__,__done__){ "+
2015-03-17 14:40:12 +01:00
"var __msgid__ = msg._msgid;"+
"var node = {"+
"id:__node__.id,"+
"name:__node__.name,"+
2015-03-17 14:40:12 +01:00
"log:__node__.log,"+
"error:__node__.error,"+
"warn:__node__.warn,"+
"debug:__node__.debug,"+
"trace:__node__.trace,"+
2015-03-17 14:40:12 +01:00
"on:__node__.on,"+
2015-10-15 14:33:01 +02:00
"status:__node__.status,"+
"send:function(msgs,cloneMsg){ __node__.send(__send__,__msgid__,msgs,cloneMsg);},"+
2019-08-06 15:27:56 +02:00
"done:__done__"+
2015-03-17 14:40:12 +01:00
"};\n"+
this.func+"\n"+
2019-08-06 15:27:56 +02:00
"})(msg,send,done);";
2014-05-04 00:32:04 +02:00
this.topic = n.topic;
this.outstandingTimers = [];
this.outstandingIntervals = [];
var sandbox = {
console:console,
util:util,
Buffer:Buffer,
Date: Date,
2016-06-13 23:16:36 +02:00
RED: {
util: RED.util
},
2015-03-17 14:40:12 +01:00
__node__: {
id: node.id,
name: node.name,
2015-03-17 14:40:12 +01:00
log: function() {
node.log.apply(node, arguments);
},
2015-10-15 14:33:01 +02:00
error: function() {
node.error.apply(node, arguments);
},
warn: function() {
node.warn.apply(node, arguments);
2015-03-17 14:40:12 +01:00
},
debug: function() {
node.debug.apply(node, arguments);
},
trace: function() {
node.trace.apply(node, arguments);
},
send: function(send, id, msgs, cloneMsg) {
sendResults(node, send, id, msgs, cloneMsg);
2015-03-17 14:40:12 +01:00
},
on: function() {
if (arguments[0] === "input") {
throw new Error(RED._("function.error.inputListener"));
}
2015-10-15 14:33:01 +02:00
node.on.apply(node, arguments);
},
status: function() {
node.status.apply(node, arguments);
}
},
context: {
set: function() {
node.context().set.apply(node,arguments);
},
get: function() {
return node.context().get.apply(node,arguments);
},
keys: function() {
return node.context().keys.apply(node,arguments);
},
get global() {
return node.context().global;
},
get flow() {
return node.context().flow;
}
},
flow: {
set: function() {
node.context().flow.set.apply(node,arguments);
},
get: function() {
return node.context().flow.get.apply(node,arguments);
},
keys: function() {
return node.context().flow.keys.apply(node,arguments);
}
},
global: {
set: function() {
node.context().global.set.apply(node,arguments);
},
get: function() {
return node.context().global.get.apply(node,arguments);
},
keys: function() {
return node.context().global.keys.apply(node,arguments);
}
2015-03-17 14:40:12 +01:00
},
2018-08-30 23:42:30 +02:00
env: {
get: function(envVar) {
var flow = node._flow;
return flow.getSetting(envVar);
2018-08-30 23:42:30 +02:00
}
},
setTimeout: function () {
var func = arguments[0];
var timerId;
arguments[0] = function() {
sandbox.clearTimeout(timerId);
try {
func.apply(this,arguments);
} catch(err) {
node.error(err,{});
}
};
timerId = setTimeout.apply(this,arguments);
node.outstandingTimers.push(timerId);
return timerId;
},
clearTimeout: function(id) {
clearTimeout(id);
var index = node.outstandingTimers.indexOf(id);
if (index > -1) {
node.outstandingTimers.splice(index,1);
}
},
setInterval: function() {
var func = arguments[0];
var timerId;
arguments[0] = function() {
try {
func.apply(this,arguments);
} catch(err) {
node.error(err,{});
}
};
timerId = setInterval.apply(this,arguments);
node.outstandingIntervals.push(timerId);
return timerId;
},
clearInterval: function(id) {
clearInterval(id);
var index = node.outstandingIntervals.indexOf(id);
if (index > -1) {
node.outstandingIntervals.splice(index,1);
}
}
};
if (util.hasOwnProperty('promisify')) {
sandbox.setTimeout[util.promisify.custom] = function(after, value) {
return new Promise(function(resolve, reject) {
sandbox.setTimeout(function(){ resolve(value); }, after);
});
};
}
var context = vm.createContext(sandbox);
2014-05-04 00:32:04 +02:00
try {
this.script = vm.createScript(functionText, {
filename: 'Function node:'+this.id+(this.name?' ['+this.name+']':''), // filename for stack traces
displayErrors: true
// Using the following options causes node 4/6 to not include the line number
// in the stack output. So don't use them.
// lineOffset: -11, // line number offset to be used for stack traces
// columnOffset: 0, // column number offset to be used for stack traces
});
2019-08-06 15:27:56 +02:00
this.on("input", function(msg,send,done) {
2014-09-08 22:10:06 +02:00
try {
var start = process.hrtime();
context.msg = msg;
2019-08-06 15:27:56 +02:00
context.send = send;
context.done = done;
2014-09-08 22:10:06 +02:00
this.script.runInContext(context);
sendResults(this,send,msg._msgid,context.results,false);
2019-08-06 15:27:56 +02:00
if (handleNodeDoneCall) {
done();
}
2015-02-04 11:01:46 +01:00
var duration = process.hrtime(start);
2015-10-15 14:33:01 +02:00
var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100;
2015-02-04 11:01:46 +01:00
this.metric("duration", msg, converted);
if (process.env.NODE_RED_FUNCTION_TIME) {
this.status({fill:"yellow",shape:"dot",text:""+converted});
2014-09-08 22:10:06 +02:00
}
} catch(err) {
if ((typeof err === "object") && err.hasOwnProperty("stack")) {
//remove unwanted part
var index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/);
err.stack = err.stack.slice(0, index).split('\n').slice(0,-1).join('\n');
var stack = err.stack.split(/\r?\n/);
2018-04-17 15:46:09 +02:00
//store the error in msg to be used in flows
msg.error = err;
var line = 0;
var errorMessage;
if (stack.length > 0) {
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
line++;
}
if (line < stack.length) {
errorMessage = stack[line];
var m = /:(\d+):(\d+)$/.exec(stack[line+1]);
if (m) {
var lineno = Number(m[1])-1;
var cha = m[2];
errorMessage += " (line "+lineno+", col "+cha+")";
}
}
}
if (!errorMessage) {
errorMessage = err.toString();
}
done(errorMessage);
}
else if (typeof err === "string") {
done(err);
}
else {
done(JSON.stringify(err));
}
2014-05-05 09:58:53 +02:00
}
2014-05-04 00:32:04 +02:00
});
this.on("close", function() {
while (node.outstandingTimers.length > 0) {
clearTimeout(node.outstandingTimers.pop());
}
while (node.outstandingIntervals.length > 0) {
clearInterval(node.outstandingIntervals.pop());
}
this.status({});
});
2014-05-04 00:32:04 +02:00
} catch(err) {
// eg SyntaxError - which v8 doesn't include line number information
// so we can't do better than this
2014-05-04 00:32:04 +02:00
this.error(err);
}
2013-09-05 16:02:48 +02:00
}
2014-05-04 00:32:04 +02:00
RED.nodes.registerType("function",FunctionNode);
RED.library.register("functions");
};