1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00
node-red/packages/node_modules/@node-red/util/lib/util.js

997 lines
35 KiB
JavaScript
Raw Normal View History

2014-08-28 15:25:41 +02: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-12-05 14:00:25 +01:00
/**
* @mixin @node-red/util_util
*/
const clonedeep = require("lodash.clonedeep");
2018-08-22 11:00:03 +02:00
const jsonata = require("jsonata");
2020-05-28 08:20:10 +02:00
const moment = require("moment-timezone");
2018-08-22 11:00:03 +02:00
const safeJSONStringify = require("json-stringify-safe");
const util = require("util");
2018-08-22 11:00:03 +02:00
/**
* Generates a psuedo-unique-random id.
* @return {String} a random-ish id
2018-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
function generateId() {
var bytes = [];
for (var i=0;i<8;i++) {
bytes.push(Math.round(0xff*Math.random()).toString(16).padStart(2,'0'));
}
return bytes.join("");
}
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-12-05 14:00:25 +01:00
* @memberof @node-red/util_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-12-05 14:00:25 +01:00
* @memberof @node-red/util_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 Buffer.from(o);
2014-09-10 13:46:56 +02:00
}
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-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
function cloneMessage(msg) {
if (typeof msg !== "undefined" && msg !== null) {
// 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;
2020-04-01 21:22:25 +02:00
var m = clonedeep(msg);
if (req) {
m.req = req;
msg.req = req;
}
if (res) {
m.res = res;
msg.res = res;
}
return m;
}
return msg;
}
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-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
2014-11-23 23:25:09 +01:00
function compareObjects(obj1,obj2) {
var i;
2014-11-23 23:25:09 +01:00
if (obj1 === obj2) {
return true;
}
if (obj1 == null || obj2 == null) {
return false;
}
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) {
if (obj1.length !== obj2.length) {
2014-11-23 23:25:09 +01:00
return false;
}
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;
}
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);
} 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) {
/* 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;
}
function createError(code, message) {
var e = new Error(message);
e.code = code;
return e;
}
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']`
*
* If `msg` is provided, any internal cross-references will be evaluated against that
* object. Otherwise, it will return a nested set of properties
*
* For example, without msg set, 'a[msg.foo]' returns `['a', [ 'msg', 'foo'] ]`
* But if msg is set to '{"foo": "bar"}', 'a[msg.foo]' returns `['a', 'bar' ]`
*
2018-08-22 11:00:03 +02:00
* @param {String} str - the property expression
* @return {Array} the normalised expression
2018-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
function normalisePropertyExpression(str, msg, toString) {
// This must be kept in sync with validatePropertyExpression
// in editor/js/ui/utils.js
var length = str.length;
2017-01-06 22:58:17 +01:00
if (length === 0) {
throw createError("INVALID_EXPR","Invalid property expression: zero-length");
2017-01-06 22:58:17 +01:00
}
var parts = [];
var start = 0;
var inString = false;
var inBox = false;
var boxExpression = false;
var quoteChar;
var v;
for (var i=0;i<length;i++) {
var c = str[i];
if (!inString) {
if (c === "'" || c === '"') {
if (i != start) {
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+c+" at position "+i);
}
inString = true;
quoteChar = c;
start = i+1;
} else if (c === '.') {
if (i===0) {
throw createError("INVALID_EXPR","Invalid property expression: unexpected . at position 0");
}
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 createError("INVALID_EXPR","Invalid property expression: unterminated expression");
}
// Next char is first char of an identifier: a-z 0-9 $ _
if (!/[a-z0-9\$\_]/i.test(str[i+1])) {
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
}
start = i+1;
} else if (c === '[') {
if (i === 0) {
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+c+" at position "+i);
}
if (start != i) {
parts.push(str.substring(start,i));
}
if (i===length-1) {
throw createError("INVALID_EXPR","Invalid property expression: unterminated expression");
}
// Start of a new expression. If it starts with msg it is a nested expression
// Need to scan ahead to find the closing bracket
if (/^msg[.\[]/.test(str.substring(i+1))) {
var depth = 1;
var inLocalString = false;
var localStringQuote;
for (var j=i+1;j<length;j++) {
if (/["']/.test(str[j])) {
if (inLocalString) {
if (str[j] === localStringQuote) {
inLocalString = false
}
} else {
inLocalString = true;
localStringQuote = str[j]
}
}
if (str[j] === '[') {
depth++;
} else if (str[j] === ']') {
depth--;
}
if (depth === 0) {
try {
if (msg) {
var crossRefProp = getMessageProperty(msg, str.substring(i+1,j));
if (crossRefProp === undefined) {
throw createError("INVALID_EXPR","Invalid expression: undefined reference at position "+(i+1)+" : "+str.substring(i+1,j))
}
parts.push(crossRefProp)
} else {
parts.push(normalisePropertyExpression(str.substring(i+1,j), msg));
}
inBox = false;
i = j;
start = j+1;
break;
} catch(err) {
throw createError("INVALID_EXPR","Invalid expression started at position "+(i+1))
}
}
}
if (depth > 0) {
throw createError("INVALID_EXPR","Invalid property expression: unmatched '[' at position "+i);
}
continue;
} else if (!/["'\d]/.test(str[i+1])) {
// Next char is either a quote or a number
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
}
start = i+1;
inBox = true;
} else if (c === ']') {
if (!inBox) {
throw createError("INVALID_EXPR","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 createError("INVALID_EXPR","Invalid property expression: unexpected array expression at position "+start);
}
}
start = i+1;
inBox = false;
} else if (c === ' ') {
throw createError("INVALID_EXPR","Invalid property expression: unexpected ' ' at position "+i);
}
} else {
if (c === quoteChar) {
if (i-start === 0) {
throw createError("INVALID_EXPR","Invalid property expression: zero-length string at position "+start);
}
parts.push(str.substring(start,i));
// If inBox, next char must be a ]. Otherwise it may be [ or .
if (inBox && !/\]/.test(str[i+1])) {
throw createError("INVALID_EXPR","Invalid property expression: unexpected array expression at position "+start);
} else if (!inBox && i+1!==length && !/[\[\.]/.test(str[i+1])) {
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+str[i+1]+" expression at position "+(i+1));
}
start = i+1;
inString = false;
}
}
}
if (inBox || inString) {
throw new createError("INVALID_EXPR","Invalid property expression: unterminated expression");
}
if (start < length) {
parts.push(str.substring(start));
}
if (toString) {
var result = parts.shift();
while(parts.length > 0) {
var p = parts.shift();
if (typeof p === 'string') {
if (/"/.test(p)) {
p = "'"+p+"'";
} else {
p = '"'+p+'"';
}
}
result = result+"["+p+"]";
}
return result;
}
return parts;
}
2018-08-22 11:00:03 +02:00
/**
* Gets a property of a message object.
*
* 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} expr - the property expression
2018-08-22 11:00:03 +02:00
* @return {any} the message property, or undefined if it does not exist
* @throws Will throw an error if the *parent* of the property does not exist
2018-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
function getMessageProperty(msg,expr) {
if (expr.indexOf('msg.')===0) {
expr = expr.substring(4);
}
return getObjectProperty(msg,expr);
}
2018-08-22 11:00:03 +02:00
/**
* Gets a property of an object.
*
* Given the object:
*
* {
* "pet": {
* "type": "cat"
* }
* }
*
* - `pet.type` will return `"cat"`.
* - `pet.name` will return `undefined`
* - `car` will return `undefined`
* - `car.type` will throw an Error (as `car` does not exist)
*
2018-08-22 11:00:03 +02:00
* @param {Object} msg - the object
* @param {String} expr - the property expression
2018-08-22 11:00:03 +02:00
* @return {any} the object property, or undefined if it does not exist
* @throws Will throw an error if the *parent* of the property does not exist
2018-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
function getObjectProperty(msg,expr) {
var result = null;
var msgPropParts = normalisePropertyExpression(expr,msg);
msgPropParts.reduce(function(obj, key) {
result = (typeof obj[key] !== "undefined" ? obj[key] : undefined);
return result;
}, msg);
return result;
}
2018-08-22 11:00:03 +02:00
/**
* Sets a property of a message object.
*
* 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-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
function setMessageProperty(msg,prop,value,createMissing) {
if (prop.indexOf('msg.')===0) {
prop = prop.substring(4);
}
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-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
function setObjectProperty(msg,prop,value,createMissing) {
if (typeof createMissing === 'undefined') {
createMissing = (typeof value !== 'undefined');
}
var msgPropParts = normalisePropertyExpression(prop, msg);
var depth = 0;
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)) {
if (length > 1 && ((typeof obj[key] !== "object" && typeof obj[key] !== "function") || obj[key] === null)) {
// Break out early as we cannot create a property beneath
// this type of value
return false;
}
obj = obj[key];
} else if (createMissing) {
if (typeof msgPropParts[i+1] === 'string') {
obj[key] = {};
} else {
obj[key] = [];
}
obj = obj[key];
} else {
return false;
}
} else if (typeof key === 'number') {
// obj is an array
if (obj[key] === undefined) {
if (createMissing) {
if (typeof msgPropParts[i+1] === 'string') {
obj[key] = {};
} else {
obj[key] = [];
}
obj = obj[key];
} else {
return false;
}
} else {
obj = obj[key];
}
}
}
key = msgPropParts[length-1];
if (typeof value === "undefined") {
if (typeof key === 'number' && Array.isArray(obj)) {
obj.splice(key,1);
} else {
delete obj[key]
}
} else {
if (typeof obj === "object" && obj !== null) {
obj[key] = value;
} else {
// Cannot set a property of a non-object/array
return false;
}
}
return true;
}
2019-02-28 23:21:22 +01:00
/*!
2019-01-26 15:15:20 +01:00
* Get value of environment variable.
* @param {Node} node - accessing node
* @param {String} name - name of variable
* @return {String} value of env var
*/
function getSetting(node, name, flow_) {
var flow = (flow_ ? flow_ : (node ? node._flow : null));
if (flow) {
if (node && node.g) {
const group = flow.getGroupNode(node.g);
2021-08-30 01:00:58 +02:00
const result = flow.getGroupEnvSetting(node, group, name);
if (result) {
return result.val;
}
2019-01-28 14:14:08 +01:00
}
return flow.getSetting(name);
2019-01-26 15:15:20 +01:00
}
return process.env[name];
}
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
2019-01-26 15:15:20 +01:00
* @param {Node} node - the node evaluating the property
2018-08-22 11:00:03 +02:00
* @return {String} The parsed string
2019-02-28 23:21:22 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
2019-01-26 15:15:20 +01:00
function evaluateEnvProperty(value, node) {
var flow = (node && node.hasOwnProperty("_flow")) ? node._flow : null;
var result;
2018-05-21 16:10:06 +02:00
if (/^\${[^}]+}$/.test(value)) {
// ${ENV_VAR}
2019-01-26 15:15:20 +01:00
var name = value.substring(2,value.length-1);
result = getSetting(node, name, flow);
2018-05-21 16:10:06 +02:00
} else if (!/\${\S+}/.test(value)) {
// ENV_VAR
result = getSetting(node, value, flow);
2018-05-21 16:10:06 +02:00
} else {
// FOO${ENV_VAR}BAR
2019-01-26 15:15:20 +01:00
return value.replace(/\${([^}]+)}/g, function(match, name) {
var val = getSetting(node, name, flow);
return (val === undefined)?"":val;
2018-05-21 16:10:06 +02:00
});
}
return (result === undefined)?"":result;
2018-05-21 16:10:06 +02:00
}
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} key - the context property string to parse
2018-08-22 11:00:03 +02:00
* @return {Object} The parsed property
2018-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
function parseContextStore(key) {
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-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
function evaluateNodeProperty(value, type, node, msg, callback) {
var result = value;
2015-12-31 00:09:35 +01:00
if (type === 'str') {
result = ""+value;
2015-12-31 00:09:35 +01:00
} else if (type === 'num') {
result = Number(value);
2015-12-31 00:09:35 +01:00
} else if (type === 'json') {
result = JSON.parse(value);
2015-12-31 00:09:35 +01:00
} else if (type === 're') {
result = new RegExp(value);
} else if (type === 'date') {
result = Date.now();
2017-06-11 22:19:46 +02:00
} else if (type === 'bin') {
var data = JSON.parse(value);
result = Buffer.from(data);
2015-12-31 00:09:35 +01:00
} else if (type === 'msg' && msg) {
try {
result = getMessageProperty(msg,value);
} catch(err) {
if (callback) {
callback(err);
} else {
throw err;
}
return;
}
} else if ((type === 'flow' || type === 'global') && node) {
var contextKey = parseContextStore(value);
if (/\[msg/.test(contextKey.key)) {
// The key has a nest msg. reference to evaluate first
contextKey.key = normalisePropertyExpression(contextKey.key, msg, true)
}
result = node.context()[type].get(contextKey.key,contextKey.store,callback);
if (callback) {
return;
}
} else if (type === 'bool') {
result = /^true$/i.test(value);
} else if (type === 'jsonata') {
var expr = prepareJSONataExpression(value,node);
result = evaluateJSONataExpression(expr,msg);
2018-05-21 16:10:06 +02:00
} else if (type === 'env') {
2019-01-26 15:15:20 +01:00
result = evaluateEnvProperty(value, node);
}
if (callback) {
callback(null,result);
} else {
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-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
function prepareJSONataExpression(value,node) {
var expr = jsonata(value);
expr.assign('flowContext', function(val, store) {
return node.context().flow.get(val, store);
});
expr.assign('globalContext', function(val, store) {
return node.context().global.get(val, store);
});
2019-01-26 15:15:20 +01:00
expr.assign('env', function(name) {
var val = getSetting(node, name, node._flow);
if (typeof val !== 'undefined') {
return val;
} else {
return "";
}
});
expr.assign('moment', function(arg1, arg2, arg3, arg4) {
return moment(arg1, arg2, arg3, arg4);
});
2018-01-13 21:42:23 +01:00
expr.registerFunction('clone', cloneMessage, '<(oa)-:o>');
expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value);
expr._node = node;
return expr;
}
2018-08-22 11:00:03 +02:00
/**
* Evaluates a JSONata expression.
* 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-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
function evaluateJSONataExpression(expr,msg,callback) {
var context = msg;
if (expr._legacyMode) {
context = {msg:msg};
}
var bindings = {};
if (callback) {
// If callback provided, need to override the pre-assigned sync
// context functions to be their async variants
bindings.flowContext = function(val, store) {
return new Promise((resolve,reject) => {
expr._node.context().flow.get(val, store, function(err,value) {
if (err) {
reject(err);
} else {
resolve(value);
}
})
});
}
bindings.globalContext = function(val, store) {
return new Promise((resolve,reject) => {
expr._node.context().global.get(val, store, function(err,value) {
if (err) {
reject(err);
} else {
resolve(value);
}
})
});
}
}
return expr.evaluate(context, bindings, callback);
}
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-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02: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-12-05 14:00:25 +01:00
* @memberof @node-red/util_util
2018-08-22 11:00:03 +02:00
*/
function encodeObject(msg,opts) {
try {
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);
var needsStringify = isArray;
if (isArray) {
msg.format = "array["+msg.msg.length+"]";
if (msg.msg.length > debuglength) {
// msg.msg = msg.msg.slice(0,debuglength);
msg.msg = {
__enc__: true,
type: "array",
data: msg.msg.slice(0,debuglength),
length: msg.msg.length
}
}
} else if (msg.msg && msg.msg.constructor.name === "Set") {
msg.format = "set["+msg.msg.size+"]";
msg.msg = {
__enc__: true,
type: "set",
data: Array.from(msg.msg).slice(0,debuglength),
length: msg.msg.size
}
needsStringify = true;
} else if (msg.msg && msg.msg.constructor.name === "Map") {
msg.format = "map";
msg.msg = {
__enc__: true,
type: "map",
data: Object.fromEntries(Array.from(msg.msg.entries()).slice(0,debuglength)),
length: msg.msg.size
}
needsStringify = true;
} else if (msg.msg && msg.msg.constructor.name === "RegExp") {
msg.format = 'regexp';
msg.msg = msg.msg.toString();
}
if (needsStringify || (msg.format === "Object")) {
msg.msg = safeJSONStringify(msg.msg, function(key, value) {
if (key === '_req' || key === '_res') {
value = {
__enc__: true,
type: "internal"
}
} else if (value instanceof Error) {
value = value.toString()
} else if (util.isArray(value) && value.length > debuglength) {
value = {
__enc__: true,
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 = {
__enc__: true,
type: "function"
}
} else if (typeof value === 'number') {
if (isNaN(value) || value === Infinity || value === -Infinity) {
value = {
__enc__: true,
type: "number",
data: value.toString()
}
}
2020-11-17 21:50:29 +01:00
} else if (typeof value === 'bigint') {
value = {
__enc__: true,
type: 'bigint',
data: value.toString()
}
} else if (value && value.constructor) {
if (value.type === "Buffer") {
value.__enc__ = true;
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]"
} else if (value.constructor.name === "Set") {
value = {
__enc__: true,
type: "set",
data: Array.from(value).slice(0,debuglength),
length: value.size
}
} else if (value.constructor.name === "Map") {
value = {
__enc__: true,
type: "map",
data: Object.fromEntries(Array.from(value.entries()).slice(0,debuglength)),
length: value.size
}
} else if (value.constructor.name === "RegExp") {
value = {
__enc__: true,
type: "regexp",
data: value.toString()
}
}
} else if (value === undefined) {
value = {
__enc__: true,
type: "undefined",
}
}
return value;
});
} else {
try { msg.msg = msg.msg.toString(); }
catch(e) { msg.msg = "[Type not printable]" + util.inspect(msg.msg); }
}
}
} 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();
2020-11-17 21:50:29 +01:00
} else if (msgType === "bigint") {
msg.format = "bigint";
msg.msg = {
__enc__: true,
type: 'bigint',
data: 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;
} catch(e) {
msg.format = "error";
var errorMsg = {};
if (e.name) {
errorMsg.name = e.name;
}
if (e.hasOwnProperty('message')) {
errorMsg.message = 'encodeObject Error: ['+e.message + '] Value: '+util.inspect(msg.msg);
} else {
errorMsg.message = 'encodeObject Error: ['+e.toString() + '] Value: '+util.inspect(msg.msg);
}
if (errorMsg.message.length > debuglength) {
errorMsg.message = errorMsg.message.substring(0,debuglength);
}
msg.msg = JSON.stringify(errorMsg);
return msg;
}
}
2014-08-28 15:25:41 +02:00
module.exports = {
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,
compareObjects: compareObjects,
generateId: generateId,
getMessageProperty: getMessageProperty,
2015-12-31 00:09:35 +01:00
setMessageProperty: setMessageProperty,
getObjectProperty: getObjectProperty,
setObjectProperty: setObjectProperty,
evaluateNodeProperty: evaluateNodeProperty,
normalisePropertyExpression: normalisePropertyExpression,
normaliseNodeTypeName: normaliseNodeTypeName,
prepareJSONataExpression: prepareJSONataExpression,
evaluateJSONataExpression: evaluateJSONataExpression,
parseContextStore: parseContextStore,
getSetting: getSetting
2014-08-28 15:25:41 +02:00
};