2014-08-28 15:25:41 +02:00
|
|
|
/**
|
2017-01-11 16:24:33 +01:00
|
|
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
2014-08-28 15:25:41 +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.
|
2018-08-22 11:00:03 +02:00
|
|
|
* @ignore
|
2014-08-28 15:25:41 +02:00
|
|
|
**/
|
|
|
|
|
2018-08-28 14:45:38 +02:00
|
|
|
/**
|
|
|
|
* @module util
|
|
|
|
* @memberof module:@node-red/util
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
const clone = require("clone");
|
|
|
|
const jsonata = require("jsonata");
|
|
|
|
const safeJSONStringify = require("json-stringify-safe");
|
|
|
|
const util = require("util");
|
2014-11-06 12:39:30 +01:00
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
/**
|
|
|
|
* Generates a psuedo-unique-random id.
|
|
|
|
* @return {String} a random-ish id
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2015-06-02 16:54:37 +02:00
|
|
|
function generateId() {
|
|
|
|
return (1+Math.random()*4294967295).toString(16);
|
|
|
|
}
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
/**
|
|
|
|
* Converts the provided argument to a String, using type-dependent
|
|
|
|
* methods.
|
|
|
|
*
|
|
|
|
* @param {any} o - the property to convert to a String
|
|
|
|
* @return {String} the stringified version
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2014-08-28 15:25:41 +02:00
|
|
|
function ensureString(o) {
|
|
|
|
if (Buffer.isBuffer(o)) {
|
|
|
|
return o.toString();
|
|
|
|
} else if (typeof o === "object") {
|
|
|
|
return JSON.stringify(o);
|
|
|
|
} else if (typeof o === "string") {
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
return ""+o;
|
|
|
|
}
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
/**
|
|
|
|
* Converts the provided argument to a Buffer, using type-dependent
|
|
|
|
* methods.
|
|
|
|
*
|
|
|
|
* @param {any} o - the property to convert to a Buffer
|
|
|
|
* @return {String} the Buffer version
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2014-09-10 13:46:56 +02:00
|
|
|
function ensureBuffer(o) {
|
|
|
|
if (Buffer.isBuffer(o)) {
|
|
|
|
return o;
|
|
|
|
} else if (typeof o === "object") {
|
|
|
|
o = JSON.stringify(o);
|
|
|
|
} else if (typeof o !== "string") {
|
|
|
|
o = ""+o;
|
|
|
|
}
|
|
|
|
return new Buffer(o);
|
|
|
|
}
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
/**
|
|
|
|
* Safely clones a message object. This handles msg.req/msg.res objects that must
|
|
|
|
* not be cloned.
|
|
|
|
*
|
|
|
|
* @param {any} msg - the message object to clone
|
|
|
|
* @return {Object} the cloned message
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2014-11-06 12:39:30 +01:00
|
|
|
function cloneMessage(msg) {
|
|
|
|
// Temporary fix for #97
|
|
|
|
// TODO: remove this http-node-specific fix somehow
|
|
|
|
var req = msg.req;
|
|
|
|
var res = msg.res;
|
|
|
|
delete msg.req;
|
|
|
|
delete msg.res;
|
|
|
|
var m = clone(msg);
|
|
|
|
if (req) {
|
|
|
|
m.req = req;
|
|
|
|
msg.req = req;
|
|
|
|
}
|
|
|
|
if (res) {
|
|
|
|
m.res = res;
|
|
|
|
msg.res = res;
|
|
|
|
}
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
/**
|
|
|
|
* Compares two objects, handling various JavaScript types.
|
|
|
|
*
|
|
|
|
* @param {any} obj1
|
|
|
|
* @param {any} obj2
|
|
|
|
* @return {boolean} whether the two objects are the same
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2014-11-23 23:25:09 +01:00
|
|
|
function compareObjects(obj1,obj2) {
|
2016-04-01 11:12:15 +02:00
|
|
|
var i;
|
2014-11-23 23:25:09 +01:00
|
|
|
if (obj1 === obj2) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (obj1 == null || obj2 == null) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-01 11:12:15 +02:00
|
|
|
|
2014-11-23 23:25:09 +01:00
|
|
|
var isArray1 = Array.isArray(obj1);
|
|
|
|
var isArray2 = Array.isArray(obj2);
|
|
|
|
if (isArray1 != isArray2) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (isArray1 && isArray2) {
|
2016-04-01 11:12:15 +02:00
|
|
|
if (obj1.length !== obj2.length) {
|
2014-11-23 23:25:09 +01:00
|
|
|
return false;
|
|
|
|
}
|
2016-04-01 11:12:15 +02:00
|
|
|
for (i=0;i<obj1.length;i++) {
|
2014-11-23 23:25:09 +01:00
|
|
|
if (!compareObjects(obj1[i],obj2[i])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2016-04-01 11:12:15 +02:00
|
|
|
var isBuffer1 = Buffer.isBuffer(obj1);
|
|
|
|
var isBuffer2 = Buffer.isBuffer(obj2);
|
|
|
|
if (isBuffer1 != isBuffer2) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (isBuffer1 && isBuffer2) {
|
2016-04-01 11:32:11 +02:00
|
|
|
if (obj1.equals) {
|
|
|
|
// For node 0.12+ - use the native equals
|
|
|
|
return obj1.equals(obj2);
|
2016-04-01 11:12:15 +02:00
|
|
|
} else {
|
|
|
|
if (obj1.length !== obj2.length) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (i=0;i<obj1.length;i++) {
|
|
|
|
if (obj1.readUInt8(i) !== obj2.readUInt8(i)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
|
|
|
|
return false;
|
|
|
|
}
|
2014-11-23 23:25:09 +01:00
|
|
|
var keys1 = Object.keys(obj1);
|
|
|
|
var keys2 = Object.keys(obj2);
|
|
|
|
if (keys1.length != keys2.length) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (var k in obj1) {
|
2015-03-06 15:14:47 +01:00
|
|
|
/* istanbul ignore else */
|
2014-11-23 23:25:09 +01:00
|
|
|
if (obj1.hasOwnProperty(k)) {
|
|
|
|
if (!compareObjects(obj1[k],obj2[k])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
/**
|
|
|
|
* Parses a property expression, such as `msg.foo.bar[3]` to validate it
|
|
|
|
* and convert it to a canonical version expressed as an Array of property
|
|
|
|
* names.
|
|
|
|
*
|
|
|
|
* For example, `a["b"].c` returns `['a','b','c']`
|
|
|
|
*
|
|
|
|
* @param {String} str - the property expression
|
|
|
|
* @return {Array} the normalised expression
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2016-06-08 00:01:23 +02:00
|
|
|
function normalisePropertyExpression(str) {
|
2017-01-06 12:23:19 +01:00
|
|
|
// This must be kept in sync with validatePropertyExpression
|
|
|
|
// in editor/js/ui/utils.js
|
|
|
|
|
2016-06-08 00:01:23 +02:00
|
|
|
var length = str.length;
|
2017-01-06 22:58:17 +01:00
|
|
|
if (length === 0) {
|
|
|
|
throw new Error("Invalid property expression: zero-length");
|
|
|
|
}
|
2016-06-08 00:01:23 +02:00
|
|
|
var parts = [];
|
|
|
|
var start = 0;
|
|
|
|
var inString = false;
|
|
|
|
var inBox = false;
|
|
|
|
var quoteChar;
|
|
|
|
var v;
|
|
|
|
for (var i=0;i<length;i++) {
|
|
|
|
var c = str[i];
|
|
|
|
if (!inString) {
|
|
|
|
if (c === "'" || c === '"') {
|
2017-01-06 12:23:19 +01:00
|
|
|
if (i != start) {
|
2016-06-08 00:01:23 +02:00
|
|
|
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
|
|
|
|
}
|
|
|
|
inString = true;
|
|
|
|
quoteChar = c;
|
|
|
|
start = i+1;
|
|
|
|
} else if (c === '.') {
|
2016-06-13 22:59:20 +02:00
|
|
|
if (i===0) {
|
|
|
|
throw new Error("Invalid property expression: unexpected . at position 0");
|
|
|
|
}
|
2016-06-08 00:01:23 +02:00
|
|
|
if (start != i) {
|
|
|
|
v = str.substring(start,i);
|
|
|
|
if (/^\d+$/.test(v)) {
|
|
|
|
parts.push(parseInt(v));
|
|
|
|
} else {
|
|
|
|
parts.push(v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i===length-1) {
|
|
|
|
throw new Error("Invalid property expression: unterminated expression");
|
|
|
|
}
|
2017-01-06 15:32:37 +01:00
|
|
|
// Next char is first char of an identifier: a-z 0-9 $ _
|
2016-11-21 22:36:18 +01:00
|
|
|
if (!/[a-z0-9\$\_]/i.test(str[i+1])) {
|
2016-06-08 00:01:23 +02:00
|
|
|
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
|
|
|
|
}
|
|
|
|
start = i+1;
|
|
|
|
} else if (c === '[') {
|
|
|
|
if (i === 0) {
|
|
|
|
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
|
|
|
|
}
|
|
|
|
if (start != i) {
|
|
|
|
parts.push(str.substring(start,i));
|
|
|
|
}
|
|
|
|
if (i===length-1) {
|
|
|
|
throw new Error("Invalid property expression: unterminated expression");
|
|
|
|
}
|
|
|
|
// Next char is either a quote or a number
|
|
|
|
if (!/["'\d]/.test(str[i+1])) {
|
|
|
|
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
|
|
|
|
}
|
|
|
|
start = i+1;
|
|
|
|
inBox = true;
|
|
|
|
} else if (c === ']') {
|
|
|
|
if (!inBox) {
|
|
|
|
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
|
|
|
|
}
|
|
|
|
if (start != i) {
|
|
|
|
v = str.substring(start,i);
|
|
|
|
if (/^\d+$/.test(v)) {
|
|
|
|
parts.push(parseInt(v));
|
|
|
|
} else {
|
|
|
|
throw new Error("Invalid property expression: unexpected array expression at position "+start);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
start = i+1;
|
|
|
|
inBox = false;
|
2016-06-13 22:59:20 +02:00
|
|
|
} else if (c === ' ') {
|
|
|
|
throw new Error("Invalid property expression: unexpected ' ' at position "+i);
|
2016-06-08 00:01:23 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (c === quoteChar) {
|
2017-01-06 12:23:19 +01:00
|
|
|
if (i-start === 0) {
|
|
|
|
throw new Error("Invalid property expression: zero-length string at position "+start);
|
|
|
|
}
|
2016-06-08 00:01:23 +02:00
|
|
|
parts.push(str.substring(start,i));
|
2017-01-06 12:23:19 +01:00
|
|
|
// If inBox, next char must be a ]. Otherwise it may be [ or .
|
|
|
|
if (inBox && !/\]/.test(str[i+1])) {
|
2016-06-08 00:01:23 +02:00
|
|
|
throw new Error("Invalid property expression: unexpected array expression at position "+start);
|
2017-01-06 12:23:19 +01:00
|
|
|
} else if (!inBox && i+1!==length && !/[\[\.]/.test(str[i+1])) {
|
|
|
|
throw new Error("Invalid property expression: unexpected "+str[i+1]+" expression at position "+(i+1));
|
2016-06-08 00:01:23 +02:00
|
|
|
}
|
|
|
|
start = i+1;
|
|
|
|
inString = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
if (inBox || inString) {
|
|
|
|
throw new Error("Invalid property expression: unterminated expression");
|
|
|
|
}
|
|
|
|
if (start < length) {
|
|
|
|
parts.push(str.substring(start));
|
|
|
|
}
|
|
|
|
return parts;
|
|
|
|
}
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
/**
|
|
|
|
* Gets a property of a message object.
|
|
|
|
*
|
2018-08-24 14:02:06 +02:00
|
|
|
* Unlike {@link @node-red/util-util.getObjectProperty}, this function will strip `msg.` from the
|
2018-08-22 11:00:03 +02:00
|
|
|
* front of the property expression if present.
|
|
|
|
*
|
|
|
|
* @param {Object} msg - the message object
|
|
|
|
* @param {String} str - the property expression
|
|
|
|
* @return {any} the message property, or undefined if it does not exist
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2015-12-29 23:19:32 +01:00
|
|
|
function getMessageProperty(msg,expr) {
|
|
|
|
if (expr.indexOf('msg.')===0) {
|
|
|
|
expr = expr.substring(4);
|
|
|
|
}
|
2018-07-25 10:27:27 +02:00
|
|
|
return getObjectProperty(msg,expr);
|
|
|
|
}
|
2018-08-22 11:00:03 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a property of an object.
|
|
|
|
*
|
|
|
|
* @param {Object} msg - the object
|
|
|
|
* @param {String} str - the property expression
|
|
|
|
* @return {any} the object property, or undefined if it does not exist
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2018-07-25 10:27:27 +02:00
|
|
|
function getObjectProperty(msg,expr) {
|
|
|
|
var result = null;
|
2016-06-08 00:01:23 +02:00
|
|
|
var msgPropParts = normalisePropertyExpression(expr);
|
|
|
|
var m;
|
|
|
|
msgPropParts.reduce(function(obj, key) {
|
|
|
|
result = (typeof obj[key] !== "undefined" ? obj[key] : undefined);
|
2015-12-29 23:19:32 +01:00
|
|
|
return result;
|
|
|
|
}, msg);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
/**
|
|
|
|
* Sets a property of a message object.
|
|
|
|
*
|
2018-08-24 14:02:06 +02:00
|
|
|
* Unlike {@link @node-red/util-util.setObjectProperty}, this function will strip `msg.` from the
|
2018-08-22 11:00:03 +02:00
|
|
|
* front of the property expression if present.
|
|
|
|
*
|
|
|
|
* @param {Object} msg - the message object
|
|
|
|
* @param {String} prop - the property expression
|
|
|
|
* @param {any} value - the value to set
|
|
|
|
* @param {boolean} createMissing - whether to create missing parent properties
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2015-12-29 23:19:32 +01:00
|
|
|
function setMessageProperty(msg,prop,value,createMissing) {
|
|
|
|
if (prop.indexOf('msg.')===0) {
|
|
|
|
prop = prop.substring(4);
|
|
|
|
}
|
2018-07-25 10:27:27 +02:00
|
|
|
return setObjectProperty(msg,prop,value,createMissing);
|
|
|
|
}
|
2018-08-22 11:00:03 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets a property of an object.
|
|
|
|
*
|
|
|
|
* @param {Object} msg - the object
|
|
|
|
* @param {String} prop - the property expression
|
|
|
|
* @param {any} value - the value to set
|
|
|
|
* @param {boolean} createMissing - whether to create missing parent properties
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2018-07-25 10:27:27 +02:00
|
|
|
function setObjectProperty(msg,prop,value,createMissing) {
|
|
|
|
if (typeof createMissing === 'undefined') {
|
|
|
|
createMissing = (typeof value !== 'undefined');
|
|
|
|
}
|
2016-06-08 00:01:23 +02:00
|
|
|
var msgPropParts = normalisePropertyExpression(prop);
|
2015-12-29 23:19:32 +01:00
|
|
|
var depth = 0;
|
2016-06-08 00:01:23 +02:00
|
|
|
var length = msgPropParts.length;
|
|
|
|
var obj = msg;
|
|
|
|
var key;
|
|
|
|
for (var i=0;i<length-1;i++) {
|
|
|
|
key = msgPropParts[i];
|
|
|
|
if (typeof key === 'string' || (typeof key === 'number' && !Array.isArray(obj))) {
|
|
|
|
if (obj.hasOwnProperty(key)) {
|
|
|
|
obj = obj[key];
|
|
|
|
} else if (createMissing) {
|
|
|
|
if (typeof msgPropParts[i+1] === 'string') {
|
|
|
|
obj[key] = {};
|
|
|
|
} else {
|
|
|
|
obj[key] = [];
|
|
|
|
}
|
|
|
|
obj = obj[key];
|
2015-12-29 23:19:32 +01:00
|
|
|
} else {
|
2016-06-08 00:01:23 +02:00
|
|
|
return null;
|
2015-12-29 23:19:32 +01:00
|
|
|
}
|
2016-06-08 00:01:23 +02:00
|
|
|
} else if (typeof key === 'number') {
|
|
|
|
// obj is an array
|
|
|
|
if (obj[key] === undefined) {
|
2015-12-29 23:19:32 +01:00
|
|
|
if (createMissing) {
|
2016-06-08 00:01:23 +02:00
|
|
|
if (typeof msgPropParts[i+1] === 'string') {
|
|
|
|
obj[key] = {};
|
|
|
|
} else {
|
|
|
|
obj[key] = [];
|
|
|
|
}
|
|
|
|
obj = obj[key];
|
2015-12-29 23:19:32 +01:00
|
|
|
} else {
|
2018-07-25 02:15:27 +02:00
|
|
|
return null;
|
2015-12-29 23:19:32 +01:00
|
|
|
}
|
2016-06-08 00:01:23 +02:00
|
|
|
} else {
|
|
|
|
obj = obj[key];
|
2015-12-29 23:19:32 +01:00
|
|
|
}
|
|
|
|
}
|
2016-06-08 00:01:23 +02:00
|
|
|
}
|
|
|
|
key = msgPropParts[length-1];
|
|
|
|
if (typeof value === "undefined") {
|
|
|
|
if (typeof key === 'number' && Array.isArray(obj)) {
|
|
|
|
obj.splice(key,1);
|
|
|
|
} else {
|
|
|
|
delete obj[key]
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
obj[key] = value;
|
|
|
|
}
|
2015-12-29 23:19:32 +01:00
|
|
|
}
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
/**
|
|
|
|
* Checks if a String contains any Environment Variable specifiers and returns
|
|
|
|
* it with their values substituted in place.
|
|
|
|
*
|
|
|
|
* For example, if the env var `WHO` is set to `Joe`, the string `Hello ${WHO}!`
|
|
|
|
* will return `Hello Joe!`.
|
|
|
|
* @param {String} value - the string to parse
|
|
|
|
* @return {String} The parsed string
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2018-07-16 09:09:15 +02:00
|
|
|
function evaluateEnvProperty(value) {
|
2018-05-21 16:10:06 +02:00
|
|
|
if (/^\${[^}]+}$/.test(value)) {
|
|
|
|
// ${ENV_VAR}
|
|
|
|
value = value.substring(2,value.length-1);
|
|
|
|
value = process.env.hasOwnProperty(value)?process.env[value]:""
|
|
|
|
} else if (!/\${\S+}/.test(value)) {
|
|
|
|
// ENV_VAR
|
|
|
|
value = process.env.hasOwnProperty(value)?process.env[value]:""
|
|
|
|
} else {
|
|
|
|
// FOO${ENV_VAR}BAR
|
|
|
|
value = value.replace(/\${([^}]+)}/g, function(match, v) {
|
|
|
|
return process.env.hasOwnProperty(v)?process.env[v]:""
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Parses a context property string, as generated by the TypedInput, to extract
|
|
|
|
* the store name if present.
|
|
|
|
*
|
|
|
|
* For example, `#:(file)::foo` results in ` { store: "file", key: "foo" }`.
|
|
|
|
*
|
|
|
|
* @param {String} value - the context property string to parse
|
|
|
|
* @return {Object} The parsed property
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
|
|
|
function parseContextStore(key) {
|
2018-07-05 11:43:33 +02:00
|
|
|
var parts = {};
|
|
|
|
var m = /^#:\((\S+?)\)::(.*)$/.exec(key);
|
|
|
|
if (m) {
|
|
|
|
parts.store = m[1];
|
|
|
|
parts.key = m[2];
|
|
|
|
} else {
|
|
|
|
parts.key = key;
|
|
|
|
}
|
|
|
|
return parts;
|
|
|
|
}
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Evaluates a property value according to its type.
|
|
|
|
*
|
|
|
|
* @param {String} value - the raw value
|
|
|
|
* @param {String} type - the type of the value
|
|
|
|
* @param {Node} node - the node evaluating the property
|
|
|
|
* @param {Object} msg - the message object to evaluate against
|
|
|
|
* @param {Function} callback - (optional) called when the property is evaluated
|
|
|
|
* @return {any} The evaluted property, if no `callback` is provided
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2018-07-05 11:43:33 +02:00
|
|
|
function evaluateNodeProperty(value, type, node, msg, callback) {
|
2018-07-09 13:40:25 +02:00
|
|
|
var result = value;
|
2015-12-31 00:09:35 +01:00
|
|
|
if (type === 'str') {
|
2018-07-05 11:43:33 +02:00
|
|
|
result = ""+value;
|
2015-12-31 00:09:35 +01:00
|
|
|
} else if (type === 'num') {
|
2018-07-05 11:43:33 +02:00
|
|
|
result = Number(value);
|
2015-12-31 00:09:35 +01:00
|
|
|
} else if (type === 'json') {
|
2018-07-05 11:43:33 +02:00
|
|
|
result = JSON.parse(value);
|
2015-12-31 00:09:35 +01:00
|
|
|
} else if (type === 're') {
|
2018-07-05 11:43:33 +02:00
|
|
|
result = new RegExp(value);
|
2016-04-18 15:38:32 +02:00
|
|
|
} else if (type === 'date') {
|
2018-07-05 11:43:33 +02:00
|
|
|
result = Date.now();
|
2017-06-11 22:19:46 +02:00
|
|
|
} else if (type === 'bin') {
|
|
|
|
var data = JSON.parse(value);
|
2018-07-05 11:43:33 +02:00
|
|
|
result = Buffer.from(data);
|
2015-12-31 00:09:35 +01:00
|
|
|
} else if (type === 'msg' && msg) {
|
2018-07-09 12:30:53 +02:00
|
|
|
try {
|
|
|
|
result = getMessageProperty(msg,value);
|
|
|
|
} catch(err) {
|
|
|
|
if (callback) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2018-07-05 11:43:33 +02:00
|
|
|
} else if ((type === 'flow' || type === 'global') && node) {
|
|
|
|
var contextKey = parseContextStore(value);
|
|
|
|
result = node.context()[type].get(contextKey.key,contextKey.store,callback);
|
|
|
|
if (callback) {
|
|
|
|
return;
|
|
|
|
}
|
2016-01-03 23:26:47 +01:00
|
|
|
} else if (type === 'bool') {
|
2018-07-05 11:43:33 +02:00
|
|
|
result = /^true$/i.test(value);
|
2016-11-11 00:58:34 +01:00
|
|
|
} else if (type === 'jsonata') {
|
2017-05-05 12:23:24 +02:00
|
|
|
var expr = prepareJSONataExpression(value,node);
|
2018-07-05 11:43:33 +02:00
|
|
|
result = evaluateJSONataExpression(expr,msg);
|
2018-05-21 16:10:06 +02:00
|
|
|
} else if (type === 'env') {
|
2018-07-16 09:09:15 +02:00
|
|
|
result = evaluateEnvProperty(value);
|
2018-07-05 11:43:33 +02:00
|
|
|
}
|
|
|
|
if (callback) {
|
2018-07-09 12:30:53 +02:00
|
|
|
callback(null,result);
|
2018-07-05 11:43:33 +02:00
|
|
|
} else {
|
2018-07-07 23:09:55 +02:00
|
|
|
return result;
|
2015-12-31 00:09:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepares a JSONata expression for evaluation.
|
|
|
|
* This attaches Node-RED specific functions to the expression.
|
|
|
|
*
|
|
|
|
* @param {String} value - the JSONata expression
|
|
|
|
* @param {Node} node - the node evaluating the property
|
|
|
|
* @return {Object} The JSONata expression that can be evaluated
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2017-05-03 16:48:30 +02:00
|
|
|
function prepareJSONataExpression(value,node) {
|
|
|
|
var expr = jsonata(value);
|
2018-07-25 12:07:29 +02:00
|
|
|
expr.assign('flowContext',function(val) {
|
|
|
|
return node.context().flow.get(val);
|
2017-05-03 16:48:30 +02:00
|
|
|
});
|
2018-07-25 12:07:29 +02:00
|
|
|
expr.assign('globalContext',function(val) {
|
|
|
|
return node.context().global.get(val);
|
2017-05-03 16:48:30 +02:00
|
|
|
});
|
2018-05-21 16:28:15 +02:00
|
|
|
expr.assign('env', function(val) {
|
|
|
|
return process.env[val];
|
|
|
|
})
|
2018-01-13 21:42:23 +01:00
|
|
|
expr.registerFunction('clone', cloneMessage, '<(oa)-:o>');
|
2017-05-05 12:23:24 +02:00
|
|
|
expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value);
|
2018-07-09 16:12:09 +02:00
|
|
|
expr._node = node;
|
2017-05-03 16:48:30 +02:00
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
/**
|
|
|
|
* Evaluates a JSONata expression.
|
2018-08-24 14:02:06 +02:00
|
|
|
* The expression must have been prepared with {@link @node-red/util-util.prepareJSONataExpression}
|
2018-08-22 11:00:03 +02:00
|
|
|
* before passing to this function.
|
|
|
|
*
|
|
|
|
* @param {Object} expr - the prepared JSONata expression
|
|
|
|
* @param {Object} msg - the message object to evaluate against
|
|
|
|
* @param {Function} callback - (optional) called when the expression is evaluated
|
|
|
|
* @return {any} If no callback was provided, the result of the expression
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2018-07-09 16:12:09 +02:00
|
|
|
function evaluateJSONataExpression(expr,msg,callback) {
|
2017-05-05 12:23:24 +02:00
|
|
|
var context = msg;
|
|
|
|
if (expr._legacyMode) {
|
|
|
|
context = {msg:msg};
|
|
|
|
}
|
2018-07-09 16:12:09 +02:00
|
|
|
var bindings = {};
|
|
|
|
|
|
|
|
if (callback) {
|
|
|
|
// If callback provided, need to override the pre-assigned sync
|
|
|
|
// context functions to be their async variants
|
2018-07-13 11:12:06 +02:00
|
|
|
bindings.flowContext = function(val, store) {
|
2018-07-09 16:12:09 +02:00
|
|
|
return new Promise((resolve,reject) => {
|
2018-07-13 11:12:06 +02:00
|
|
|
expr._node.context().flow.get(val, store, function(err,value) {
|
2018-07-09 16:12:09 +02:00
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve(value);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
});
|
|
|
|
}
|
2018-07-13 11:12:06 +02:00
|
|
|
bindings.globalContext = function(val, store) {
|
2018-07-09 16:12:09 +02:00
|
|
|
return new Promise((resolve,reject) => {
|
2018-07-13 11:12:06 +02:00
|
|
|
expr._node.context().global.get(val, store, function(err,value) {
|
2018-07-09 16:12:09 +02:00
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve(value);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return expr.evaluate(context, bindings, callback);
|
2017-05-05 12:23:24 +02:00
|
|
|
}
|
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
/**
|
|
|
|
* Normalise a node type name to camel case.
|
|
|
|
*
|
|
|
|
* For example: `a-random node type` will normalise to `aRandomNodeType`
|
|
|
|
*
|
|
|
|
* @param {String} name - the node type
|
|
|
|
* @return {String} The normalised name
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2017-03-08 15:38:33 +01:00
|
|
|
function normaliseNodeTypeName(name) {
|
|
|
|
var result = name.replace(/[^a-zA-Z0-9]/g, " ");
|
|
|
|
result = result.trim();
|
|
|
|
result = result.replace(/ +/g, " ");
|
|
|
|
result = result.replace(/ ./g,
|
|
|
|
function(s) {
|
|
|
|
return s.charAt(1).toUpperCase();
|
|
|
|
}
|
|
|
|
);
|
|
|
|
result = result.charAt(0).toLowerCase() + result.slice(1);
|
|
|
|
return result;
|
|
|
|
}
|
2015-12-31 00:09:35 +01:00
|
|
|
|
2018-08-22 11:00:03 +02:00
|
|
|
/**
|
|
|
|
* Encode an object to JSON without losing information about non-JSON types
|
|
|
|
* such as Buffer and Function.
|
|
|
|
*
|
|
|
|
* *This function is closely tied to its reverse within the editor*
|
|
|
|
*
|
|
|
|
* @param {Object} msg
|
|
|
|
* @param {Object} opts
|
|
|
|
* @return {Object} the encoded object
|
2018-08-28 14:45:38 +02:00
|
|
|
* @memberof module:@node-red/util.module:util
|
2018-08-22 11:00:03 +02:00
|
|
|
*/
|
2018-06-25 23:31:11 +02:00
|
|
|
function encodeObject(msg,opts) {
|
|
|
|
var debuglength = 1000;
|
|
|
|
if (opts && opts.hasOwnProperty('maxLength')) {
|
|
|
|
debuglength = opts.maxLength;
|
|
|
|
}
|
|
|
|
var msgType = typeof msg.msg;
|
|
|
|
if (msg.msg instanceof Error) {
|
|
|
|
msg.format = "error";
|
|
|
|
var errorMsg = {};
|
|
|
|
if (msg.msg.name) {
|
|
|
|
errorMsg.name = msg.msg.name;
|
|
|
|
}
|
|
|
|
if (msg.msg.hasOwnProperty('message')) {
|
|
|
|
errorMsg.message = msg.msg.message;
|
|
|
|
} else {
|
|
|
|
errorMsg.message = msg.msg.toString();
|
|
|
|
}
|
|
|
|
msg.msg = JSON.stringify(errorMsg);
|
|
|
|
} else if (msg.msg instanceof Buffer) {
|
|
|
|
msg.format = "buffer["+msg.msg.length+"]";
|
|
|
|
msg.msg = msg.msg.toString('hex');
|
|
|
|
if (msg.msg.length > debuglength) {
|
|
|
|
msg.msg = msg.msg.substring(0,debuglength);
|
|
|
|
}
|
|
|
|
} else if (msg.msg && msgType === 'object') {
|
|
|
|
try {
|
|
|
|
msg.format = msg.msg.constructor.name || "Object";
|
|
|
|
// Handle special case of msg.req/res objects from HTTP In node
|
|
|
|
if (msg.format === "IncomingMessage" || msg.format === "ServerResponse") {
|
|
|
|
msg.format = "Object";
|
|
|
|
}
|
|
|
|
} catch(err) {
|
|
|
|
msg.format = "Object";
|
|
|
|
}
|
|
|
|
if (/error/i.test(msg.format)) {
|
|
|
|
msg.msg = JSON.stringify({
|
|
|
|
name: msg.msg.name,
|
|
|
|
message: msg.msg.message
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
var isArray = util.isArray(msg.msg);
|
|
|
|
if (isArray) {
|
|
|
|
msg.format = "array["+msg.msg.length+"]";
|
|
|
|
if (msg.msg.length > debuglength) {
|
|
|
|
// msg.msg = msg.msg.slice(0,debuglength);
|
|
|
|
msg.msg = {
|
2018-07-15 00:06:15 +02:00
|
|
|
__enc__: true,
|
2018-06-25 23:31:11 +02:00
|
|
|
type: "array",
|
|
|
|
data: msg.msg.slice(0,debuglength),
|
|
|
|
length: msg.msg.length
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (isArray || (msg.format === "Object")) {
|
|
|
|
msg.msg = safeJSONStringify(msg.msg, function(key, value) {
|
|
|
|
if (key === '_req' || key === '_res') {
|
|
|
|
value = {
|
2018-07-15 00:06:15 +02:00
|
|
|
__enc__: true,
|
2018-06-25 23:31:11 +02:00
|
|
|
type: "internal"
|
|
|
|
}
|
|
|
|
} else if (value instanceof Error) {
|
|
|
|
value = value.toString()
|
|
|
|
} else if (util.isArray(value) && value.length > debuglength) {
|
|
|
|
value = {
|
2018-07-15 00:06:15 +02:00
|
|
|
__enc__: true,
|
2018-06-25 23:31:11 +02:00
|
|
|
type: "array",
|
|
|
|
data: value.slice(0,debuglength),
|
|
|
|
length: value.length
|
|
|
|
}
|
|
|
|
} else if (typeof value === 'string') {
|
|
|
|
if (value.length > debuglength) {
|
|
|
|
value = value.substring(0,debuglength)+"...";
|
|
|
|
}
|
|
|
|
} else if (typeof value === 'function') {
|
|
|
|
value = {
|
2018-07-15 00:06:15 +02:00
|
|
|
__enc__: true,
|
2018-06-25 23:31:11 +02:00
|
|
|
type: "function"
|
|
|
|
}
|
2018-06-29 11:50:07 +02:00
|
|
|
} else if (typeof value === 'number') {
|
|
|
|
if (isNaN(value) || value === Infinity || value === -Infinity) {
|
|
|
|
value = {
|
2018-07-15 00:06:15 +02:00
|
|
|
__enc__: true,
|
2018-06-29 11:50:07 +02:00
|
|
|
type: "number",
|
|
|
|
data: value.toString()
|
|
|
|
}
|
|
|
|
}
|
2018-06-25 23:31:11 +02:00
|
|
|
} else if (value && value.constructor) {
|
|
|
|
if (value.type === "Buffer") {
|
2018-07-15 00:06:15 +02:00
|
|
|
value.__enc__ = true;
|
2018-06-25 23:31:11 +02:00
|
|
|
value.length = value.data.length;
|
|
|
|
if (value.length > debuglength) {
|
|
|
|
value.data = value.data.slice(0,debuglength);
|
|
|
|
}
|
|
|
|
} else if (value.constructor.name === "ServerResponse") {
|
|
|
|
value = "[internal]"
|
|
|
|
} else if (value.constructor.name === "Socket") {
|
|
|
|
value = "[internal]"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}," ");
|
|
|
|
} else {
|
|
|
|
try { msg.msg = msg.msg.toString(); }
|
|
|
|
catch(e) { msg.msg = "[Type not printable]"; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (msgType === "function") {
|
|
|
|
msg.format = "function";
|
|
|
|
msg.msg = "[function]"
|
|
|
|
} else if (msgType === "boolean") {
|
|
|
|
msg.format = "boolean";
|
|
|
|
msg.msg = msg.msg.toString();
|
|
|
|
} else if (msgType === "number") {
|
|
|
|
msg.format = "number";
|
|
|
|
msg.msg = msg.msg.toString();
|
|
|
|
} else if (msg.msg === null || msgType === "undefined") {
|
|
|
|
msg.format = (msg.msg === null)?"null":"undefined";
|
|
|
|
msg.msg = "(undefined)";
|
|
|
|
} else {
|
|
|
|
msg.format = "string["+msg.msg.length+"]";
|
|
|
|
if (msg.msg.length > debuglength) {
|
|
|
|
msg.msg = msg.msg.substring(0,debuglength)+"...";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
2014-08-28 15:25:41 +02:00
|
|
|
module.exports = {
|
2018-06-25 23:31:11 +02:00
|
|
|
encodeObject: encodeObject,
|
2014-08-28 15:25:41 +02:00
|
|
|
ensureString: ensureString,
|
2014-09-10 13:46:56 +02:00
|
|
|
ensureBuffer: ensureBuffer,
|
2014-11-23 23:25:09 +01:00
|
|
|
cloneMessage: cloneMessage,
|
2015-06-02 16:54:37 +02:00
|
|
|
compareObjects: compareObjects,
|
2015-12-29 23:19:32 +01:00
|
|
|
generateId: generateId,
|
|
|
|
getMessageProperty: getMessageProperty,
|
2015-12-31 00:09:35 +01:00
|
|
|
setMessageProperty: setMessageProperty,
|
2018-07-25 10:27:27 +02:00
|
|
|
getObjectProperty: getObjectProperty,
|
|
|
|
setObjectProperty: setObjectProperty,
|
2016-06-08 00:01:23 +02:00
|
|
|
evaluateNodeProperty: evaluateNodeProperty,
|
2017-03-08 15:38:33 +01:00
|
|
|
normalisePropertyExpression: normalisePropertyExpression,
|
2017-05-03 16:48:30 +02:00
|
|
|
normaliseNodeTypeName: normaliseNodeTypeName,
|
2017-05-05 12:23:24 +02:00
|
|
|
prepareJSONataExpression: prepareJSONataExpression,
|
2018-07-11 16:39:34 +02:00
|
|
|
evaluateJSONataExpression: evaluateJSONataExpression,
|
|
|
|
parseContextStore: parseContextStore
|
2014-08-28 15:25:41 +02:00
|
|
|
};
|