mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Resolve merge conflicts on update from upstream
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -27,3 +27,4 @@ docs
 | 
			
		||||
.vscode
 | 
			
		||||
.nyc_output
 | 
			
		||||
sync.ffs_db
 | 
			
		||||
.idea
 | 
			
		||||
@@ -1,5 +0,0 @@
 | 
			
		||||
# Security Policy
 | 
			
		||||
 | 
			
		||||
## Reporting a Vulnerability
 | 
			
		||||
 | 
			
		||||
Please report any potential security issues to `team@nodered.org`. This will notify the core project team who will respond accordingly.
 | 
			
		||||
@@ -1,177 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    const {scheduleTask} = require("cronosjs");
 | 
			
		||||
 | 
			
		||||
    function InjectNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
 | 
			
		||||
        /* Handle legacy */
 | 
			
		||||
        if(!Array.isArray(n.props)){
 | 
			
		||||
            n.props = [];
 | 
			
		||||
            n.props.push({
 | 
			
		||||
                p:'payload',
 | 
			
		||||
                v:n.payload,
 | 
			
		||||
                vt:n.payloadType
 | 
			
		||||
            });
 | 
			
		||||
            n.props.push({
 | 
			
		||||
                p:'topic',
 | 
			
		||||
                v:n.topic,
 | 
			
		||||
                vt:'str'
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            for (var i=0,l=n.props.length; i<l; i++) {
 | 
			
		||||
                if (n.props[i].p === 'payload' && !n.props[i].hasOwnProperty('v')) {
 | 
			
		||||
                    n.props[i].v = n.payload;
 | 
			
		||||
                    n.props[i].vt = n.payloadType;
 | 
			
		||||
                } else if (n.props[i].p === 'topic' && n.props[i].vt === 'str' && !n.props[i].hasOwnProperty('v')) {
 | 
			
		||||
                    n.props[i].v = n.topic;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.props = n.props;
 | 
			
		||||
        this.repeat = n.repeat;
 | 
			
		||||
        this.crontab = n.crontab;
 | 
			
		||||
        this.once = n.once;
 | 
			
		||||
        this.onceDelay = (n.onceDelay || 0.1) * 1000;
 | 
			
		||||
        this.interval_id = null;
 | 
			
		||||
        this.cronjob = null;
 | 
			
		||||
        var node = this;
 | 
			
		||||
 | 
			
		||||
        node.props.forEach(function (prop) {
 | 
			
		||||
            if (prop.vt === "jsonata") {
 | 
			
		||||
                try {
 | 
			
		||||
                    var val = prop.v ? prop.v : "";
 | 
			
		||||
                    prop.exp = RED.util.prepareJSONataExpression(val, node);
 | 
			
		||||
                }
 | 
			
		||||
                catch (err) {
 | 
			
		||||
                    node.error(RED._("inject.errors.invalid-expr", {error:err.message}));
 | 
			
		||||
                    prop.exp = null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (node.repeat > 2147483) {
 | 
			
		||||
            node.error(RED._("inject.errors.toolong", this));
 | 
			
		||||
            delete node.repeat;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        node.repeaterSetup = function () {
 | 
			
		||||
            if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
 | 
			
		||||
                this.repeat = this.repeat * 1000;
 | 
			
		||||
                if (RED.settings.verbose) {
 | 
			
		||||
                    this.log(RED._("inject.repeat", this));
 | 
			
		||||
                }
 | 
			
		||||
                this.interval_id = setInterval(function() {
 | 
			
		||||
                    node.emit("input", {});
 | 
			
		||||
                }, this.repeat);
 | 
			
		||||
            } else if (this.crontab) {
 | 
			
		||||
                if (RED.settings.verbose) {
 | 
			
		||||
                    this.log(RED._("inject.crontab", this));
 | 
			
		||||
                }
 | 
			
		||||
                this.cronjob = scheduleTask(this.crontab,() => { node.emit("input", {})});
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (this.once) {
 | 
			
		||||
            this.onceTimeout = setTimeout( function() {
 | 
			
		||||
                node.emit("input",{});
 | 
			
		||||
                node.repeaterSetup();
 | 
			
		||||
            }, this.onceDelay);
 | 
			
		||||
        } else {
 | 
			
		||||
            node.repeaterSetup();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.on("input", function(msg, send, done) {
 | 
			
		||||
            var errors = [];
 | 
			
		||||
            var props = this.props;
 | 
			
		||||
            if (msg.__user_inject_props__ && Array.isArray(msg.__user_inject_props__)) {
 | 
			
		||||
                props = msg.__user_inject_props__;
 | 
			
		||||
            }
 | 
			
		||||
            delete msg.__user_inject_props__;
 | 
			
		||||
            props.forEach(p => {
 | 
			
		||||
                var property = p.p;
 | 
			
		||||
                var value = p.v ? p.v : '';
 | 
			
		||||
                var valueType = p.vt ? p.vt : 'str';
 | 
			
		||||
 | 
			
		||||
                if (!property) return;
 | 
			
		||||
 | 
			
		||||
                if (valueType === "jsonata") {
 | 
			
		||||
                    if (p.exp) {
 | 
			
		||||
                        try {
 | 
			
		||||
                            var val = RED.util.evaluateJSONataExpression(p.exp, msg);
 | 
			
		||||
                            RED.util.setMessageProperty(msg, property, val, true);
 | 
			
		||||
                        }
 | 
			
		||||
                        catch  (err) {
 | 
			
		||||
                            errors.push(err.message);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                try {
 | 
			
		||||
                    RED.util.setMessageProperty(msg,property,RED.util.evaluateNodeProperty(value, valueType, this, msg),true);
 | 
			
		||||
                } catch (err) {
 | 
			
		||||
                    errors.push(err.toString());
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (errors.length) {
 | 
			
		||||
                done(errors.join('; '));
 | 
			
		||||
            } else {
 | 
			
		||||
                send(msg);
 | 
			
		||||
                done();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType("inject",InjectNode);
 | 
			
		||||
 | 
			
		||||
    InjectNode.prototype.close = function() {
 | 
			
		||||
        if (this.onceTimeout) {
 | 
			
		||||
            clearTimeout(this.onceTimeout);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.interval_id != null) {
 | 
			
		||||
            clearInterval(this.interval_id);
 | 
			
		||||
            if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
 | 
			
		||||
        } else if (this.cronjob != null) {
 | 
			
		||||
            this.cronjob.stop();
 | 
			
		||||
            if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
 | 
			
		||||
            delete this.cronjob;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    RED.httpAdmin.post("/inject/:id", RED.auth.needsPermission("inject.write"), function(req,res) {
 | 
			
		||||
        var node = RED.nodes.getNode(req.params.id);
 | 
			
		||||
        if (node != null) {
 | 
			
		||||
            try {
 | 
			
		||||
                if (req.body && req.body.__user_inject_props__) {
 | 
			
		||||
                    node.receive(req.body);
 | 
			
		||||
                } else {
 | 
			
		||||
                    node.receive();
 | 
			
		||||
                }
 | 
			
		||||
                res.sendStatus(200);
 | 
			
		||||
            } catch(err) {
 | 
			
		||||
                res.sendStatus(500);
 | 
			
		||||
                node.error(RED._("inject.failed",{error:err.toString()}));
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            res.sendStatus(404);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
@@ -1,513 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="debug">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-typed-complete"><i class="fa fa-list"></i> <span data-i18n="debug.output"></span></label>
 | 
			
		||||
        <input id="node-input-typed-complete" type="text" style="width: 70%">
 | 
			
		||||
        <input id="node-input-complete" type="hidden">
 | 
			
		||||
        <input id="node-input-targetType" type="hidden">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-tosidebar"><i class="fa fa-random"></i> <span data-i18n="debug.to"></span></label>
 | 
			
		||||
        <label for="node-input-tosidebar" style="width:70%">
 | 
			
		||||
        <input type="checkbox" id="node-input-tosidebar" style="display:inline-block; width:22px; vertical-align:top;"><span data-i18n="debug.toSidebar"></span>
 | 
			
		||||
        </label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-console"> </label>
 | 
			
		||||
        <label for="node-input-console" style="width:70%">
 | 
			
		||||
        <input type="checkbox" id="node-input-console" style="display:inline-block; width:22px; vertical-align:top;"><span data-i18n="debug.toConsole"></span>
 | 
			
		||||
        </label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
    <label for="node-input-tostatus"> </label>
 | 
			
		||||
    <label for="node-input-tostatus" style="width:70%">
 | 
			
		||||
        <input type="checkbox" id="node-input-tostatus" style="display:inline-block; width:22px; vertical-align:top;"><span data-i18n="debug.toStatus"></span>
 | 
			
		||||
    </label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" id="node-tostatus-line">
 | 
			
		||||
        <label for="node-input-typed-status"></label>
 | 
			
		||||
        <input id="node-input-typed-status" type="text" style="width: 70%">
 | 
			
		||||
        <input id="node-input-statusVal" type="hidden">
 | 
			
		||||
        <input id="node-input-statusType" type="hidden">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script src="debug/view/debug-utils.js"></script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
(function() {
 | 
			
		||||
    var subWindow = null;
 | 
			
		||||
 | 
			
		||||
    function activateAjaxCall(node, active, successCallback) {
 | 
			
		||||
        var url;
 | 
			
		||||
        var body;
 | 
			
		||||
 | 
			
		||||
        if (Array.isArray(node)) {
 | 
			
		||||
            url = "debug/"+(active?"enable":"disable");
 | 
			
		||||
            body = {nodes: node.map(function(n) { return n.id})}
 | 
			
		||||
            node = node[0];
 | 
			
		||||
        } else {
 | 
			
		||||
            url = "debug/"+node.id+"/"+(active?"enable":"disable");
 | 
			
		||||
        }
 | 
			
		||||
        $.ajax({
 | 
			
		||||
            url: url,
 | 
			
		||||
            type: "POST",
 | 
			
		||||
            data: body,
 | 
			
		||||
            success: successCallback,
 | 
			
		||||
            error: function(jqXHR,textStatus,errorThrown) {
 | 
			
		||||
                if (jqXHR.status == 404) {
 | 
			
		||||
                    RED.notify(node._("common.notification.error", {message: node._("common.notification.errors.not-deployed")}),"error");
 | 
			
		||||
                } else if (jqXHR.status === 0) {
 | 
			
		||||
                    RED.notify(node._("common.notification.error", {message: node._("common.notification.errors.no-response")}),"error");
 | 
			
		||||
                } else {
 | 
			
		||||
                    // TODO where is the err.status comming from?
 | 
			
		||||
                    RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.unexpected",{status:err.status,message:err.response})}),"error");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('debug',{
 | 
			
		||||
        category: 'common',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            active: {value:true},
 | 
			
		||||
            tosidebar: {value:true},
 | 
			
		||||
            console: {value:false},
 | 
			
		||||
            tostatus: {value:false},
 | 
			
		||||
            complete: {value:"false", required:true},
 | 
			
		||||
            targetType: {value:undefined},
 | 
			
		||||
            statusVal: {value:""},
 | 
			
		||||
            statusType: {value:"auto"}
 | 
			
		||||
        },
 | 
			
		||||
        label: function() {
 | 
			
		||||
            var suffix = "";
 | 
			
		||||
            if (this.console === true || this.console === "true") { suffix = " ⇲"; }
 | 
			
		||||
            if (this.targetType === "jsonata") {
 | 
			
		||||
                return (this.name || "JSONata") + suffix;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.complete === true || this.complete === "true") {
 | 
			
		||||
                return (this.name||"msg") + suffix;
 | 
			
		||||
            } else {
 | 
			
		||||
                return (this.name || "msg." + ((!this.complete || this.complete === "false") ? "payload" : this.complete)) + suffix;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        color:"#87a980",
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:0,
 | 
			
		||||
        icon: "debug.svg",
 | 
			
		||||
        align: "right",
 | 
			
		||||
        button: {
 | 
			
		||||
            toggle: "active",
 | 
			
		||||
            visible: function() { return this.tosidebar; },
 | 
			
		||||
            onclick: function() {
 | 
			
		||||
                var label = RED.utils.sanitize(this.name||"debug");
 | 
			
		||||
                var node = this;
 | 
			
		||||
                activateAjaxCall(node, node.active, function(resp, textStatus, xhr) {
 | 
			
		||||
                    var historyEvent = {
 | 
			
		||||
                        t:'edit',
 | 
			
		||||
                        node:node,
 | 
			
		||||
                        changes:{
 | 
			
		||||
                            active:!node.active
 | 
			
		||||
                        },
 | 
			
		||||
                        dirty:node.dirty,
 | 
			
		||||
                        changed:node.changed,
 | 
			
		||||
                        callback: function(ev) {
 | 
			
		||||
                            activateAjaxCall(ev.node, ev.node.active);
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                    node.changed = true;
 | 
			
		||||
                    node.dirty = true;
 | 
			
		||||
                    RED.nodes.dirty(true);
 | 
			
		||||
                    RED.history.push(historyEvent);
 | 
			
		||||
                    RED.view.redraw();
 | 
			
		||||
                    if (xhr.status == 200) {
 | 
			
		||||
                        RED.notify(node._("debug.notification.activated",{label:label}),{type: "success", timeout: 2000});
 | 
			
		||||
                    } else if (xhr.status == 201) {
 | 
			
		||||
                        RED.notify(node._("debug.notification.deactivated",{label:label}),{type: "success", timeout: 2000});
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        onpaletteadd: function() {
 | 
			
		||||
            var options = {
 | 
			
		||||
                messageMouseEnter: function(sourceId) {
 | 
			
		||||
                    if (sourceId) {
 | 
			
		||||
                        var n = RED.nodes.node(sourceId);
 | 
			
		||||
                        if (n) {
 | 
			
		||||
                            n.highlighted = true;
 | 
			
		||||
                            n.dirty = true;
 | 
			
		||||
                        }
 | 
			
		||||
                        RED.view.redraw();
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                messageMouseLeave: function(sourceId) {
 | 
			
		||||
                    if (sourceId) {
 | 
			
		||||
                        var n = RED.nodes.node(sourceId);
 | 
			
		||||
                        if (n) {
 | 
			
		||||
                            n.highlighted = false;
 | 
			
		||||
                            n.dirty = true;
 | 
			
		||||
                        }
 | 
			
		||||
                        RED.view.redraw();
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                messageSourceClick: function(sourceId, aliasId, path) {
 | 
			
		||||
                    // Get all of the nodes that could have logged this message
 | 
			
		||||
                    var candidateNodes = [RED.nodes.node(sourceId)]
 | 
			
		||||
                    if (path) {
 | 
			
		||||
                        for (var i=2;i<path.length;i++) {
 | 
			
		||||
                            candidateNodes.push(RED.nodes.node(path[i]))
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (aliasId) {
 | 
			
		||||
                        candidateNodes.push(RED.nodes.node(aliasId));
 | 
			
		||||
                    }
 | 
			
		||||
                    if (candidateNodes.length > 1) {
 | 
			
		||||
                        // The node is in a subflow. Check to see if the active
 | 
			
		||||
                        // workspace is a subflow in the node's parentage. If
 | 
			
		||||
                        // so, reveal the relevant subflow instance node.
 | 
			
		||||
                        var ws = RED.workspaces.active();
 | 
			
		||||
                        for (var i=0;i<candidateNodes.length;i++) {
 | 
			
		||||
                            if (candidateNodes[i].z === ws) {
 | 
			
		||||
                                RED.view.reveal(candidateNodes[i].id);
 | 
			
		||||
                                return
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        // The active workspace is unrelated to the node. So
 | 
			
		||||
                        // fall back to revealing the top most node
 | 
			
		||||
                    }
 | 
			
		||||
                    RED.view.reveal(candidateNodes[0].id);
 | 
			
		||||
                },
 | 
			
		||||
                clear: function() {
 | 
			
		||||
                    RED.nodes.eachNode(function(node) {
 | 
			
		||||
                        node.highlighted = false;
 | 
			
		||||
                        node.dirty = true;
 | 
			
		||||
                    });
 | 
			
		||||
                    RED.view.redraw();
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var uiComponents = RED.debug.init(options);
 | 
			
		||||
 | 
			
		||||
            RED.sidebar.addTab({
 | 
			
		||||
                id: "debug",
 | 
			
		||||
                label: this._("debug.sidebar.label"),
 | 
			
		||||
                name: this._("debug.sidebar.name"),
 | 
			
		||||
                content: uiComponents.content,
 | 
			
		||||
                toolbar: uiComponents.footer,
 | 
			
		||||
                enableOnEdit: true,
 | 
			
		||||
                pinned: true,
 | 
			
		||||
                iconClass: "fa fa-bug",
 | 
			
		||||
                action: "core:show-debug-tab"
 | 
			
		||||
            });
 | 
			
		||||
            RED.actions.add("core:show-debug-tab",function() { RED.sidebar.show('debug'); });
 | 
			
		||||
 | 
			
		||||
            var that = this;
 | 
			
		||||
            RED._debug = function(msg) {
 | 
			
		||||
                that.handleDebugMessage("", {
 | 
			
		||||
                    name:"debug",
 | 
			
		||||
                    msg:msg
 | 
			
		||||
                });
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.refreshMessageList = function() {
 | 
			
		||||
                RED.debug.refreshMessageList(RED.workspaces.active());
 | 
			
		||||
                if (subWindow) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        subWindow.postMessage({event:"workspaceChange",activeWorkspace:RED.workspaces.active()},"*");
 | 
			
		||||
                    } catch(err) {
 | 
			
		||||
                        console.log(err);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            RED.events.on("workspace:change", this.refreshMessageList);
 | 
			
		||||
 | 
			
		||||
            this.handleDebugMessage = function(t,o) {
 | 
			
		||||
                // console.log("->",o.id,o.z,o._alias);
 | 
			
		||||
                //
 | 
			
		||||
                // sourceNode should be the top-level node - one that is on a flow.
 | 
			
		||||
                var sourceNode;
 | 
			
		||||
                var pathParts;
 | 
			
		||||
                if (o.path) {
 | 
			
		||||
                    // Path is a `/`-separated list of ids that identifies the
 | 
			
		||||
                    // complete parentage of the node that generated this message.
 | 
			
		||||
                    //    flow-id/subflow-A-instance/subflow-A-type/subflow-B-instance/subflow-B-type/node-id
 | 
			
		||||
 | 
			
		||||
                    // If it has one id, that is a top level flow
 | 
			
		||||
                    // each subsequent id is the instance id of a subflow node
 | 
			
		||||
                    //
 | 
			
		||||
                    pathParts = o.path.split("/");
 | 
			
		||||
                    if (pathParts.length === 1) {
 | 
			
		||||
                        // The source node is on a flow - so can use its id to find
 | 
			
		||||
                        sourceNode = RED.nodes.node(o.id);
 | 
			
		||||
                    } else if (pathParts.length > 1) {
 | 
			
		||||
                        // Highlight the subflow instance node.
 | 
			
		||||
                        sourceNode = RED.nodes.node(pathParts[1]);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // This is probably redundant...
 | 
			
		||||
                    sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z);
 | 
			
		||||
                }
 | 
			
		||||
                if (sourceNode) {
 | 
			
		||||
                    o._source = {
 | 
			
		||||
                        id:sourceNode.id,
 | 
			
		||||
                        z:sourceNode.z,
 | 
			
		||||
                        name:sourceNode.name,
 | 
			
		||||
                        type:sourceNode.type,
 | 
			
		||||
                        // _alias identifies the actual logging node. This is
 | 
			
		||||
                        // not necessarily the same as sourceNode, which will be
 | 
			
		||||
                        // the top-level subflow instance node.
 | 
			
		||||
                        // This means the node's name is displayed in the sidebar.
 | 
			
		||||
                        _alias:o._alias,
 | 
			
		||||
                        path: pathParts
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
                RED.debug.handleDebugMessage(o);
 | 
			
		||||
                if (subWindow) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        subWindow.postMessage({event:"message",msg:o},"*");
 | 
			
		||||
                    } catch(err) {
 | 
			
		||||
                        console.log(err);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            RED.comms.subscribe("debug",this.handleDebugMessage);
 | 
			
		||||
 | 
			
		||||
            this.clearMessageList = function() {
 | 
			
		||||
                RED.debug.clearMessageList(true);
 | 
			
		||||
                if (subWindow) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        subWindow.postMessage({event:"projectChange"},"*");
 | 
			
		||||
                    } catch(err) {
 | 
			
		||||
                        console.log(err);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            RED.events.on("project:change", this.clearMessageList);
 | 
			
		||||
            RED.actions.add("core:clear-debug-messages", function() { RED.debug.clearMessageList(true) });
 | 
			
		||||
            RED.actions.add("core:clear-filtered-debug-messages", function() { RED.debug.clearMessageList(true, true) });
 | 
			
		||||
 | 
			
		||||
            RED.actions.add("core:activate-selected-debug-nodes", function() { setDebugNodeState(getSelectedDebugNodes(true), true); });
 | 
			
		||||
            RED.actions.add("core:activate-all-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(true, true),true); });
 | 
			
		||||
            RED.actions.add("core:activate-all-flow-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(true, false),true); });
 | 
			
		||||
 | 
			
		||||
            RED.actions.add("core:deactivate-selected-debug-nodes", function() { setDebugNodeState(getSelectedDebugNodes(false), false); });
 | 
			
		||||
            RED.actions.add("core:deactivate-all-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(false, true),false); });
 | 
			
		||||
            RED.actions.add("core:deactivate-all-flow-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(false, false),false); });
 | 
			
		||||
 | 
			
		||||
            function getSelectedDebugNodes(state) {
 | 
			
		||||
                var nodes = [];
 | 
			
		||||
                var selection = RED.view.selection();
 | 
			
		||||
                if (selection.nodes) {
 | 
			
		||||
                    selection.nodes.forEach(function(n) {
 | 
			
		||||
                        if (RED.nodes.subflow(n.z)) {
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                        if (n.type === 'debug' && n.active !== state) {
 | 
			
		||||
                            nodes.push(n);
 | 
			
		||||
                        } else if (n.type === 'group') {
 | 
			
		||||
                            nodes = nodes.concat( RED.group.getNodes(n,true).filter(function(n) {
 | 
			
		||||
                                return n.type === 'debug' && n.active !== state
 | 
			
		||||
                            }));
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                return nodes;
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            function getMatchingDebugNodes(state,globally) {
 | 
			
		||||
                var nodes = [];
 | 
			
		||||
                var filter = {type:"debug"};
 | 
			
		||||
                if (!globally) {
 | 
			
		||||
                    filter.z = RED.workspaces.active();
 | 
			
		||||
                }
 | 
			
		||||
                var candidateNodes = RED.nodes.filterNodes(filter);
 | 
			
		||||
                nodes = candidateNodes.filter(function(n) {
 | 
			
		||||
                    return n.active !== state && !RED.nodes.subflow(n.z)
 | 
			
		||||
                })
 | 
			
		||||
                return nodes;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function setDebugNodeState(nodes,state) {
 | 
			
		||||
                var historyEvents = [];
 | 
			
		||||
                if (nodes.length > 0) {
 | 
			
		||||
                    activateAjaxCall(nodes,false, function(resp, textStatus, xhr) {
 | 
			
		||||
                        nodes.forEach(function(n) {
 | 
			
		||||
                            historyEvents.push({
 | 
			
		||||
                                t: "edit",
 | 
			
		||||
                                node: n,
 | 
			
		||||
                                changed: n.changed,
 | 
			
		||||
                                changes: {
 | 
			
		||||
                                    active: n.active
 | 
			
		||||
                                }
 | 
			
		||||
                            });
 | 
			
		||||
                            n.active = state;
 | 
			
		||||
                            n.changed = true;
 | 
			
		||||
                            n.dirty = true;
 | 
			
		||||
                        })
 | 
			
		||||
                        RED.history.push({
 | 
			
		||||
                            t: "multi",
 | 
			
		||||
                            events: historyEvents,
 | 
			
		||||
                            dirty: RED.nodes.dirty(),
 | 
			
		||||
                            callback: function() {
 | 
			
		||||
                                activateAjaxCall(nodes,nodes[0].active);
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                        RED.nodes.dirty(true);
 | 
			
		||||
                        RED.view.redraw();
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $("#red-ui-sidebar-debug-open").on("click", function(e) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                subWindow = window.open(document.location.toString().replace(/[?#].*$/,"")+"debug/view/view.html"+document.location.search,"nodeREDDebugView","menubar=no,location=no,toolbar=no,chrome,height=500,width=600");
 | 
			
		||||
                subWindow.onload = function() {
 | 
			
		||||
                    subWindow.postMessage({event:"workspaceChange",activeWorkspace:RED.workspaces.active()},"*");
 | 
			
		||||
                };
 | 
			
		||||
            });
 | 
			
		||||
            RED.popover.tooltip($("#red-ui-sidebar-debug-open"),RED._('node-red:debug.sidebar.openWindow'));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            $(window).on('beforeunload',function() {
 | 
			
		||||
                if (subWindow) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        subWindow.close();
 | 
			
		||||
                    } catch(err) {
 | 
			
		||||
                        console.log(err);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            this.handleWindowMessage = function(evt) {
 | 
			
		||||
                var msg = evt.data;
 | 
			
		||||
                if (msg.event === "mouseEnter") {
 | 
			
		||||
                    options.messageMouseEnter(msg.id);
 | 
			
		||||
                } else if (msg.event === "mouseLeave") {
 | 
			
		||||
                    options.messageMouseLeave(msg.id);
 | 
			
		||||
                } else if (msg.event === "mouseClick") {
 | 
			
		||||
                    options.messageSourceClick(msg.id,msg._alias,msg.path);
 | 
			
		||||
                } else if (msg.event === "clear") {
 | 
			
		||||
                    options.clear();
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            window.addEventListener('message',this.handleWindowMessage);
 | 
			
		||||
        },
 | 
			
		||||
        onpaletteremove: function() {
 | 
			
		||||
            RED.comms.unsubscribe("debug",this.handleDebugMessage);
 | 
			
		||||
            RED.sidebar.removeTab("debug");
 | 
			
		||||
            RED.events.off("workspace:change", this.refreshMessageList);
 | 
			
		||||
            window.removeEventListener("message",this.handleWindowMessage);
 | 
			
		||||
            RED.actions.remove("core:show-debug-tab");
 | 
			
		||||
            RED.actions.remove("core:clear-debug-messages");
 | 
			
		||||
            delete RED._debug;
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var autoType = {
 | 
			
		||||
                value: "auto",
 | 
			
		||||
                label: RED._("node-red:debug.autostatus"),
 | 
			
		||||
                hasValue: false
 | 
			
		||||
            };
 | 
			
		||||
            $("#node-input-typed-status").typedInput({
 | 
			
		||||
                default: "auto",
 | 
			
		||||
                types:[autoType, "msg", "jsonata"],
 | 
			
		||||
                typeField: $("#node-input-statusType")
 | 
			
		||||
            });
 | 
			
		||||
            var that = this;
 | 
			
		||||
            var none = {
 | 
			
		||||
                value: "none",
 | 
			
		||||
                label: RED._("node-red:debug.none"),
 | 
			
		||||
                hasValue: false
 | 
			
		||||
            };
 | 
			
		||||
            if (this.tosidebar === undefined) {
 | 
			
		||||
                this.tosidebar = true;
 | 
			
		||||
                $("#node-input-tosidebar").prop('checked', true);
 | 
			
		||||
            }
 | 
			
		||||
            if (this.statusVal === undefined) {
 | 
			
		||||
                this.statusVal = (this.complete === "false") ? "payload" : ((this.complete === "true") ? "payload" : this.complete+"");
 | 
			
		||||
                $("#node-input-typed-status").typedInput('value',this.statusVal || "");
 | 
			
		||||
            }
 | 
			
		||||
            if (this.statusType === undefined) {
 | 
			
		||||
                this.statusType = "auto";
 | 
			
		||||
                $("#node-input-typed-status").typedInput('type',this.statusType || "auto");
 | 
			
		||||
            }
 | 
			
		||||
            if (typeof this.console === "string") {
 | 
			
		||||
                this.console = (this.console == 'true');
 | 
			
		||||
                $("#node-input-console").prop('checked', this.console);
 | 
			
		||||
                $("#node-input-tosidebar").prop('checked', true);
 | 
			
		||||
            }
 | 
			
		||||
            var fullType = {
 | 
			
		||||
                value: "full",
 | 
			
		||||
                label: RED._("node-red:debug.msgobj"),
 | 
			
		||||
                hasValue: false
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            $("#node-input-typed-complete").typedInput({
 | 
			
		||||
                default: "msg",
 | 
			
		||||
                types:['msg', fullType, "jsonata"],
 | 
			
		||||
                typeField: $("#node-input-targetType")
 | 
			
		||||
            });
 | 
			
		||||
            if (this.targetType === "jsonata") {
 | 
			
		||||
                var property = this.complete || "";
 | 
			
		||||
                $("#node-input-typed-complete").typedInput('type','jsonata');
 | 
			
		||||
                $("#node-input-typed-complete").typedInput('value',property);
 | 
			
		||||
            } else if ((this.targetType === "full") || this.complete === "true" || this.complete === true) {
 | 
			
		||||
                // show complete message object
 | 
			
		||||
                $("#node-input-typed-complete").typedInput('type','full');
 | 
			
		||||
            } else {
 | 
			
		||||
                var property = (!this.complete||(this.complete === "false")) ? "payload" : this.complete+"";
 | 
			
		||||
                $("#node-input-typed-complete").typedInput('type','msg');
 | 
			
		||||
                $("#node-input-typed-complete").typedInput('value',property);
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-typed-complete").on('change',function() {
 | 
			
		||||
                if ($("#node-input-typed-complete").typedInput('type') === 'msg' &&
 | 
			
		||||
                    $("#node-input-typed-complete").typedInput('value') === ''
 | 
			
		||||
                ) {
 | 
			
		||||
                    $("#node-input-typed-complete").typedInput('value','payload');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $("#node-input-tostatus").on('change',function() {
 | 
			
		||||
                if ($(this).is(":checked")) {
 | 
			
		||||
                    if (!that.hasOwnProperty("statusVal") || that.statusVal === "") {
 | 
			
		||||
                        var type = $("#node-input-typed-complete").typedInput('type');
 | 
			
		||||
                        var comp = "payload";
 | 
			
		||||
                        if (type !== 'full') {
 | 
			
		||||
                            comp = $("#node-input-typed-complete").typedInput('value');
 | 
			
		||||
                        }
 | 
			
		||||
                        that.statusType = "auto";
 | 
			
		||||
                        that.statusVal = comp;
 | 
			
		||||
                    }
 | 
			
		||||
                    $("#node-input-typed-status").typedInput('type',that.statusType);
 | 
			
		||||
                    $("#node-input-typed-status").typedInput('value',that.statusVal);
 | 
			
		||||
                    $("#node-tostatus-line").show();
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    $("#node-tostatus-line").hide();
 | 
			
		||||
                    that.statusType = "auto";
 | 
			
		||||
                    that.statusVal = "";
 | 
			
		||||
                    $("#node-input-typed-status").typedInput('type',that.statusType);
 | 
			
		||||
                    $("#node-input-typed-status").typedInput('value',that.statusVal);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            var type = $("#node-input-typed-complete").typedInput('type');
 | 
			
		||||
            if (type === 'full') {
 | 
			
		||||
                $("#node-input-complete").val("true");
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-input-complete").val($("#node-input-typed-complete").typedInput('value'));
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-statusVal").val($("#node-input-typed-status").typedInput('value'));
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
})();
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,297 +0,0 @@
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    var util = require("util");
 | 
			
		||||
    var events = require("events");
 | 
			
		||||
    const fs = require("fs-extra");
 | 
			
		||||
    const path = require("path");
 | 
			
		||||
    var debuglength = RED.settings.debugMaxLength || 1000;
 | 
			
		||||
    var useColors = RED.settings.debugUseColors || false;
 | 
			
		||||
    util.inspect.styles.boolean = "red";
 | 
			
		||||
 | 
			
		||||
    function DebugNode(n) {
 | 
			
		||||
        var hasEditExpression = (n.targetType === "jsonata");
 | 
			
		||||
        var editExpression = hasEditExpression ? n.complete : null;
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.name = n.name;
 | 
			
		||||
        this.complete = hasEditExpression ? null : (n.complete||"payload").toString();
 | 
			
		||||
        if (this.complete === "false") { this.complete = "payload"; }
 | 
			
		||||
        this.console = ""+(n.console || false);
 | 
			
		||||
        this.tostatus = n.tostatus || false;
 | 
			
		||||
        this.statusType = n.statusType || "auto";
 | 
			
		||||
        this.statusVal = n.statusVal || this.complete;
 | 
			
		||||
        this.tosidebar = n.tosidebar;
 | 
			
		||||
        if (this.tosidebar === undefined) { this.tosidebar = true; }
 | 
			
		||||
        this.active = (n.active === null || typeof n.active === "undefined") || n.active;
 | 
			
		||||
        if (this.tostatus) {
 | 
			
		||||
            this.status({fill:"grey", shape:"ring"});
 | 
			
		||||
            this.oldState = "{}";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var hasStatExpression = (n.statusType === "jsonata");
 | 
			
		||||
        var statExpression = hasStatExpression ? n.statusVal : null;
 | 
			
		||||
 | 
			
		||||
        var node = this;
 | 
			
		||||
        var preparedEditExpression = null;
 | 
			
		||||
        var preparedStatExpression = null;
 | 
			
		||||
        if (editExpression) {
 | 
			
		||||
            try {
 | 
			
		||||
                preparedEditExpression = RED.util.prepareJSONataExpression(editExpression, this);
 | 
			
		||||
            }
 | 
			
		||||
            catch (e) {
 | 
			
		||||
                node.error(RED._("debug.invalid-exp", {error: editExpression}));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (statExpression) {
 | 
			
		||||
            try {
 | 
			
		||||
                preparedStatExpression = RED.util.prepareJSONataExpression(statExpression, this);
 | 
			
		||||
            }
 | 
			
		||||
            catch (e) {
 | 
			
		||||
                node.error(RED._("debug.invalid-exp", {error: editExpression}));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function prepareValue(msg, done) {
 | 
			
		||||
            // Either apply the jsonata expression or...
 | 
			
		||||
            if (preparedEditExpression) {
 | 
			
		||||
                RED.util.evaluateJSONataExpression(preparedEditExpression, msg, (err, value) => {
 | 
			
		||||
                    if (err) { done(RED._("debug.invalid-exp", {error: editExpression})); }
 | 
			
		||||
                    else { done(null,{id:node.id, z:node.z, _alias: node._alias, path:node._flow.path, name:node.name, topic:msg.topic, msg:value}); }
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                // Extract the required message property
 | 
			
		||||
                var property = "payload";
 | 
			
		||||
                var output = msg[property];
 | 
			
		||||
                if (node.complete !== "false" && typeof node.complete !== "undefined") {
 | 
			
		||||
                    property = node.complete;
 | 
			
		||||
                    try { output = RED.util.getMessageProperty(msg,node.complete); }
 | 
			
		||||
                    catch(err) { output = undefined; }
 | 
			
		||||
                }
 | 
			
		||||
                done(null,{id:node.id, z:node.z, _alias: node._alias,  path:node._flow.path, name:node.name, topic:msg.topic, property:property, msg:output});
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function prepareStatus(msg, done) {
 | 
			
		||||
            if (node.statusType === "auto") {
 | 
			
		||||
                if (node.complete === "true") {
 | 
			
		||||
                    done(null,{msg:msg.payload});
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    prepareValue(msg,function(err,debugMsg) {
 | 
			
		||||
                        if (err) { node.error(err); return; }
 | 
			
		||||
                        done(null,{msg:debugMsg.msg});
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                // Either apply the jsonata expression or...
 | 
			
		||||
                if (preparedStatExpression) {
 | 
			
		||||
                    RED.util.evaluateJSONataExpression(preparedStatExpression, msg, (err, value) => {
 | 
			
		||||
                        if (err) { done(RED._("debug.invalid-exp", {error:editExpression})); }
 | 
			
		||||
                        else { done(null,{msg:value}); }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    // Extract the required message property
 | 
			
		||||
                    var output;
 | 
			
		||||
                    try { output = RED.util.getMessageProperty(msg,node.statusVal); }
 | 
			
		||||
                    catch(err) { output = undefined; }
 | 
			
		||||
                    done(null,{msg:output});
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        this.on("close", function() {
 | 
			
		||||
            if (this.oldState) {
 | 
			
		||||
                this.status({});
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        this.on("input", function(msg, send, done) {
 | 
			
		||||
            if (msg.hasOwnProperty("status") && msg.status.hasOwnProperty("source") && msg.status.source.hasOwnProperty("id") && (msg.status.source.id === node.id)) {
 | 
			
		||||
                done();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (node.tostatus === true) {
 | 
			
		||||
                prepareStatus(msg, function(err,debugMsg) {
 | 
			
		||||
                    if (err) { node.error(err); return; }
 | 
			
		||||
                    var output = debugMsg.msg;
 | 
			
		||||
                    var st = (typeof output === 'string') ? output : util.inspect(output);
 | 
			
		||||
                    var fill = "grey";
 | 
			
		||||
                    var shape = "dot";
 | 
			
		||||
                    if (typeof output === 'object' && output.hasOwnProperty("fill") && output.hasOwnProperty("shape") && output.hasOwnProperty("text")) {
 | 
			
		||||
                        fill = output.fill;
 | 
			
		||||
                        shape = output.shape;
 | 
			
		||||
                        st = output.text;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (node.statusType === "auto") {
 | 
			
		||||
                        if (msg.hasOwnProperty("error")) {
 | 
			
		||||
                            fill = "red";
 | 
			
		||||
                            st = msg.error.message;
 | 
			
		||||
                        }
 | 
			
		||||
                        if (msg.hasOwnProperty("status")) {
 | 
			
		||||
                            fill = msg.status.fill || "grey";
 | 
			
		||||
                            shape = msg.status.shape || "ring";
 | 
			
		||||
                            st = msg.status.text || "";
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (st.length > 32) { st = st.substr(0,32) + "..."; }
 | 
			
		||||
                    var newStatus = {fill:fill, shape:shape, text:st};
 | 
			
		||||
                    if (JSON.stringify(newStatus) !== node.oldState) { // only send if we have to
 | 
			
		||||
                        node.status(newStatus);
 | 
			
		||||
                        node.oldState = JSON.stringify(newStatus);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.complete === "true") {
 | 
			
		||||
                // debug complete msg object
 | 
			
		||||
                if (this.console === "true") {
 | 
			
		||||
                    node.log("\n"+util.inspect(msg, {colors:useColors, depth:10}));
 | 
			
		||||
                }
 | 
			
		||||
                if (this.active && this.tosidebar) {
 | 
			
		||||
                    sendDebug({id:node.id, z:node.z, _alias: node._alias,  path:node._flow.path, name:node.name, topic:msg.topic, msg:msg});
 | 
			
		||||
                }
 | 
			
		||||
                done();
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                prepareValue(msg,function(err,debugMsg) {
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        node.error(err);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    var output = debugMsg.msg;
 | 
			
		||||
                    if (node.console === "true") {
 | 
			
		||||
                        if (typeof output === "string") {
 | 
			
		||||
                            node.log((output.indexOf("\n") !== -1 ? "\n" : "") + output);
 | 
			
		||||
                        } else if (typeof output === "object") {
 | 
			
		||||
                            node.log("\n"+util.inspect(output, {colors:useColors, depth:10}));
 | 
			
		||||
                        } else {
 | 
			
		||||
                            node.log(util.inspect(output, {colors:useColors}));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (node.active) {
 | 
			
		||||
                        if (node.tosidebar == true) {
 | 
			
		||||
                            sendDebug(debugMsg);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    done();
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType("debug",DebugNode, {
 | 
			
		||||
        settings: {
 | 
			
		||||
            debugUseColors: {
 | 
			
		||||
                value: false,
 | 
			
		||||
            },
 | 
			
		||||
            debugMaxLength: {
 | 
			
		||||
                value: 1000,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function sendDebug(msg) {
 | 
			
		||||
        // don't put blank errors in sidebar (but do add to logs)
 | 
			
		||||
        //if ((msg.msg === "") && (msg.hasOwnProperty("level")) && (msg.level === 20)) { return; }
 | 
			
		||||
        msg = RED.util.encodeObject(msg,{maxLength:debuglength});
 | 
			
		||||
        RED.comms.publish("debug",msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    DebugNode.logHandler = new events.EventEmitter();
 | 
			
		||||
    DebugNode.logHandler.on("log",function(msg) {
 | 
			
		||||
        if (msg.level === RED.log.WARN || msg.level === RED.log.ERROR) {
 | 
			
		||||
            sendDebug(msg);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    RED.log.addHandler(DebugNode.logHandler);
 | 
			
		||||
 | 
			
		||||
    function setNodeState(node,state) {
 | 
			
		||||
        if (state) {
 | 
			
		||||
            node.active = true;
 | 
			
		||||
        } else {
 | 
			
		||||
            node.active = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.httpAdmin.post("/debug/:state", RED.auth.needsPermission("debug.write"), function(req,res) {
 | 
			
		||||
        var state = req.params.state;
 | 
			
		||||
        if (state !== 'enable' && state !== 'disable') {
 | 
			
		||||
            res.sendStatus(404);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        var nodes = req.body && req.body.nodes;
 | 
			
		||||
        if (Array.isArray(nodes)) {
 | 
			
		||||
            nodes.forEach(function(id) {
 | 
			
		||||
                var node = RED.nodes.getNode(id);
 | 
			
		||||
                if (node !== null && typeof node !== "undefined" ) {
 | 
			
		||||
                    setNodeState(node, state === "enable");
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            res.sendStatus(state === "enable" ? 200 : 201);
 | 
			
		||||
        } else {
 | 
			
		||||
            res.sendStatus(400);
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    RED.httpAdmin.post("/debug/:id/:state", RED.auth.needsPermission("debug.write"), function(req,res) {
 | 
			
		||||
        var state = req.params.state;
 | 
			
		||||
        if (state !== 'enable' && state !== 'disable') {
 | 
			
		||||
            res.sendStatus(404);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        var node = RED.nodes.getNode(req.params.id);
 | 
			
		||||
        if (node !== null && typeof node !== "undefined" ) {
 | 
			
		||||
            setNodeState(node,state === "enable");
 | 
			
		||||
            res.sendStatus(state === "enable" ? 200 : 201);
 | 
			
		||||
        } else {
 | 
			
		||||
            res.sendStatus(404);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let cachedDebugView;
 | 
			
		||||
    RED.httpAdmin.get("/debug/view/view.html", function(req,res) {
 | 
			
		||||
        if (!cachedDebugView) {
 | 
			
		||||
            fs.readFile(path.join(__dirname,"lib","debug","view.html")).then(data => {
 | 
			
		||||
                let customStyles = "";
 | 
			
		||||
                try {
 | 
			
		||||
                    let customStyleList = RED.settings.editorTheme.page._.css || [];
 | 
			
		||||
                    customStyleList.forEach(style => {
 | 
			
		||||
                        customStyles += `<link rel="stylesheet" href="../../${style}">\n`
 | 
			
		||||
                    })
 | 
			
		||||
                } catch(err) {}
 | 
			
		||||
                cachedDebugView = data.toString().replace("<!-- INSERT-THEME-CSS -->",customStyles)
 | 
			
		||||
                res.set('Content-Type', 'text/html');
 | 
			
		||||
                res.send(cachedDebugView).end();
 | 
			
		||||
            }).catch(err => {
 | 
			
		||||
                res.sendStatus(404);
 | 
			
		||||
            })
 | 
			
		||||
        } else {
 | 
			
		||||
            res.send(cachedDebugView).end();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // As debug/view/debug-utils.js is loaded via <script> tag, it won't get
 | 
			
		||||
    // the auth header attached. So do not use RED.auth.needsPermission here.
 | 
			
		||||
    RED.httpAdmin.get("/debug/view/*",function(req,res) {
 | 
			
		||||
        var options = {
 | 
			
		||||
            root: path.join(__dirname,"lib","debug"),
 | 
			
		||||
            dotfiles: 'deny'
 | 
			
		||||
        };
 | 
			
		||||
        try {
 | 
			
		||||
            res.sendFile(
 | 
			
		||||
                req.params[0],
 | 
			
		||||
                options,
 | 
			
		||||
                 err => {
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        res.sendStatus(404);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        } catch(err) {
 | 
			
		||||
            res.sendStatus(404);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
@@ -1,154 +0,0 @@
 | 
			
		||||
<script type="text/html" data-template-name="complete">
 | 
			
		||||
    <div class="form-row node-input-target-row">
 | 
			
		||||
        <button id="node-input-complete-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-input-target-row node-input-target-list-row" style="position: relative; min-height: 100px">
 | 
			
		||||
        <div style="position: absolute; top: -30px; right: 0;"><input type="text" id="node-input-complete-target-filter"></div>
 | 
			
		||||
        <div id="node-input-complete-target-container-div"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('complete',{
 | 
			
		||||
        category: 'common',
 | 
			
		||||
        color:"#c0edc0",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            scope: {value:[], type:"*[]"},
 | 
			
		||||
            uncaught: {value:false}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:0,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "alert.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            if (this.name) {
 | 
			
		||||
                return this.name;
 | 
			
		||||
            }
 | 
			
		||||
            return this._("complete.completeNodes",{number:this.scope.length});
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var node = this;
 | 
			
		||||
            var scope = node.scope || [];
 | 
			
		||||
 | 
			
		||||
            this._resize = function() {
 | 
			
		||||
                var rows = $("#dialog-form>div:not(.node-input-target-list-row)");
 | 
			
		||||
                var height = $("#dialog-form").height();
 | 
			
		||||
                for (var i=0;i<rows.length;i++) {
 | 
			
		||||
                    height -= $(rows[i]).outerHeight(true);
 | 
			
		||||
                }
 | 
			
		||||
                var editorRow = $("#dialog-form>div.node-input-target-list-row");
 | 
			
		||||
                editorRow.css("height",height+"px");
 | 
			
		||||
            };
 | 
			
		||||
            var search = $("#node-input-complete-target-filter").searchBox({
 | 
			
		||||
                style: "compact",
 | 
			
		||||
                delay: 300,
 | 
			
		||||
                change: function() {
 | 
			
		||||
                    var val = $(this).val().trim().toLowerCase();
 | 
			
		||||
                    if (val === "") {
 | 
			
		||||
                        dirList.treeList("filter", null);
 | 
			
		||||
                        search.searchBox("count","");
 | 
			
		||||
                    } else {
 | 
			
		||||
                        var count = dirList.treeList("filter", function(item) {
 | 
			
		||||
                            return item.label.toLowerCase().indexOf(val) > -1 || item.node.type.toLowerCase().indexOf(val) > -1
 | 
			
		||||
                        });
 | 
			
		||||
                        search.searchBox("count",count+" / "+candidateNodes.length);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var dirList = $("#node-input-complete-target-container-div").css({width: "100%", height: "100%"})
 | 
			
		||||
                .treeList({multi:true}).on("treelistitemmouseover", function(e, item) {
 | 
			
		||||
                    item.node.highlighted = true;
 | 
			
		||||
                    item.node.dirty = true;
 | 
			
		||||
                    RED.view.redraw();
 | 
			
		||||
                }).on("treelistitemmouseout", function(e, item) {
 | 
			
		||||
                    item.node.highlighted = false;
 | 
			
		||||
                    item.node.dirty = true;
 | 
			
		||||
                    RED.view.redraw();
 | 
			
		||||
                })
 | 
			
		||||
            var candidateNodes = RED.nodes.filterNodes({z:node.z});
 | 
			
		||||
            var allChecked = true;
 | 
			
		||||
            var items = [];
 | 
			
		||||
            var nodeItemMap = {};
 | 
			
		||||
 | 
			
		||||
            candidateNodes.forEach(function(n) {
 | 
			
		||||
                if (n.id === node.id) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var isChecked = scope.indexOf(n.id) !== -1;
 | 
			
		||||
 | 
			
		||||
                allChecked = allChecked && isChecked;
 | 
			
		||||
 | 
			
		||||
                var nodeDef = RED.nodes.getType(n.type);
 | 
			
		||||
                var label;
 | 
			
		||||
                var sublabel;
 | 
			
		||||
                if (nodeDef) {
 | 
			
		||||
                    var l = nodeDef.label;
 | 
			
		||||
                    label = (typeof l === "function" ? l.call(n) : l)||"";
 | 
			
		||||
                    sublabel = n.type;
 | 
			
		||||
                    if (sublabel.indexOf("subflow:") === 0) {
 | 
			
		||||
                        var subflowId = sublabel.substring(8);
 | 
			
		||||
                        var subflow = RED.nodes.subflow(subflowId);
 | 
			
		||||
                        sublabel = "subflow : "+subflow.name;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (!nodeDef || !label) {
 | 
			
		||||
                    label = n.type;
 | 
			
		||||
                }
 | 
			
		||||
                nodeItemMap[n.id] = {
 | 
			
		||||
                    node: n,
 | 
			
		||||
                    label: label,
 | 
			
		||||
                    sublabel: sublabel,
 | 
			
		||||
                    selected: isChecked,
 | 
			
		||||
                    checkbox: true
 | 
			
		||||
                };
 | 
			
		||||
                items.push(nodeItemMap[n.id]);
 | 
			
		||||
            });
 | 
			
		||||
            dirList.treeList('data',items);
 | 
			
		||||
 | 
			
		||||
            $("#node-input-complete-target-select").on("click", function(e) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                var preselected = dirList.treeList('selected').map(function(n) {return n.node.id});
 | 
			
		||||
                RED.tray.hide();
 | 
			
		||||
                RED.view.selectNodes({
 | 
			
		||||
                    selected: preselected,
 | 
			
		||||
                    onselect: function(selection) {
 | 
			
		||||
                        RED.tray.show();
 | 
			
		||||
                        var newlySelected = {};
 | 
			
		||||
                        selection.forEach(function(n) {
 | 
			
		||||
                            newlySelected[n.id] = true;
 | 
			
		||||
                            if (nodeItemMap[n.id]) {
 | 
			
		||||
                                nodeItemMap[n.id].treeList.select(true);
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                        preselected.forEach(function(id) {
 | 
			
		||||
                            if (!newlySelected[id]) {
 | 
			
		||||
                                nodeItemMap[id].treeList.select(false);
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                    },
 | 
			
		||||
                    oncancel: function() {
 | 
			
		||||
                        RED.tray.show();
 | 
			
		||||
                    },
 | 
			
		||||
                    filter: function(n) {
 | 
			
		||||
                        return n.id !== node.id;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            this.scope = $("#node-input-complete-target-container-div").treeList('selected').map(function(i) { return i.node.id})
 | 
			
		||||
        },
 | 
			
		||||
        oneditresize: function(size) {
 | 
			
		||||
            this._resize();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,31 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    function CompleteNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        var node = this;
 | 
			
		||||
        this.scope = n.scope;
 | 
			
		||||
        this.on("input",function(msg, send, done) {
 | 
			
		||||
            send(msg);
 | 
			
		||||
            done();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType("complete",CompleteNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,191 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="catch">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label style="width: auto" for="node-input-scope" data-i18n="catch.label.source"></label>
 | 
			
		||||
        <select id="node-input-scope-select">
 | 
			
		||||
            <option value="all" data-i18n="catch.scope.all"></option>
 | 
			
		||||
            <option value="target" data-i18n="catch.scope.selected"></options>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-input-uncaught-row">
 | 
			
		||||
        <input type="checkbox" id="node-input-uncaught" style="display: inline-block; width: auto; vertical-align: top; margin-left: 30px; margin-right: 5px;">
 | 
			
		||||
        <label for="node-input-uncaught" style="width: auto" data-i18n="catch.label.uncaught"></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-input-target-row">
 | 
			
		||||
        <button id="node-input-catch-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-input-target-row node-input-target-list-row" style="position: relative; min-height: 100px">
 | 
			
		||||
        <div style="position: absolute; top: -30px; right: 0;"><input type="text" id="node-input-catch-target-filter"></div>
 | 
			
		||||
        <div id="node-input-catch-target-container-div"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('catch',{
 | 
			
		||||
        category: 'common',
 | 
			
		||||
        color:"#e49191",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            scope: {value:null, type:"*[]"},
 | 
			
		||||
            uncaught: {value:false}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:0,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "alert.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            if (this.name) {
 | 
			
		||||
                return this.name;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.scope) {
 | 
			
		||||
                return this._("catch.catchNodes",{number:this.scope.length});
 | 
			
		||||
            }
 | 
			
		||||
            return this.uncaught?this._("catch.catchUncaught"):this._("catch.catch")
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var node = this;
 | 
			
		||||
            var scope = node.scope || [];
 | 
			
		||||
 | 
			
		||||
            this._resize = function() {
 | 
			
		||||
                var rows = $("#dialog-form>div:not(.node-input-target-list-row)");
 | 
			
		||||
                var height = $("#dialog-form").height();
 | 
			
		||||
                for (var i=0;i<rows.length;i++) {
 | 
			
		||||
                    height -= $(rows[i]).outerHeight(true);
 | 
			
		||||
                }
 | 
			
		||||
                var editorRow = $("#dialog-form>div.node-input-target-list-row");
 | 
			
		||||
                editorRow.css("height",height+"px");
 | 
			
		||||
            };
 | 
			
		||||
            var search = $("#node-input-catch-target-filter").searchBox({
 | 
			
		||||
                style: "compact",
 | 
			
		||||
                delay: 300,
 | 
			
		||||
                change: function() {
 | 
			
		||||
                    var val = $(this).val().trim().toLowerCase();
 | 
			
		||||
                    if (val === "") {
 | 
			
		||||
                        dirList.treeList("filter", null);
 | 
			
		||||
                        search.searchBox("count","");
 | 
			
		||||
                    } else {
 | 
			
		||||
                        var count = dirList.treeList("filter", function(item) {
 | 
			
		||||
                            return item.label.toLowerCase().indexOf(val) > -1 || item.node.type.toLowerCase().indexOf(val) > -1
 | 
			
		||||
                        });
 | 
			
		||||
                        search.searchBox("count",count+" / "+candidateNodes.length);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            var dirList = $("#node-input-catch-target-container-div").css({width: "100%", height: "100%"})
 | 
			
		||||
                .treeList({multi:true}).on("treelistitemmouseover", function(e, item) {
 | 
			
		||||
                    item.node.highlighted = true;
 | 
			
		||||
                    item.node.dirty = true;
 | 
			
		||||
                    RED.view.redraw();
 | 
			
		||||
                }).on("treelistitemmouseout", function(e, item) {
 | 
			
		||||
                    item.node.highlighted = false;
 | 
			
		||||
                    item.node.dirty = true;
 | 
			
		||||
                    RED.view.redraw();
 | 
			
		||||
                })
 | 
			
		||||
            var candidateNodes = RED.nodes.filterNodes({z:node.z});
 | 
			
		||||
            var allChecked = true;
 | 
			
		||||
            var items = [];
 | 
			
		||||
            var nodeItemMap = {};
 | 
			
		||||
 | 
			
		||||
            candidateNodes.forEach(function(n) {
 | 
			
		||||
                if (n.id === node.id) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var isChecked = scope.indexOf(n.id) !== -1;
 | 
			
		||||
 | 
			
		||||
                allChecked = allChecked && isChecked;
 | 
			
		||||
 | 
			
		||||
                var nodeDef = RED.nodes.getType(n.type);
 | 
			
		||||
                var label;
 | 
			
		||||
                var sublabel;
 | 
			
		||||
                if (nodeDef) {
 | 
			
		||||
                    var l = nodeDef.label;
 | 
			
		||||
                    label = (typeof l === "function" ? l.call(n) : l)||"";
 | 
			
		||||
                    sublabel = n.type;
 | 
			
		||||
                    if (sublabel.indexOf("subflow:") === 0) {
 | 
			
		||||
                        var subflowId = sublabel.substring(8);
 | 
			
		||||
                        var subflow = RED.nodes.subflow(subflowId);
 | 
			
		||||
                        sublabel = "subflow : "+subflow.name;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (!nodeDef || !label) {
 | 
			
		||||
                    label = n.type;
 | 
			
		||||
                }
 | 
			
		||||
                nodeItemMap[n.id] = {
 | 
			
		||||
                    node: n,
 | 
			
		||||
                    label: label,
 | 
			
		||||
                    sublabel: sublabel,
 | 
			
		||||
                    selected: isChecked,
 | 
			
		||||
                    checkbox: true
 | 
			
		||||
                };
 | 
			
		||||
                items.push(nodeItemMap[n.id]);
 | 
			
		||||
            });
 | 
			
		||||
            dirList.treeList('data',items);
 | 
			
		||||
 | 
			
		||||
            $("#node-input-catch-target-select").on("click", function(e) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                var preselected = dirList.treeList('selected').map(function(n) {return n.node.id});
 | 
			
		||||
                RED.tray.hide();
 | 
			
		||||
                RED.view.selectNodes({
 | 
			
		||||
                    selected: preselected,
 | 
			
		||||
                    onselect: function(selection) {
 | 
			
		||||
                        RED.tray.show();
 | 
			
		||||
                        var newlySelected = {};
 | 
			
		||||
                        selection.forEach(function(n) {
 | 
			
		||||
                            newlySelected[n.id] = true;
 | 
			
		||||
                            if (nodeItemMap[n.id]) {
 | 
			
		||||
                                nodeItemMap[n.id].treeList.select(true);
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                        preselected.forEach(function(id) {
 | 
			
		||||
                            if (!newlySelected[id]) {
 | 
			
		||||
                                nodeItemMap[id].treeList.select(false);
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                    },
 | 
			
		||||
                    oncancel: function() {
 | 
			
		||||
                        RED.tray.show();
 | 
			
		||||
                    },
 | 
			
		||||
                    filter: function(n) {
 | 
			
		||||
                        return n.id !== node.id;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            $("#node-input-scope-select").on("change", function(e) {
 | 
			
		||||
                var scope = $(this).val();
 | 
			
		||||
                if (scope === "target") {
 | 
			
		||||
                    $(".node-input-target-row").show();
 | 
			
		||||
                    $(".node-input-uncaught-row").hide();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $(".node-input-target-row").hide();
 | 
			
		||||
                    $(".node-input-uncaught-row").show();
 | 
			
		||||
                }
 | 
			
		||||
                node._resize();
 | 
			
		||||
            });
 | 
			
		||||
            if (this.scope === null) {
 | 
			
		||||
                $("#node-input-scope-select").val("all");
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-input-scope-select").val("target");
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-scope-select").trigger("change");
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            var scope = $("#node-input-scope-select").val();
 | 
			
		||||
            if (scope === 'all') {
 | 
			
		||||
                this.scope = null;
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-input-uncaught").prop("checked",false);
 | 
			
		||||
                this.scope = $("#node-input-catch-target-container-div").treeList('selected').map(function(i) { return i.node.id})
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        oneditresize: function(size) {
 | 
			
		||||
            this._resize();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    function CatchNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        var node = this;
 | 
			
		||||
        this.scope = n.scope;
 | 
			
		||||
        this.uncaught = n.uncaught;
 | 
			
		||||
        this.on("input",function(msg, send, done) {
 | 
			
		||||
            send(msg);
 | 
			
		||||
            done();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType("catch",CatchNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,177 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="status">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label style="width: auto" for="node-input-scope" data-i18n="status.label.source"></label>
 | 
			
		||||
        <select id="node-input-scope-select">
 | 
			
		||||
            <option value="all" data-i18n="status.scope.all"></option>
 | 
			
		||||
            <option value="target" data-i18n="status.scope.selected"></options>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-input-target-row">
 | 
			
		||||
        <button id="node-input-status-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-input-target-row node-input-target-list-row" style="position: relative; min-height: 100px">
 | 
			
		||||
        <div style="position: absolute; top: -30px; right: 0;"><input type="text" id="node-input-status-target-filter"></div>
 | 
			
		||||
        <div id="node-input-status-target-container-div"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('status',{
 | 
			
		||||
        category: 'common',
 | 
			
		||||
        color:"#94c1d0",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            scope: {value:null, type:"*[]"}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:0,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "status.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||(this.scope?this._("status.statusNodes",{number:this.scope.length}):this._("status.status"));
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var node = this;
 | 
			
		||||
            var scope = node.scope || [];
 | 
			
		||||
            this._resize = function() {
 | 
			
		||||
                var rows = $("#dialog-form>div:not(.node-input-target-list-row)");
 | 
			
		||||
                var height = $("#dialog-form").height();
 | 
			
		||||
                for (var i=0;i<rows.length;i++) {
 | 
			
		||||
                    height -= $(rows[i]).outerHeight(true);
 | 
			
		||||
                }
 | 
			
		||||
                var editorRow = $("#dialog-form>div.node-input-target-list-row");
 | 
			
		||||
                editorRow.css("height",height+"px");
 | 
			
		||||
            };
 | 
			
		||||
            var search = $("#node-input-status-target-filter").searchBox({
 | 
			
		||||
                style: "compact",
 | 
			
		||||
                delay: 300,
 | 
			
		||||
                change: function() {
 | 
			
		||||
                    var val = $(this).val().trim().toLowerCase();
 | 
			
		||||
                    if (val === "") {
 | 
			
		||||
                        dirList.treeList("filter", null);
 | 
			
		||||
                        search.searchBox("count","");
 | 
			
		||||
                    } else {
 | 
			
		||||
                        var count = dirList.treeList("filter", function(item) {
 | 
			
		||||
                            return item.label.toLowerCase().indexOf(val) > -1 || item.node.type.toLowerCase().indexOf(val) > -1
 | 
			
		||||
                        });
 | 
			
		||||
                        search.searchBox("count",count+" / "+candidateNodes.length);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var dirList = $("#node-input-status-target-container-div").css({width: "100%", height: "100%"})
 | 
			
		||||
                .treeList({multi:true}).on("treelistitemmouseover", function(e, item) {
 | 
			
		||||
                    item.node.highlighted = true;
 | 
			
		||||
                    item.node.dirty = true;
 | 
			
		||||
                    RED.view.redraw();
 | 
			
		||||
                }).on("treelistitemmouseout", function(e, item) {
 | 
			
		||||
                    item.node.highlighted = false;
 | 
			
		||||
                    item.node.dirty = true;
 | 
			
		||||
                    RED.view.redraw();
 | 
			
		||||
                })
 | 
			
		||||
            var candidateNodes = RED.nodes.filterNodes({z:node.z});
 | 
			
		||||
            var allChecked = true;
 | 
			
		||||
            var items = [];
 | 
			
		||||
            var nodeItemMap = {};
 | 
			
		||||
 | 
			
		||||
            candidateNodes.forEach(function(n) {
 | 
			
		||||
                if (n.id === node.id) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var isChecked = scope.indexOf(n.id) !== -1;
 | 
			
		||||
 | 
			
		||||
                allChecked = allChecked && isChecked;
 | 
			
		||||
 | 
			
		||||
                var nodeDef = RED.nodes.getType(n.type);
 | 
			
		||||
                var label;
 | 
			
		||||
                var sublabel;
 | 
			
		||||
                if (nodeDef) {
 | 
			
		||||
                    var l = nodeDef.label;
 | 
			
		||||
                    label = (typeof l === "function" ? l.call(n) : l)||"";
 | 
			
		||||
                    sublabel = n.type;
 | 
			
		||||
                    if (sublabel.indexOf("subflow:") === 0) {
 | 
			
		||||
                        var subflowId = sublabel.substring(8);
 | 
			
		||||
                        var subflow = RED.nodes.subflow(subflowId);
 | 
			
		||||
                        sublabel = "subflow : "+subflow.name;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (!nodeDef || !label) {
 | 
			
		||||
                    label = n.type;
 | 
			
		||||
                }
 | 
			
		||||
                nodeItemMap[n.id] = {
 | 
			
		||||
                    node: n,
 | 
			
		||||
                    label: label,
 | 
			
		||||
                    sublabel: sublabel,
 | 
			
		||||
                    selected: isChecked,
 | 
			
		||||
                    checkbox: true
 | 
			
		||||
                };
 | 
			
		||||
                items.push(nodeItemMap[n.id]);
 | 
			
		||||
            });
 | 
			
		||||
            dirList.treeList('data',items);
 | 
			
		||||
 | 
			
		||||
            $("#node-input-status-target-select").on("click", function(e) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                var preselected = dirList.treeList('selected').map(function(n) {return n.node.id});
 | 
			
		||||
                RED.tray.hide();
 | 
			
		||||
                RED.view.selectNodes({
 | 
			
		||||
                    selected: preselected,
 | 
			
		||||
                    onselect: function(selection) {
 | 
			
		||||
                        RED.tray.show();
 | 
			
		||||
                        var newlySelected = {};
 | 
			
		||||
                        selection.forEach(function(n) {
 | 
			
		||||
                            newlySelected[n.id] = true;
 | 
			
		||||
                            if (nodeItemMap[n.id]) {
 | 
			
		||||
                                nodeItemMap[n.id].treeList.select(true);
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                        preselected.forEach(function(id) {
 | 
			
		||||
                            if (!newlySelected[id]) {
 | 
			
		||||
                                nodeItemMap[id].treeList.select(false);
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                    },
 | 
			
		||||
                    oncancel: function() {
 | 
			
		||||
                        RED.tray.show();
 | 
			
		||||
                    },
 | 
			
		||||
                    filter: function(n) {
 | 
			
		||||
                        return n.id !== node.id;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            $("#node-input-scope-select").on("change", function(e) {
 | 
			
		||||
                var scope = $(this).val();
 | 
			
		||||
                if (scope === "target") {
 | 
			
		||||
                    $(".node-input-target-row").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $(".node-input-target-row").hide();
 | 
			
		||||
                }
 | 
			
		||||
                node._resize();
 | 
			
		||||
            });
 | 
			
		||||
            if (this.scope === null) {
 | 
			
		||||
                $("#node-input-scope-select").val("all");
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-input-scope-select").val("target");
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-scope-select").trigger("change");
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            var scope = $("#node-input-scope-select").val();
 | 
			
		||||
            if (scope === 'all') {
 | 
			
		||||
                this.scope = null;
 | 
			
		||||
            } else {
 | 
			
		||||
                this.scope = $("#node-input-status-target-container-div").treeList('selected').map(function(i) { return i.node.id})
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        oneditresize: function(size) {
 | 
			
		||||
            this._resize();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,31 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    function StatusNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        var node = this;
 | 
			
		||||
        this.scope = n.scope;
 | 
			
		||||
        this.on("input", function(msg, send, done) {
 | 
			
		||||
            send(msg);
 | 
			
		||||
            done();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType("status",StatusNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,340 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="link in">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div style="position:relative; height: 30px; text-align: right;"><div style="display:inline-block"><input type="text" id="node-input-link-target-filter"></div></div>
 | 
			
		||||
    <div class="form-row node-input-link-row"></div>
 | 
			
		||||
</script>
 | 
			
		||||
<script type="text/html" data-template-name="link out">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-mode"><span data-i18n="link.outMode"></span></label>
 | 
			
		||||
        <select id="node-input-mode" style="width: 70%">
 | 
			
		||||
            <option value="link" selected data-i18n="link.sendToAll"></option>
 | 
			
		||||
            <option value="return" data-i18n="link.returnToCaller"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="node-input-link-rows" style="position:relative; height: 30px; text-align: right;"><div style="display:inline-block"><input type="text" id="node-input-link-target-filter"></div></div>
 | 
			
		||||
    <div class="form-row node-input-link-row node-input-link-rows"></div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="link call">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-timeout"><span data-i18n="exec.label.timeout"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-timeout" placeholder="30" style="width: 70px; margin-right: 5px;"><span data-i18n="inject.seconds"></span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div style="position:relative; height: 30px; text-align: right;"><div style="display:inline-block"><input type="text" id="node-input-link-target-filter"></div></div>
 | 
			
		||||
    <div class="form-row node-input-link-row"></div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
(function() {
 | 
			
		||||
 | 
			
		||||
    var treeList;
 | 
			
		||||
 | 
			
		||||
    function onEditPrepare(node,targetType) {
 | 
			
		||||
        if (!node.links) {
 | 
			
		||||
            node.links = [];
 | 
			
		||||
        }
 | 
			
		||||
        node.oldLinks = [];
 | 
			
		||||
 | 
			
		||||
        var activeSubflow = RED.nodes.subflow(node.z);
 | 
			
		||||
 | 
			
		||||
        treeList = $("<div>")
 | 
			
		||||
            .css({width: "100%", height: "100%"})
 | 
			
		||||
            .appendTo(".node-input-link-row")
 | 
			
		||||
            .treeList({autoSelect:false})
 | 
			
		||||
            .on('treelistitemmouseover',function(e,item) {
 | 
			
		||||
                if (item.node) {
 | 
			
		||||
                    item.node.highlighted = true;
 | 
			
		||||
                    item.node.dirty = true;
 | 
			
		||||
                    RED.view.redraw();
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            .on('treelistitemmouseout',function(e,item) {
 | 
			
		||||
                if (item.node) {
 | 
			
		||||
                    item.node.highlighted = false;
 | 
			
		||||
                    item.node.dirty = true;
 | 
			
		||||
                    RED.view.redraw();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        var candidateNodes = RED.nodes.filterNodes({type:targetType});
 | 
			
		||||
        var candidateNodesCount = 0;
 | 
			
		||||
 | 
			
		||||
        var search = $("#node-input-link-target-filter").searchBox({
 | 
			
		||||
            style: "compact",
 | 
			
		||||
            delay: 300,
 | 
			
		||||
            change: function() {
 | 
			
		||||
                var val = $(this).val().trim().toLowerCase();
 | 
			
		||||
                if (val === "") {
 | 
			
		||||
                    treeList.treeList("filter", null);
 | 
			
		||||
                    search.searchBox("count","");
 | 
			
		||||
                } else {
 | 
			
		||||
                    var count = treeList.treeList("filter", function(item) {
 | 
			
		||||
                        return item.label.toLowerCase().indexOf(val) > -1 || (item.node && item.node.type.toLowerCase().indexOf(val) > -1)
 | 
			
		||||
                    });
 | 
			
		||||
                    search.searchBox("count",count+" / "+candidateNodesCount);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var flows = [];
 | 
			
		||||
        var flowMap = {};
 | 
			
		||||
 | 
			
		||||
        if (activeSubflow) {
 | 
			
		||||
            flowMap[activeSubflow.id] = {
 | 
			
		||||
                id: activeSubflow.id,
 | 
			
		||||
                class: 'red-ui-palette-header',
 | 
			
		||||
                label:  "Subflow : "+(activeSubflow.name || activeSubflow.id),
 | 
			
		||||
                expanded: true,
 | 
			
		||||
                children: []
 | 
			
		||||
            };
 | 
			
		||||
            flows.push(flowMap[activeSubflow.id])
 | 
			
		||||
        } else {
 | 
			
		||||
            RED.nodes.eachWorkspace(function(ws) {
 | 
			
		||||
                flowMap[ws.id] = {
 | 
			
		||||
                    id: ws.id,
 | 
			
		||||
                    class: 'red-ui-palette-header',
 | 
			
		||||
                    label: (ws.label || ws.id)+(node.z===ws.id ? " *":""),
 | 
			
		||||
                    expanded: true,
 | 
			
		||||
                    children: []
 | 
			
		||||
                }
 | 
			
		||||
                flows.push(flowMap[ws.id])
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        candidateNodes.forEach(function(n) {
 | 
			
		||||
            if (flowMap[n.z]) {
 | 
			
		||||
                if (targetType === "link out" && n.mode === 'return') {
 | 
			
		||||
                    // Link In nodes looking for Link Out nodes should not
 | 
			
		||||
                    // include return-mode nodes.
 | 
			
		||||
                    return
 | 
			
		||||
                }
 | 
			
		||||
                var isChecked = false;
 | 
			
		||||
                isChecked = (node.links.indexOf(n.id) !== -1) || (n.links||[]).indexOf(node.id) !== -1;
 | 
			
		||||
                if (isChecked) {
 | 
			
		||||
                    node.oldLinks.push(n.id);
 | 
			
		||||
                }
 | 
			
		||||
                flowMap[n.z].children.push({
 | 
			
		||||
                    id: n.id,
 | 
			
		||||
                    node: n,
 | 
			
		||||
                    label: n.name||n.id,
 | 
			
		||||
                    selected: isChecked,
 | 
			
		||||
                    checkbox: node.type !== "link call",
 | 
			
		||||
                    radio: node.type === "link call"
 | 
			
		||||
                })
 | 
			
		||||
                candidateNodesCount++;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        flows = flows.filter(function(f) { return f.children.length > 0 })
 | 
			
		||||
        treeList.treeList('data',flows);
 | 
			
		||||
        setTimeout(function() {
 | 
			
		||||
            treeList.treeList('show',node.z);
 | 
			
		||||
        },100);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function resizeNodeList() {
 | 
			
		||||
        var rows = $("#dialog-form>div:not(.node-input-link-row)");
 | 
			
		||||
        var height = $("#dialog-form").height();
 | 
			
		||||
        for (var i=0;i<rows.length;i++) {
 | 
			
		||||
            height -= $(rows[i]).outerHeight(true);
 | 
			
		||||
        }
 | 
			
		||||
        var editorRow = $("#dialog-form>div.node-input-link-row");
 | 
			
		||||
        height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
 | 
			
		||||
        $(".node-input-link-row").css("height",height+"px");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function onEditSave(node) {
 | 
			
		||||
        var flows = treeList.treeList('data');
 | 
			
		||||
        node.links = [];
 | 
			
		||||
        if (node.type !== "link out" || $("#node-input-mode").val() === 'link') {
 | 
			
		||||
            flows.forEach(function(f) {
 | 
			
		||||
                f.children.forEach(function(n) {
 | 
			
		||||
                    if (n.selected) {
 | 
			
		||||
                        node.links.push(n.id);
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        node.oldLinks.sort();
 | 
			
		||||
        node.links.sort();
 | 
			
		||||
 | 
			
		||||
        if (node.type === "link call") {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var nodeMap = {};
 | 
			
		||||
        var length = Math.max(node.oldLinks.length,node.links.length);
 | 
			
		||||
        for (var i=0;i<length;i++) {
 | 
			
		||||
            if (i<node.oldLinks.length) {
 | 
			
		||||
                nodeMap[node.oldLinks[i]] = nodeMap[node.oldLinks[i]]||{};
 | 
			
		||||
                nodeMap[node.oldLinks[i]].old = true;
 | 
			
		||||
            }
 | 
			
		||||
            if (i<node.links.length) {
 | 
			
		||||
                nodeMap[node.links[i]] = nodeMap[node.links[i]]||{};
 | 
			
		||||
                nodeMap[node.links[i]].new = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        var n;
 | 
			
		||||
        for (var id in nodeMap) {
 | 
			
		||||
            if (nodeMap.hasOwnProperty(id)) {
 | 
			
		||||
                n = RED.nodes.node(id);
 | 
			
		||||
                if (n) {
 | 
			
		||||
                    if (nodeMap[id].old && !nodeMap[id].new) {
 | 
			
		||||
                        // Removed id
 | 
			
		||||
                        i = n.links.indexOf(node.id);
 | 
			
		||||
                        if (i > -1) {
 | 
			
		||||
                            n.links.splice(i,1);
 | 
			
		||||
                        }
 | 
			
		||||
                    } else if (!nodeMap[id].old && nodeMap[id].new) {
 | 
			
		||||
                        // Added id
 | 
			
		||||
                        i = n.links.indexOf(id);
 | 
			
		||||
                        if (i === -1) {
 | 
			
		||||
                            n.links.push(node.id);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function onAdd() {
 | 
			
		||||
        for (var i=0;i<this.links.length;i++) {
 | 
			
		||||
            var n = RED.nodes.node(this.links[i]);
 | 
			
		||||
            if (n && n.links.indexOf(this.id) === -1) {
 | 
			
		||||
                n.links.push(this.id);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('link in',{
 | 
			
		||||
        category: 'common',
 | 
			
		||||
        color:"#ddd",//"#87D8CF",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            links: { value: [], type:"link out[]" }
 | 
			
		||||
        },
 | 
			
		||||
        inputs:0,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "link-out.svg",
 | 
			
		||||
        outputLabels: function(i) {
 | 
			
		||||
            return this.name||this._("link.linkIn");
 | 
			
		||||
        },
 | 
			
		||||
        showLabel: false,
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||this._("link.linkIn");
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            onEditPrepare(this,"link out");
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            onEditSave(this);
 | 
			
		||||
            // In case the name has changed, ensure any link call nodes on this
 | 
			
		||||
            // tab are redrawn with the updated name
 | 
			
		||||
            var localCallNodes = RED.nodes.filterNodes({z:RED.workspaces.active(), type:"link call"});
 | 
			
		||||
            localCallNodes.forEach(function(node) {
 | 
			
		||||
                node.dirty = true;
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        onadd: onAdd,
 | 
			
		||||
        oneditresize: resizeNodeList
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('link call',{
 | 
			
		||||
        category: 'common',
 | 
			
		||||
        color:"#ddd",//"#87D8CF",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            links: { value: [], type:"link in[]"},
 | 
			
		||||
            timeout: { value: "30", validate:RED.validators.number(true) }
 | 
			
		||||
        },
 | 
			
		||||
        inputs: 1,
 | 
			
		||||
        outputs: 1,
 | 
			
		||||
        icon: "link-call.svg",
 | 
			
		||||
        inputLabels: function(i) {
 | 
			
		||||
            return this.name||this._("link.linkCall");
 | 
			
		||||
        },
 | 
			
		||||
        label: function() {
 | 
			
		||||
            if (this.name) {
 | 
			
		||||
                return this.name;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.links.length > 0) {
 | 
			
		||||
                var targetNode = RED.nodes.node(this.links[0]);
 | 
			
		||||
                return targetNode && (targetNode.name || this._("link.linkCall"));
 | 
			
		||||
            }
 | 
			
		||||
            return this._("inject.none");
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            onEditPrepare(this,"link in");
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            onEditSave(this);
 | 
			
		||||
        },
 | 
			
		||||
        oneditresize: resizeNodeList
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('link out',{
 | 
			
		||||
        category: 'common',
 | 
			
		||||
        color:"#ddd",//"#87D8CF",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            mode: { value: "link" },// link || return
 | 
			
		||||
            links: { value: [], type:"link in[]"}
 | 
			
		||||
        },
 | 
			
		||||
        align:"right",
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:0,
 | 
			
		||||
        icon: function() {
 | 
			
		||||
            if (this.mode === "return") {
 | 
			
		||||
                return "link-return.svg";
 | 
			
		||||
            } else {
 | 
			
		||||
                return "link-out.svg";
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        inputLabels: function(i) {
 | 
			
		||||
            return this.name||(this.mode === "return" ?this._("link.linkOutReturn"):this._("link.linkOut"));
 | 
			
		||||
        },
 | 
			
		||||
        showLabel: false,
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||(this.mode === "return" ?this._("link.linkOutReturn"):this._("link.linkOut"));
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            onEditPrepare(this,"link in");
 | 
			
		||||
            $("#node-input-mode").on("change", function() {
 | 
			
		||||
                $(".node-input-link-rows").toggle(this.value === "link")
 | 
			
		||||
            })
 | 
			
		||||
            if (!this.mode) {
 | 
			
		||||
                $("#node-input-mode").val('link').trigger("change");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            onEditSave(this);
 | 
			
		||||
        },
 | 
			
		||||
        onadd: onAdd,
 | 
			
		||||
        oneditresize: resizeNodeList
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,132 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    const crypto = require("crypto");
 | 
			
		||||
 | 
			
		||||
    function LinkInNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        var node = this;
 | 
			
		||||
        var event = "node:"+n.id;
 | 
			
		||||
        var handler = function(msg) {
 | 
			
		||||
            msg._event = n.event;
 | 
			
		||||
            node.receive(msg);
 | 
			
		||||
        }
 | 
			
		||||
        RED.events.on(event,handler);
 | 
			
		||||
        this.on("input", function(msg, send, done) {
 | 
			
		||||
            send(msg);
 | 
			
		||||
            done();
 | 
			
		||||
        });
 | 
			
		||||
        this.on("close",function() {
 | 
			
		||||
            RED.events.removeListener(event,handler);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType("link in",LinkInNode);
 | 
			
		||||
 | 
			
		||||
    function LinkOutNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        var node = this;
 | 
			
		||||
        var mode = n.mode || "link";
 | 
			
		||||
 | 
			
		||||
        var event = "node:"+n.id;
 | 
			
		||||
        this.on("input", function(msg, send, done) {
 | 
			
		||||
            msg._event = event;
 | 
			
		||||
            RED.events.emit(event,msg)
 | 
			
		||||
 | 
			
		||||
            if (mode === "return") {
 | 
			
		||||
                if (Array.isArray(msg._linkSource) && msg._linkSource.length > 0) {
 | 
			
		||||
                    var messageEvent = msg._linkSource.pop();
 | 
			
		||||
                    var returnNode = RED.nodes.getNode(messageEvent.node);
 | 
			
		||||
                    if (returnNode && returnNode.returnLinkMessage) {
 | 
			
		||||
                        returnNode.returnLinkMessage(messageEvent.id, msg);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        node.warn(RED._("link.error.missingReturn"))
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    node.warn(RED._("link.error.missingReturn"))
 | 
			
		||||
                }
 | 
			
		||||
                done();
 | 
			
		||||
            } else if (mode === "link") {
 | 
			
		||||
                send(msg);
 | 
			
		||||
                done();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("link out",LinkOutNode);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function LinkCallNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        const node = this;
 | 
			
		||||
        const target = n.links[0];
 | 
			
		||||
        const messageEvents = {};
 | 
			
		||||
        let timeout = parseFloat(n.timeout || "30")*1000;
 | 
			
		||||
        if (isNaN(timeout)) {
 | 
			
		||||
            timeout = 30000;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.on("input", function(msg, send, done) {
 | 
			
		||||
            msg._linkSource = msg._linkSource || [];
 | 
			
		||||
            const messageEvent = {
 | 
			
		||||
                id: crypto.randomBytes(14).toString('hex'),
 | 
			
		||||
                node: node.id,
 | 
			
		||||
            }
 | 
			
		||||
            messageEvents[messageEvent.id] = {
 | 
			
		||||
                msg: RED.util.cloneMessage(msg),
 | 
			
		||||
                send,
 | 
			
		||||
                done,
 | 
			
		||||
                ts: setTimeout(function() {
 | 
			
		||||
                    timeoutMessage(messageEvent.id)
 | 
			
		||||
                }, timeout )
 | 
			
		||||
            };
 | 
			
		||||
            msg._linkSource.push(messageEvent);
 | 
			
		||||
            var targetNode = RED.nodes.getNode(target);
 | 
			
		||||
            if (targetNode) {
 | 
			
		||||
                targetNode.receive(msg);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.returnLinkMessage = function(eventId, msg) {
 | 
			
		||||
            if (Array.isArray(msg._linkSource) && msg._linkSource.length === 0) {
 | 
			
		||||
                delete msg._linkSource;
 | 
			
		||||
            }
 | 
			
		||||
            const messageEvent = messageEvents[eventId];
 | 
			
		||||
            if (messageEvent) {
 | 
			
		||||
                clearTimeout(messageEvent.ts);
 | 
			
		||||
                delete messageEvents[eventId];
 | 
			
		||||
                messageEvent.send(msg);
 | 
			
		||||
                messageEvent.done();
 | 
			
		||||
            } else {
 | 
			
		||||
                node.send(msg);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function timeoutMessage(eventId) {
 | 
			
		||||
            const messageEvent = messageEvents[eventId];
 | 
			
		||||
            if (messageEvent) {
 | 
			
		||||
                delete messageEvents[eventId];
 | 
			
		||||
                node.error("timeout",messageEvent.msg);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("link call",LinkCallNode);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="unknown">
 | 
			
		||||
    <div class="form-tips"><span data-i18n="[html]unknown.tip"></span></div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('unknown',{
 | 
			
		||||
        category: 'unknown',
 | 
			
		||||
        color:"#fff0f0",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return "("+this.name+")"||this._("unknown.label.unknown");
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return "node_label_unknown";
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    function UnknownNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("unknown",UnknownNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,440 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="switch">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" style="width: calc(100% - 105px)" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="switch.label.property"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-property" style="width: calc(100% - 105px)"/>
 | 
			
		||||
        <input type="hidden" id="node-input-outputs"/>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-input-rule-container-row">
 | 
			
		||||
        <ol id="node-input-rule-container"></ol>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <select id="node-input-checkall" style="width:100%; margin-right:5px;">
 | 
			
		||||
            <option value="true" data-i18n="switch.checkall"></option>
 | 
			
		||||
            <option value="false" data-i18n="switch.stopfirst"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <input type="checkbox" id="node-input-repair" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label style="width: auto;" for="node-input-repair"><span data-i18n="switch.label.repair"></span></label></input>
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
(function() {
 | 
			
		||||
    var operators = [
 | 
			
		||||
        {v:"eq",t:"==",kind:'V'},
 | 
			
		||||
        {v:"neq",t:"!=",kind:'V'},
 | 
			
		||||
        {v:"lt",t:"<",kind:'V'},
 | 
			
		||||
        {v:"lte",t:"<=",kind:'V'},
 | 
			
		||||
        {v:"gt",t:">",kind:'V'},
 | 
			
		||||
        {v:"gte",t:">=",kind:'V'},
 | 
			
		||||
        {v:"hask",t:"switch.rules.hask",kind:'V'},
 | 
			
		||||
        {v:"btwn",t:"switch.rules.btwn",kind:'V'},
 | 
			
		||||
        {v:"cont",t:"switch.rules.cont",kind:'V'},
 | 
			
		||||
        {v:"regex",t:"switch.rules.regex",kind:'V'},
 | 
			
		||||
        {v:"true",t:"switch.rules.true",kind:'V'},
 | 
			
		||||
        {v:"false",t:"switch.rules.false",kind:'V'},
 | 
			
		||||
        {v:"null",t:"switch.rules.null",kind:'V'},
 | 
			
		||||
        {v:"nnull",t:"switch.rules.nnull",kind:'V'},
 | 
			
		||||
        {v:"istype",t:"switch.rules.istype",kind:'V'},
 | 
			
		||||
        {v:"empty",t:"switch.rules.empty",kind:'V'},
 | 
			
		||||
        {v:"nempty",t:"switch.rules.nempty",kind:'V'},
 | 
			
		||||
        {v:"head",t:"switch.rules.head",kind:'S'},
 | 
			
		||||
        {v:"index",t:"switch.rules.index",kind:'S'},
 | 
			
		||||
        {v:"tail",t:"switch.rules.tail",kind:'S'},
 | 
			
		||||
        {v:"jsonata_exp",t:"switch.rules.exp",kind:'O'},
 | 
			
		||||
        {v:"else",t:"switch.rules.else",kind:'O'}
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    var previousValueType = {value:"prev",label:RED._("node-red:switch.previous"),hasValue:false};
 | 
			
		||||
    function clipValueLength(v) {
 | 
			
		||||
        if (v.length > 15) {
 | 
			
		||||
            return v.substring(0,15)+"...";
 | 
			
		||||
        }
 | 
			
		||||
        return v;
 | 
			
		||||
    }
 | 
			
		||||
    function prop2name(key) {
 | 
			
		||||
        var result = RED.utils.parseContextKey(key);
 | 
			
		||||
        return result.key;
 | 
			
		||||
    }
 | 
			
		||||
    function getValueLabel(t,v) {
 | 
			
		||||
        if (t === 'str') {
 | 
			
		||||
            return '"'+clipValueLength(v)+'"';
 | 
			
		||||
        } else if (t === 'msg') {
 | 
			
		||||
            return t+"."+clipValueLength(v);
 | 
			
		||||
        } else if (t === 'flow' || t === 'global') {
 | 
			
		||||
            return t+"."+clipValueLength(prop2name(v));
 | 
			
		||||
        }
 | 
			
		||||
        return clipValueLength(v);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function exportRule(rule) {
 | 
			
		||||
        var type = rule.find("select").val();
 | 
			
		||||
        var r = {t:type};
 | 
			
		||||
        if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "empty" || type === "nempty" || type === "else")) {
 | 
			
		||||
            if ((type === "btwn") || (type === "index")) {
 | 
			
		||||
                r.v = rule.find(".node-input-rule-btwn-value").typedInput('value');
 | 
			
		||||
                r.vt = rule.find(".node-input-rule-btwn-value").typedInput('type');
 | 
			
		||||
                r.v2 = rule.find(".node-input-rule-btwn-value2").typedInput('value');
 | 
			
		||||
                r.v2t = rule.find(".node-input-rule-btwn-value2").typedInput('type');
 | 
			
		||||
            } else if ((type === "head") || (type === "tail")) {
 | 
			
		||||
                r.v = rule.find(".node-input-rule-num-value").typedInput('value');
 | 
			
		||||
                r.vt = rule.find(".node-input-rule-num-value").typedInput('type');
 | 
			
		||||
            } else if (type === "istype") {
 | 
			
		||||
                r.v = rule.find(".node-input-rule-type-value").typedInput('type');
 | 
			
		||||
                r.vt = rule.find(".node-input-rule-type-value").typedInput('type');
 | 
			
		||||
            } else if (type === "jsonata_exp") {
 | 
			
		||||
                r.v = rule.find(".node-input-rule-exp-value").typedInput('value');
 | 
			
		||||
                r.vt = rule.find(".node-input-rule-exp-value").typedInput('type');
 | 
			
		||||
            } else {
 | 
			
		||||
                r.v = rule.find(".node-input-rule-value").typedInput('value');
 | 
			
		||||
                r.vt = rule.find(".node-input-rule-value").typedInput('type');
 | 
			
		||||
            }
 | 
			
		||||
            if (type === "regex") {
 | 
			
		||||
                r.case = rule.find(".node-input-rule-case").prop("checked");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return r;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function createValueField(row, defaultType){
 | 
			
		||||
        return $('<input/>',{class:"node-input-rule-value",type:"text",style:"width: 100%;"}).appendTo(row)
 | 
			
		||||
            .typedInput({default:defaultType||'str',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function createNumValueField(row, defaultType){
 | 
			
		||||
        return $('<input/>',{class:"node-input-rule-num-value",type:"text",style:"width: 100%;"}).appendTo(row)
 | 
			
		||||
            .typedInput({default:defaultType||'num',types:['flow','global','num','jsonata','env']});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function createExpValueField(row){
 | 
			
		||||
        return $('<input/>',{class:"node-input-rule-exp-value",type:"text",style:"width: 100%;"}).appendTo(row)
 | 
			
		||||
            .typedInput({default:'jsonata',types:['jsonata']});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function createBtwnValueField(row, defaultType){
 | 
			
		||||
        return $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"width: 100%;"}).appendTo(row)
 | 
			
		||||
                .typedInput({default:defaultType||'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function createBtwnValue2Field(row3, andLabel, defaultType){
 | 
			
		||||
        $('<div/>',{class:"node-input-rule-btwn-label", style:"width: 120px; text-align: right;"}).text(" "+andLabel+" ").appendTo(row3);
 | 
			
		||||
        var row3InputCell = $('<div/>',{style:"flex-grow:1; margin-left: 5px;"}).appendTo(row3);
 | 
			
		||||
        return $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"width: 100%"}).appendTo(row3InputCell)
 | 
			
		||||
            .typedInput({default:defaultType||'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function createTypeValueField(row, defaultType){
 | 
			
		||||
        return $('<input/>',{class:"node-input-rule-type-value",type:"text",style:"width: 100%;"}).appendTo(row).typedInput({default:defaultType || 'string',types:[
 | 
			
		||||
            {value:"string",label:RED._("common.type.string"),hasValue:false,icon:"red/images/typedInput/az.png"},
 | 
			
		||||
            {value:"number",label:RED._("common.type.number"),hasValue:false,icon:"red/images/typedInput/09.png"},
 | 
			
		||||
            {value:"boolean",label:RED._("common.type.boolean"),hasValue:false,icon:"red/images/typedInput/bool.png"},
 | 
			
		||||
            {value:"array",label:RED._("common.type.array"),hasValue:false,icon:"red/images/typedInput/json.png"},
 | 
			
		||||
            {value:"buffer",label:RED._("common.type.buffer"),hasValue:false,icon:"red/images/typedInput/bin.png"},
 | 
			
		||||
            {value:"object",label:RED._("common.type.object"),hasValue:false,icon:"red/images/typedInput/json.png"},
 | 
			
		||||
            {value:"json",label:RED._("common.type.jsonString"),hasValue:false,icon:"red/images/typedInput/json.png"},
 | 
			
		||||
            {value:"undefined",label:RED._("common.type.undefined"),hasValue:false},
 | 
			
		||||
            {value:"null",label:RED._("common.type.null"),hasValue:false}
 | 
			
		||||
        ]});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('switch', {
 | 
			
		||||
        color: "#E2D96E",
 | 
			
		||||
        category: 'function',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            property: {value:"payload", required:true, validate: RED.validators.typedInput("propertyType")},
 | 
			
		||||
            propertyType: { value:"msg" },
 | 
			
		||||
            rules: {value:[{t:"eq", v:"", vt:"str"}]},
 | 
			
		||||
            checkall: {value:"true", required:true},
 | 
			
		||||
            repair: {value:false},
 | 
			
		||||
            outputs: {value:1}
 | 
			
		||||
        },
 | 
			
		||||
        inputs: 1,
 | 
			
		||||
        outputs: 1,
 | 
			
		||||
        outputLabels: function(index) {
 | 
			
		||||
            var rule = this.rules[index];
 | 
			
		||||
            var label = "";
 | 
			
		||||
            if (rule) {
 | 
			
		||||
                for (var i=0;i<operators.length;i++) {
 | 
			
		||||
                    if (operators[i].v === rule.t) {
 | 
			
		||||
                        label = /^switch/.test(operators[i].t)?this._(operators[i].t):operators[i].t;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if ((rule.t === 'btwn') || (rule.t === 'index')) {
 | 
			
		||||
                    label += " "+getValueLabel(rule.vt,rule.v)+" & "+getValueLabel(rule.v2t,rule.v2);
 | 
			
		||||
                } else if (rule.t !== 'true' && rule.t !== 'false' && rule.t !== 'null' && rule.t !== 'nnull' && rule.t !== 'empty' && rule.t !== 'nempty' && rule.t !== 'else' ) {
 | 
			
		||||
                    label += " "+getValueLabel(rule.vt,rule.v);
 | 
			
		||||
                }
 | 
			
		||||
                return label;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        icon: "switch.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||this._("switch.switch");
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var node = this;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            $("#node-input-property").typedInput({default:this.propertyType||'msg',types:['msg','flow','global','jsonata','env']});
 | 
			
		||||
            var outputCount = $("#node-input-outputs").val("{}");
 | 
			
		||||
 | 
			
		||||
            var andLabel = this._("switch.and");
 | 
			
		||||
            var caseLabel = this._("switch.ignorecase");
 | 
			
		||||
 | 
			
		||||
            $("#node-input-rule-container").css('min-height','150px').css('min-width','450px').editableList({
 | 
			
		||||
                addItem: function(container,i,opt) {
 | 
			
		||||
                    var focusValueField = false;
 | 
			
		||||
                    if (!opt.hasOwnProperty('r')) {
 | 
			
		||||
                        opt.r = {};
 | 
			
		||||
                        if (i > 0) {
 | 
			
		||||
                            var lastRule = $("#node-input-rule-container").editableList('getItemAt',i-1);
 | 
			
		||||
                            var exportedRule = exportRule(lastRule.element);
 | 
			
		||||
                            opt.r.vt = exportedRule.vt;
 | 
			
		||||
                            opt.r.v = "";
 | 
			
		||||
                            // We could copy the value over as well and preselect it (see the 'activeElement' code below)
 | 
			
		||||
                            // But not sure that feels right. Is copying over the last value 'expected' behaviour?
 | 
			
		||||
                            // It would make sense for an explicit 'copy' action, but not sure where the copy button would
 | 
			
		||||
                            // go for each rule without being wasted space for most users.
 | 
			
		||||
                            // opt.r.v = exportedRule.v;
 | 
			
		||||
                            focusValueField = true;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    opt.element = container;
 | 
			
		||||
                    var rule = opt.r;
 | 
			
		||||
                    if (!rule.hasOwnProperty('t')) {
 | 
			
		||||
                        rule.t = 'eq';
 | 
			
		||||
                    }
 | 
			
		||||
                    if (!opt.hasOwnProperty('i')) {
 | 
			
		||||
                        opt._i = Math.floor((0x99999-0x10000)*Math.random()).toString();
 | 
			
		||||
                    }
 | 
			
		||||
                    container.css({
 | 
			
		||||
                        overflow: 'hidden',
 | 
			
		||||
                        whiteSpace: 'nowrap',
 | 
			
		||||
                        display: "flex",
 | 
			
		||||
                        "align-items":"center"
 | 
			
		||||
                    });
 | 
			
		||||
                    var inputRows = $('<div></div>',{style:"flex-grow:1"}).appendTo(container);
 | 
			
		||||
                    var row = $('<div></div>',{style:"display: flex;"}).appendTo(inputRows);
 | 
			
		||||
                    var row2 = $('<div/>',{style:"display: flex; padding-top: 5px; padding-left: 175px;"}).appendTo(inputRows);
 | 
			
		||||
                    var row3 = $('<div/>',{style:"display: flex; padding-top: 5px; align-items: center"}).appendTo(inputRows);
 | 
			
		||||
 | 
			
		||||
                    var selectField = $('<select/>',{style:"width:120px; text-align: center;"}).appendTo(row);
 | 
			
		||||
                    var group0 = $('<optgroup/>', { label: "value rules" }).appendTo(selectField);
 | 
			
		||||
                    for (var d in operators) {
 | 
			
		||||
                        if(operators[d].kind === 'V') {
 | 
			
		||||
                            group0.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    var group1 = $('<optgroup/>', { label: "sequence rules" }).appendTo(selectField);
 | 
			
		||||
                    for (var d in operators) {
 | 
			
		||||
                        if(operators[d].kind === 'S') {
 | 
			
		||||
                            group1.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    for (var d in operators) {
 | 
			
		||||
                        if(operators[d].kind === 'O') {
 | 
			
		||||
                            selectField.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var rowInputCell = $('<div>',{style:"flex-grow:1; margin-left: 5px;"}).appendTo(row);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                    var valueField = null;
 | 
			
		||||
                    var numValueField = null;
 | 
			
		||||
                    var expValueField = null;
 | 
			
		||||
                    var btwnAndLabel = null;
 | 
			
		||||
                    var btwnValueField = null;
 | 
			
		||||
                    var btwnValue2Field = null;
 | 
			
		||||
                    var typeValueField = null;
 | 
			
		||||
 | 
			
		||||
                    var finalspan = $('<span/>',{style:"margin-left: 5px;"}).appendTo(container);
 | 
			
		||||
                    finalspan.append(' → <span class="node-input-rule-index">'+(i+1)+'</span> ');
 | 
			
		||||
 | 
			
		||||
                    var caseSensitive = $('<input/>',{id:"node-input-rule-case-"+i,class:"node-input-rule-case",type:"checkbox",style:"width:auto;vertical-align:top"}).appendTo(row2);
 | 
			
		||||
                    $('<label/>',{for:"node-input-rule-case-"+i,style:"margin-left: 3px;"}).text(caseLabel).appendTo(row2);
 | 
			
		||||
 | 
			
		||||
                    selectField.on("change", function() {
 | 
			
		||||
                        var fieldToFocus;
 | 
			
		||||
                        var type = selectField.val();
 | 
			
		||||
                        if (valueField) { valueField.typedInput('hide'); }
 | 
			
		||||
                        if (expValueField) { expValueField.typedInput('hide'); }
 | 
			
		||||
                        if (numValueField) { numValueField.typedInput('hide'); }
 | 
			
		||||
                        if (typeValueField) { typeValueField.typedInput('hide'); }
 | 
			
		||||
                        if (btwnValueField) { btwnValueField.typedInput('hide'); }
 | 
			
		||||
                        if (btwnValue2Field) { btwnValue2Field.typedInput('hide'); }
 | 
			
		||||
 | 
			
		||||
                        if ((type === "btwn") || (type === "index")) {
 | 
			
		||||
                            if (!btwnValueField){
 | 
			
		||||
                                btwnValueField = createBtwnValueField(rowInputCell);
 | 
			
		||||
                            }
 | 
			
		||||
                            btwnValueField.typedInput('show');
 | 
			
		||||
                            fieldToFocus = btwnValueField;
 | 
			
		||||
                        } else if ((type === "head") || (type === "tail")) {
 | 
			
		||||
                            if (!numValueField){
 | 
			
		||||
                                numValueField = createNumValueField(rowInputCell);
 | 
			
		||||
                            }
 | 
			
		||||
                            numValueField.typedInput('show');
 | 
			
		||||
                            fieldToFocus = numValueField;
 | 
			
		||||
                        } else if (type === "jsonata_exp") {
 | 
			
		||||
                            if (!expValueField){
 | 
			
		||||
                                expValueField = createExpValueField(rowInputCell);
 | 
			
		||||
                            }
 | 
			
		||||
                            expValueField.typedInput('show');
 | 
			
		||||
                            fieldToFocus = expValueField;
 | 
			
		||||
 | 
			
		||||
                        } else if (type === "istype") {
 | 
			
		||||
                            if (!typeValueField){
 | 
			
		||||
                                typeValueField = createTypeValueField(rowInputCell);
 | 
			
		||||
                            }
 | 
			
		||||
                            typeValueField.typedInput('show');
 | 
			
		||||
                            fieldToFocus = typeValueField;
 | 
			
		||||
                        } else if (! (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "empty" || type === "nempty" || type === "else" )) {
 | 
			
		||||
                                if (!valueField){
 | 
			
		||||
                                    valueField = createValueField(rowInputCell);
 | 
			
		||||
                                }
 | 
			
		||||
                                valueField.typedInput('show');
 | 
			
		||||
                                fieldToFocus = valueField;
 | 
			
		||||
                        }
 | 
			
		||||
                        if (type === "regex") {
 | 
			
		||||
                            row2.show();
 | 
			
		||||
                            row3.hide();
 | 
			
		||||
                        } else if ((type === "btwn") || (type === "index")) {
 | 
			
		||||
                            row2.hide();
 | 
			
		||||
                            row3.show();
 | 
			
		||||
                            if (!btwnValue2Field){
 | 
			
		||||
                                btwnValue2Field = createBtwnValue2Field(row3, andLabel);
 | 
			
		||||
                            }
 | 
			
		||||
                            btwnValue2Field.typedInput('show');
 | 
			
		||||
                        } else {
 | 
			
		||||
                            row2.hide();
 | 
			
		||||
                            row3.hide();
 | 
			
		||||
                        }
 | 
			
		||||
                        var selectedLabel = selectField.find("option:selected").text();
 | 
			
		||||
                        if (selectedLabel.length <= 5) {
 | 
			
		||||
                            selectField.outerWidth(60);
 | 
			
		||||
                        } else if (selectedLabel.length < 12) {
 | 
			
		||||
                            selectField.outerWidth(120);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            selectField.width("auto")
 | 
			
		||||
                        }
 | 
			
		||||
                        if (fieldToFocus) {
 | 
			
		||||
                            fieldToFocus.typedInput("focus");
 | 
			
		||||
                        }
 | 
			
		||||
                        // Preselect the contents of the element
 | 
			
		||||
                        // if (focusValueField && document.activeElement) {
 | 
			
		||||
                        //     document.activeElement.selectionStart = 0;
 | 
			
		||||
                        //     document.activeElement.selectionEnd = document.activeElement.value.length;
 | 
			
		||||
                        // }
 | 
			
		||||
                    });
 | 
			
		||||
                    selectField.val(rule.t);
 | 
			
		||||
 | 
			
		||||
                    if ((rule.t == "btwn") || (rule.t == "index")) {
 | 
			
		||||
                        btwnValueField = createBtwnValueField(rowInputCell,rule.vt||'num');
 | 
			
		||||
                        btwnValueField.typedInput('value',rule.v);
 | 
			
		||||
                        btwnValue2Field = createBtwnValue2Field(row3, andLabel,rule.v2t||'num');
 | 
			
		||||
                        btwnValue2Field.typedInput('value',rule.v2);
 | 
			
		||||
                    } else if ((rule.t === "head") || (rule.t === "tail")) {
 | 
			
		||||
                        numValueField = createNumValueField(rowInputCell,rule.vt||'num');
 | 
			
		||||
                        numValueField.typedInput('value',rule.v);
 | 
			
		||||
                    } else if (rule.t === "istype") {
 | 
			
		||||
                        typeValueField = createTypeValueField(rowInputCell,rule.vt);
 | 
			
		||||
                        typeValueField.typedInput('value',rule.vt);
 | 
			
		||||
                    } else if (rule.t === "jsonata_exp") {
 | 
			
		||||
                        expValueField = createExpValueField(rowInputCell,rule.vt||'jsonata');
 | 
			
		||||
                        expValueField.typedInput('value',rule.v);
 | 
			
		||||
                    } else if (typeof rule.v != "undefined") {
 | 
			
		||||
                        valueField = createValueField(rowInputCell,rule.vt||'str');
 | 
			
		||||
                        valueField.typedInput('value',rule.v);
 | 
			
		||||
                    }
 | 
			
		||||
                    caseSensitive.prop('checked',!!rule.case);
 | 
			
		||||
                    selectField.change();
 | 
			
		||||
 | 
			
		||||
                    var currentOutputs = JSON.parse(outputCount.val()||"{}");
 | 
			
		||||
                    currentOutputs[opt.hasOwnProperty('i')?opt.i:opt._i] = i;
 | 
			
		||||
                    outputCount.val(JSON.stringify(currentOutputs));
 | 
			
		||||
                },
 | 
			
		||||
                removeItem: function(opt) {
 | 
			
		||||
                    var currentOutputs = JSON.parse(outputCount.val()||"{}");
 | 
			
		||||
                    if (opt.hasOwnProperty('i')) {
 | 
			
		||||
                        currentOutputs[opt.i] = -1;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        delete currentOutputs[opt._i];
 | 
			
		||||
                    }
 | 
			
		||||
                    var rules = $("#node-input-rule-container").editableList('items');
 | 
			
		||||
                    rules.each(function(i) {
 | 
			
		||||
                        $(this).find(".node-input-rule-index").html(i+1);
 | 
			
		||||
                        var data = $(this).data('data');
 | 
			
		||||
                        currentOutputs[data.hasOwnProperty('i')?data.i:data._i] = i;
 | 
			
		||||
                    });
 | 
			
		||||
                    outputCount.val(JSON.stringify(currentOutputs));
 | 
			
		||||
                },
 | 
			
		||||
                sortItems: function(rules) {
 | 
			
		||||
                    var currentOutputs = JSON.parse(outputCount.val()||"{}");
 | 
			
		||||
                    var rules = $("#node-input-rule-container").editableList('items');
 | 
			
		||||
                    rules.each(function(i) {
 | 
			
		||||
                        $(this).find(".node-input-rule-index").html(i+1);
 | 
			
		||||
                        var data = $(this).data('data');
 | 
			
		||||
                        currentOutputs[data.hasOwnProperty('i')?data.i:data._i] = i;
 | 
			
		||||
                    });
 | 
			
		||||
                    outputCount.val(JSON.stringify(currentOutputs));
 | 
			
		||||
                },
 | 
			
		||||
                sortable: true,
 | 
			
		||||
                removable: true
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            for (var i=0;i<this.rules.length;i++) {
 | 
			
		||||
                var rule = this.rules[i];
 | 
			
		||||
                $("#node-input-rule-container").editableList('addItem',{r:rule,i:i});
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            var rules = $("#node-input-rule-container").editableList('items');
 | 
			
		||||
            var node = this;
 | 
			
		||||
            node.rules = [];
 | 
			
		||||
            rules.each(function(i) {
 | 
			
		||||
                node.rules.push(exportRule($(this)));
 | 
			
		||||
            });
 | 
			
		||||
            this.propertyType = $("#node-input-property").typedInput('type');
 | 
			
		||||
        },
 | 
			
		||||
        oneditresize: function(size) {
 | 
			
		||||
            var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
 | 
			
		||||
            var height = size.height;
 | 
			
		||||
            for (var i=0;i<rows.length;i++) {
 | 
			
		||||
                height -= $(rows[i]).outerHeight(true);
 | 
			
		||||
            }
 | 
			
		||||
            var editorRow = $("#dialog-form>div.node-input-rule-container-row");
 | 
			
		||||
            height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
 | 
			
		||||
            height += 16;
 | 
			
		||||
            $("#node-input-rule-container").editableList('height',height);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
})();
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,521 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    var operators = {
 | 
			
		||||
        'eq': function(a, b) { return a == b; },
 | 
			
		||||
        'neq': function(a, b) { return a != b; },
 | 
			
		||||
        'lt': function(a, b) { return a < b; },
 | 
			
		||||
        'lte': function(a, b) { return a <= b; },
 | 
			
		||||
        'gt': function(a, b) { return a > b; },
 | 
			
		||||
        'gte': function(a, b) { return a >= b; },
 | 
			
		||||
        'btwn': function(a, b, c) { return (a >= b && a <= c) || (a <= b && a >= c); },
 | 
			
		||||
        'cont': function(a, b) { return (a + "").indexOf(b) != -1; },
 | 
			
		||||
        'regex': function(a, b, c, d) { return (a + "").match(new RegExp(b,d?'i':'')); },
 | 
			
		||||
        'true': function(a) { return a === true; },
 | 
			
		||||
        'false': function(a) { return a === false; },
 | 
			
		||||
        'null': function(a) { return (typeof a == "undefined" || a === null); },
 | 
			
		||||
        'nnull': function(a) { return (typeof a != "undefined" && a !== null); },
 | 
			
		||||
        'empty': function(a) {
 | 
			
		||||
            if (typeof a === 'string' || Array.isArray(a) || Buffer.isBuffer(a)) {
 | 
			
		||||
                return a.length === 0;
 | 
			
		||||
            } else if (typeof a === 'object' && a !== null) {
 | 
			
		||||
                return Object.keys(a).length === 0;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        'nempty': function(a) {
 | 
			
		||||
            if (typeof a === 'string' || Array.isArray(a) || Buffer.isBuffer(a)) {
 | 
			
		||||
                return a.length !== 0;
 | 
			
		||||
            } else if (typeof a === 'object' && a !== null) {
 | 
			
		||||
                return Object.keys(a).length !== 0;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        'istype': function(a, b) {
 | 
			
		||||
            if (b === "array") { return Array.isArray(a); }
 | 
			
		||||
            else if (b === "buffer") { return Buffer.isBuffer(a); }
 | 
			
		||||
            else if (b === "json") {
 | 
			
		||||
                try { JSON.parse(a); return true; }   // or maybe ??? a !== null; }
 | 
			
		||||
                catch(e) { return false;}
 | 
			
		||||
            }
 | 
			
		||||
            else if (b === "null") { return a === null; }
 | 
			
		||||
            else { return typeof a === b && !Array.isArray(a) && !Buffer.isBuffer(a) && a !== null; }
 | 
			
		||||
        },
 | 
			
		||||
        'head': function(a, b, c, d, parts) {
 | 
			
		||||
            var count = Number(b);
 | 
			
		||||
            return (parts.index < count);
 | 
			
		||||
        },
 | 
			
		||||
        'tail': function(a, b, c, d, parts) {
 | 
			
		||||
            var count = Number(b);
 | 
			
		||||
            return (parts.count -count <= parts.index);
 | 
			
		||||
        },
 | 
			
		||||
        'index': function(a, b, c, d, parts) {
 | 
			
		||||
            var min = Number(b);
 | 
			
		||||
            var max = Number(c);
 | 
			
		||||
            var index = parts.index;
 | 
			
		||||
            return ((min <= index) && (index <= max));
 | 
			
		||||
        },
 | 
			
		||||
        'hask': function(a, b) {
 | 
			
		||||
            return a !== undefined && a !== null && (typeof b !== "object" )  &&  a.hasOwnProperty(b+"");
 | 
			
		||||
        },
 | 
			
		||||
        'jsonata_exp': function(a, b) { return (b === true); },
 | 
			
		||||
        'else': function(a) { return a === true; }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    var _maxKeptCount;
 | 
			
		||||
 | 
			
		||||
    function getMaxKeptCount() {
 | 
			
		||||
        if (_maxKeptCount === undefined) {
 | 
			
		||||
            var name = "nodeMessageBufferMaxLength";
 | 
			
		||||
            if (RED.settings.hasOwnProperty(name)) {
 | 
			
		||||
                _maxKeptCount = RED.settings[name];
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                _maxKeptCount = 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return _maxKeptCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getProperty(node,msg,done) {
 | 
			
		||||
        if (node.propertyType === 'jsonata') {
 | 
			
		||||
            RED.util.evaluateJSONataExpression(node.property,msg,(err,value) => {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    done(RED._("switch.errors.invalid-expr",{error:err.message}));
 | 
			
		||||
                } else {
 | 
			
		||||
                    done(undefined,value);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg,(err,value) => {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    done(undefined,undefined);
 | 
			
		||||
                } else {
 | 
			
		||||
                    done(undefined,value);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getV1(node,msg,rule,hasParts,done) {
 | 
			
		||||
        if (rule.vt === 'prev') {
 | 
			
		||||
            return done(undefined,node.previousValue);
 | 
			
		||||
        } else if (rule.vt === 'jsonata') {
 | 
			
		||||
            var exp = rule.v;
 | 
			
		||||
            if (rule.t === 'jsonata_exp') {
 | 
			
		||||
                if (hasParts) {
 | 
			
		||||
                    exp.assign("I", msg.parts.index);
 | 
			
		||||
                    exp.assign("N", msg.parts.count);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            RED.util.evaluateJSONataExpression(exp,msg,(err,value) => {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    done(RED._("switch.errors.invalid-expr",{error:err.message}));
 | 
			
		||||
                } else {
 | 
			
		||||
                    done(undefined, value);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } else if (rule.vt === 'json') {
 | 
			
		||||
            done(undefined,"json"); // TODO: ?! invalid case
 | 
			
		||||
        } else if (rule.vt === 'null') {
 | 
			
		||||
            done(undefined,"null");
 | 
			
		||||
        } else {
 | 
			
		||||
            RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg, function(err,value) {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    done(undefined, undefined);
 | 
			
		||||
                } else {
 | 
			
		||||
                    done(undefined, value);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getV2(node,msg,rule,done) {
 | 
			
		||||
        var v2 = rule.v2;
 | 
			
		||||
        if (rule.v2t === 'prev') {
 | 
			
		||||
            return done(undefined,node.previousValue);
 | 
			
		||||
        } else if (rule.v2t === 'jsonata') {
 | 
			
		||||
            RED.util.evaluateJSONataExpression(rule.v2,msg,(err,value) => {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    done(RED._("switch.errors.invalid-expr",{error:err.message}));
 | 
			
		||||
                } else {
 | 
			
		||||
                    done(undefined,value);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } else if (typeof v2 !== 'undefined') {
 | 
			
		||||
            RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg, function(err,value) {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    done(undefined,undefined);
 | 
			
		||||
                } else {
 | 
			
		||||
                    done(undefined,value);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            done(undefined,v2);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function applyRule(node, msg, property, state, done) {
 | 
			
		||||
        var rule = node.rules[state.currentRule];
 | 
			
		||||
        var v1,v2;
 | 
			
		||||
 | 
			
		||||
        getV1(node,msg,rule,state.hasParts, (err,value) => {
 | 
			
		||||
            if (err) {
 | 
			
		||||
                // This only happens if v1 is an invalid JSONata expr
 | 
			
		||||
                // But that will have already been logged and the node marked
 | 
			
		||||
                // invalid as part of the constructor
 | 
			
		||||
                return done(err);
 | 
			
		||||
            }
 | 
			
		||||
            v1 = value;
 | 
			
		||||
            getV2(node,msg,rule, (err,value) => {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    // This only happens if v1 is an invalid JSONata expr
 | 
			
		||||
                    // But that will have already been logged and the node marked
 | 
			
		||||
                    // invalid as part of the constructor
 | 
			
		||||
                    return done(err);
 | 
			
		||||
                }
 | 
			
		||||
                v2 = value;
 | 
			
		||||
                if (rule.t == "else") {
 | 
			
		||||
                    property = state.elseflag;
 | 
			
		||||
                    state.elseflag = true;
 | 
			
		||||
                }
 | 
			
		||||
                try {
 | 
			
		||||
                    if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
 | 
			
		||||
                        state.onward.push(msg);
 | 
			
		||||
                        state.elseflag = false;
 | 
			
		||||
                        if (node.checkall == "false") {
 | 
			
		||||
                            return done(undefined,false);
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        state.onward.push(null);
 | 
			
		||||
                    }
 | 
			
		||||
                    done(undefined, state.currentRule < node.rules.length - 1);
 | 
			
		||||
                } catch(err) {
 | 
			
		||||
                    // An error occurred evaluating the rule - for example, an
 | 
			
		||||
                    // invalid RegExp value.
 | 
			
		||||
                    done(err);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function applyRules(node, msg, property,state,done) {
 | 
			
		||||
        if (!state) {
 | 
			
		||||
            if (node.rules.length === 0) {
 | 
			
		||||
                done(undefined, []);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            state = {
 | 
			
		||||
                currentRule: 0,
 | 
			
		||||
                elseflag: true,
 | 
			
		||||
                onward: [],
 | 
			
		||||
                hasParts: msg.hasOwnProperty("parts") &&
 | 
			
		||||
                                msg.parts.hasOwnProperty("id") &&
 | 
			
		||||
                                msg.parts.hasOwnProperty("index")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        applyRule(node,msg,property,state,(err,hasMore) => {
 | 
			
		||||
            if (err) {
 | 
			
		||||
                return done(err);
 | 
			
		||||
            }
 | 
			
		||||
            if (hasMore) {
 | 
			
		||||
                state.currentRule++;
 | 
			
		||||
                applyRules(node,msg,property,state,done);
 | 
			
		||||
            } else {
 | 
			
		||||
                node.previousValue = property;
 | 
			
		||||
                done(undefined,state.onward);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function SwitchNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this, n);
 | 
			
		||||
        this.rules = n.rules || [];
 | 
			
		||||
        this.property = n.property;
 | 
			
		||||
        this.propertyType = n.propertyType || "msg";
 | 
			
		||||
 | 
			
		||||
        if (this.propertyType === 'jsonata') {
 | 
			
		||||
            try {
 | 
			
		||||
                this.property = RED.util.prepareJSONataExpression(this.property,this);
 | 
			
		||||
            } catch(err) {
 | 
			
		||||
                this.error(RED._("switch.errors.invalid-expr",{error:err.message}));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.checkall = n.checkall || "true";
 | 
			
		||||
        this.previousValue = null;
 | 
			
		||||
        var node = this;
 | 
			
		||||
        var valid = true;
 | 
			
		||||
        var repair = n.repair;
 | 
			
		||||
        var needsCount = repair;
 | 
			
		||||
 | 
			
		||||
        for (var i=0; i<this.rules.length; i+=1) {
 | 
			
		||||
            var rule = this.rules[i];
 | 
			
		||||
            needsCount = needsCount || ((rule.t === "tail"));
 | 
			
		||||
            if (!rule.vt) {
 | 
			
		||||
                if (!isNaN(Number(rule.v))) {
 | 
			
		||||
                    rule.vt = 'num';
 | 
			
		||||
                } else {
 | 
			
		||||
                    rule.vt = 'str';
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (rule.vt === 'num') {
 | 
			
		||||
                if (!isNaN(Number(rule.v))) {
 | 
			
		||||
                    rule.v = Number(rule.v);
 | 
			
		||||
                }
 | 
			
		||||
            } else if (rule.vt === "jsonata") {
 | 
			
		||||
                try {
 | 
			
		||||
                    rule.v = RED.util.prepareJSONataExpression(rule.v,node);
 | 
			
		||||
                } catch(err) {
 | 
			
		||||
                    this.error(RED._("switch.errors.invalid-expr",{error:err.message}));
 | 
			
		||||
                    valid = false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (typeof rule.v2 !== 'undefined') {
 | 
			
		||||
                if (!rule.v2t) {
 | 
			
		||||
                    if (!isNaN(Number(rule.v2))) {
 | 
			
		||||
                        rule.v2t = 'num';
 | 
			
		||||
                    } else {
 | 
			
		||||
                        rule.v2t = 'str';
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (rule.v2t === 'num') {
 | 
			
		||||
                    rule.v2 = Number(rule.v2);
 | 
			
		||||
                } else if (rule.v2t === 'jsonata') {
 | 
			
		||||
                    try {
 | 
			
		||||
                        rule.v2 = RED.util.prepareJSONataExpression(rule.v2,node);
 | 
			
		||||
                    } catch(err) {
 | 
			
		||||
                        this.error(RED._("switch.errors.invalid-expr",{error:err.message}));
 | 
			
		||||
                        valid = false;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (!valid) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var pendingCount = 0;
 | 
			
		||||
        var pendingId = 0;
 | 
			
		||||
        var pendingIn = {};
 | 
			
		||||
        var pendingOut = {};
 | 
			
		||||
        var received = {};
 | 
			
		||||
 | 
			
		||||
        function addMessageToGroup(id, msg, parts) {
 | 
			
		||||
            if (!(id in pendingIn)) {
 | 
			
		||||
                pendingIn[id] = {
 | 
			
		||||
                    count: undefined,
 | 
			
		||||
                    msgs: [],
 | 
			
		||||
                    seq_no: pendingId++
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
            var group = pendingIn[id];
 | 
			
		||||
            group.msgs.push(msg);
 | 
			
		||||
            pendingCount++;
 | 
			
		||||
            var max_msgs = getMaxKeptCount();
 | 
			
		||||
            if ((max_msgs > 0) && (pendingCount > max_msgs)) {
 | 
			
		||||
                clearPending();
 | 
			
		||||
                node.error(RED._("switch.errors.too-many"), msg);
 | 
			
		||||
            }
 | 
			
		||||
            if (parts.hasOwnProperty("count")) {
 | 
			
		||||
                group.count = parts.count;
 | 
			
		||||
            }
 | 
			
		||||
            return group;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function drainMessageGroup(msgs,count,done) {
 | 
			
		||||
            var msg = msgs.shift();
 | 
			
		||||
            msg.parts.count = count;
 | 
			
		||||
            processMessage(msg,false, err => {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    done(err);
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (msgs.length === 0) {
 | 
			
		||||
                        done()
 | 
			
		||||
                    } else {
 | 
			
		||||
                        drainMessageGroup(msgs,count,done);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        function addMessageToPending(msg,done) {
 | 
			
		||||
            var parts = msg.parts;
 | 
			
		||||
            // We've already checked the msg.parts has the require bits
 | 
			
		||||
            var group = addMessageToGroup(parts.id, msg, parts);
 | 
			
		||||
            var msgs = group.msgs;
 | 
			
		||||
            var count = group.count;
 | 
			
		||||
            var msgsCount = msgs.length;
 | 
			
		||||
            if (count === msgsCount) {
 | 
			
		||||
                // We have a complete group - send the individual parts
 | 
			
		||||
                drainMessageGroup(msgs,count,err => {
 | 
			
		||||
                    pendingCount -= msgsCount;
 | 
			
		||||
                    delete pendingIn[parts.id];
 | 
			
		||||
                    done();
 | 
			
		||||
                })
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            done();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function sendGroup(onwards, port_count) {
 | 
			
		||||
            var counts = new Array(port_count).fill(0);
 | 
			
		||||
            for (var i = 0; i < onwards.length; i++) {
 | 
			
		||||
                var onward = onwards[i];
 | 
			
		||||
                for (var j = 0; j < port_count; j++) {
 | 
			
		||||
                    counts[j] += (onward[j] !== null) ? 1 : 0
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            var ids = new Array(port_count);
 | 
			
		||||
            for (var j = 0; j < port_count; j++) {
 | 
			
		||||
                ids[j] = RED.util.generateId();
 | 
			
		||||
            }
 | 
			
		||||
            var ports = new Array(port_count);
 | 
			
		||||
            var indexes = new Array(port_count).fill(0);
 | 
			
		||||
            for (var i = 0; i < onwards.length; i++) {
 | 
			
		||||
                var onward = onwards[i];
 | 
			
		||||
                for (var j = 0; j < port_count; j++) {
 | 
			
		||||
                    var msg = onward[j];
 | 
			
		||||
                    if (msg) {
 | 
			
		||||
                        var new_msg = RED.util.cloneMessage(msg);
 | 
			
		||||
                        var parts = new_msg.parts;
 | 
			
		||||
                        parts.id = ids[j];
 | 
			
		||||
                        parts.index = indexes[j];
 | 
			
		||||
                        parts.count = counts[j];
 | 
			
		||||
                        ports[j] = new_msg;
 | 
			
		||||
                        indexes[j]++;
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        ports[j] = null;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                node.send(ports);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function sendGroupMessages(onward, msg) {
 | 
			
		||||
            var parts = msg.parts;
 | 
			
		||||
            var gid = parts.id;
 | 
			
		||||
            received[gid] = ((gid in received) ? received[gid] : 0) +1;
 | 
			
		||||
            var send_ok = (received[gid] === parts.count);
 | 
			
		||||
 | 
			
		||||
            if (!(gid in pendingOut)) {
 | 
			
		||||
                pendingOut[gid] = {
 | 
			
		||||
                    onwards: []
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
            var group = pendingOut[gid];
 | 
			
		||||
            var onwards = group.onwards;
 | 
			
		||||
            onwards.push(onward);
 | 
			
		||||
            pendingCount++;
 | 
			
		||||
            if (send_ok) {
 | 
			
		||||
                sendGroup(onwards, onward.length, msg);
 | 
			
		||||
                pendingCount -= onward.length;
 | 
			
		||||
                delete pendingOut[gid];
 | 
			
		||||
                delete received[gid];
 | 
			
		||||
            }
 | 
			
		||||
            var max_msgs = getMaxKeptCount();
 | 
			
		||||
            if ((max_msgs > 0) && (pendingCount > max_msgs)) {
 | 
			
		||||
                clearPending();
 | 
			
		||||
                node.error(RED._("switch.errors.too-many"), msg);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function processMessage(msg, checkParts, done) {
 | 
			
		||||
            var hasParts = msg.hasOwnProperty("parts") &&
 | 
			
		||||
                            msg.parts.hasOwnProperty("id") &&
 | 
			
		||||
                            msg.parts.hasOwnProperty("index");
 | 
			
		||||
 | 
			
		||||
            if (needsCount && checkParts && hasParts) {
 | 
			
		||||
                addMessageToPending(msg,done);
 | 
			
		||||
            } else {
 | 
			
		||||
                getProperty(node,msg,(err,property) => {
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        node.warn(err);
 | 
			
		||||
                        done();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        applyRules(node,msg,property,undefined,(err,onward) => {
 | 
			
		||||
                            if (err) {
 | 
			
		||||
                                node.error(err, msg);
 | 
			
		||||
                            } else {
 | 
			
		||||
                                if (!repair || !hasParts) {
 | 
			
		||||
                                    node.send(onward);
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    sendGroupMessages(onward, msg);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            done();
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function clearPending() {
 | 
			
		||||
            pendingCount = 0;
 | 
			
		||||
            pendingId = 0;
 | 
			
		||||
            pendingIn = {};
 | 
			
		||||
            pendingOut = {};
 | 
			
		||||
            received = {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var pendingMessages = [];
 | 
			
		||||
        var handlingMessage = false;
 | 
			
		||||
        var processMessageQueue = function(msg) {
 | 
			
		||||
            if (msg) {
 | 
			
		||||
 | 
			
		||||
                // A new message has arrived - add it to the message queue
 | 
			
		||||
                pendingMessages.push(msg);
 | 
			
		||||
                if (handlingMessage) {
 | 
			
		||||
                    // The node is currently processing a message, so do nothing
 | 
			
		||||
                    // more with this message
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (pendingMessages.length === 0) {
 | 
			
		||||
                // There are no more messages to process, clear the active flag
 | 
			
		||||
                // and return
 | 
			
		||||
                handlingMessage = false;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // There are more messages to process. Get the next message and
 | 
			
		||||
            // start processing it. Recurse back in to check for any more
 | 
			
		||||
            var nextMsg = pendingMessages.shift();
 | 
			
		||||
            handlingMessage = true;
 | 
			
		||||
            processMessage(nextMsg,true,err => {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    node.error(err,nextMsg);
 | 
			
		||||
                }
 | 
			
		||||
                processMessageQueue()
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.on('input', function(msg) {
 | 
			
		||||
            processMessageQueue(msg);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.on('close', function() {
 | 
			
		||||
            clearPending();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType("switch", SwitchNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,367 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="change">
 | 
			
		||||
    <style>
 | 
			
		||||
        ol#node-input-rule-container .red-ui-typedInput-container {
 | 
			
		||||
            flex:1;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" style="width: calc(100% - 105px)"  data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" style="margin-bottom:0;">
 | 
			
		||||
        <label><i class="fa fa-list"></i> <span data-i18n="change.label.rules"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-input-rule-container-row">
 | 
			
		||||
        <ol id="node-input-rule-container"></ol>
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
(function() {
 | 
			
		||||
    function validateProperty(v,vt) {
 | 
			
		||||
        if (/msg|flow|global/.test(vt)) {
 | 
			
		||||
            if (!RED.utils.validatePropertyExpression(v)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (vt === "jsonata") {
 | 
			
		||||
            try{jsonata(v);}catch(e){return false;}
 | 
			
		||||
        } else if (vt === "json") {
 | 
			
		||||
            try{JSON.parse(v);}catch(e){return false;}
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType('change', {
 | 
			
		||||
        color: "#E2D96E",
 | 
			
		||||
        category: 'function',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            rules:{value:[{t:"set",p:"payload",pt:"msg",to:"",tot:"str"}],validate: function(rules) {
 | 
			
		||||
                if (!rules || rules.length === 0) { return true }
 | 
			
		||||
                for (var i=0;i<rules.length;i++) {
 | 
			
		||||
                    var r = rules[i];
 | 
			
		||||
                    if (r.t === 'set') {
 | 
			
		||||
                        if (!validateProperty(r.p,r.pt) || !validateProperty(r.to,r.tot)) {
 | 
			
		||||
                            return false;
 | 
			
		||||
                        }
 | 
			
		||||
                    } else if (r.t === 'change') {
 | 
			
		||||
                        if (!validateProperty(r.p,r.pt) || !validateProperty(r.from,r.fromt) || !validateProperty(r.to,r.tot)) {
 | 
			
		||||
                            return false;
 | 
			
		||||
                        }
 | 
			
		||||
                    } else if (r.t === 'move') {
 | 
			
		||||
                        if (!validateProperty(r.p,r.pt)) {
 | 
			
		||||
                            return false;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return true;
 | 
			
		||||
            }},
 | 
			
		||||
            // legacy
 | 
			
		||||
            action: {value:""},
 | 
			
		||||
            property: {value:""},
 | 
			
		||||
            from: {value:""},
 | 
			
		||||
            to: {value:""},
 | 
			
		||||
            reg: {value:false}
 | 
			
		||||
        },
 | 
			
		||||
        inputs: 1,
 | 
			
		||||
        outputs: 1,
 | 
			
		||||
        icon: "swap.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            function prop2name(type, key) {
 | 
			
		||||
                var result = RED.utils.parseContextKey(key);
 | 
			
		||||
                return type +"." +result.key;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.name) {
 | 
			
		||||
                return this.name;
 | 
			
		||||
            }
 | 
			
		||||
            if (!this.rules) {
 | 
			
		||||
                if (this.action === "replace") {
 | 
			
		||||
                    return this._("change.label.set",{property:"msg."+this.property});
 | 
			
		||||
                } else if (this.action === "change") {
 | 
			
		||||
                    return this._("change.label.change",{property:"msg."+this.property});
 | 
			
		||||
                } else if (this.action === "move") {
 | 
			
		||||
                    return this._("change.label.move",{property:"msg."+this.property});
 | 
			
		||||
                } else {
 | 
			
		||||
                    return this._("change.label.delete",{property:"msg."+this.property});
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if (this.rules.length == 1) {
 | 
			
		||||
                    if (this.rules[0].t === "set") {
 | 
			
		||||
                        return this._("change.label.set",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
 | 
			
		||||
                    } else if (this.rules[0].t === "change") {
 | 
			
		||||
                        return this._("change.label.change",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
 | 
			
		||||
                    } else if (this.rules[0].t === "move") {
 | 
			
		||||
                        return this._("change.label.move",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return this._("change.label.delete",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    return this._("change.label.changeCount",{count:this.rules.length});
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name ? "node_label_italic" : "";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var set = this._("change.action.set");
 | 
			
		||||
            var change = this._("change.action.change");
 | 
			
		||||
            var del = this._("change.action.delete");
 | 
			
		||||
            var move = this._("change.action.move");
 | 
			
		||||
            var to = this._("change.action.to");
 | 
			
		||||
            var toValueLabel = this._("change.action.toValue",to);
 | 
			
		||||
            var search = this._("change.action.search");
 | 
			
		||||
            var replace = this._("change.action.replace");
 | 
			
		||||
            var regex = this._("change.label.regex");
 | 
			
		||||
            var deepCopyLabel = this._("change.label.deepCopy");
 | 
			
		||||
 | 
			
		||||
            function createPropertyValue(row2_1, row2_2, defaultType) {
 | 
			
		||||
                var propValInput = $('<input/>',{class:"node-input-rule-property-value",type:"text"})
 | 
			
		||||
                    .appendTo(row2_1)
 | 
			
		||||
                    .typedInput({default:defaultType||'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']});
 | 
			
		||||
 | 
			
		||||
                var dcLabel = $('<label style="padding-left: 130px;"></label>').appendTo(row2_2);
 | 
			
		||||
                var deepCopy = $('<input type="checkbox" class="node-input-rule-property-deepCopy" style="width: auto; margin: 0 6px 0 0">').appendTo(dcLabel)
 | 
			
		||||
                $('<span>').text(deepCopyLabel).appendTo(dcLabel)
 | 
			
		||||
 | 
			
		||||
                propValInput.on("change", function(evt,type,val) {
 | 
			
		||||
                    row2_2.toggle(type === "msg" || type === "flow" || type === "global" || type === "env");
 | 
			
		||||
                })
 | 
			
		||||
                return [propValInput, deepCopy];
 | 
			
		||||
            }
 | 
			
		||||
            function createFromValue(row3_1, defaultType) {
 | 
			
		||||
                return $('<input/>',{class:"node-input-rule-property-search-value",type:"text"})
 | 
			
		||||
                .appendTo(row3_1)
 | 
			
		||||
                .typedInput({default:defaultType||'str',types:['msg','flow','global','str','re','num','bool','env']});
 | 
			
		||||
            }
 | 
			
		||||
            function createToValue(row3_2, defaultType) {
 | 
			
		||||
                return $('<input/>',{class:"node-input-rule-property-replace-value",type:"text"})
 | 
			
		||||
                .appendTo(row3_2)
 | 
			
		||||
                .typedInput({default:defaultType||'str',types:['msg','flow','global','str','num','bool','json','bin','env']});
 | 
			
		||||
            }
 | 
			
		||||
            function createMoveValue(row4, defaultType) {
 | 
			
		||||
                return $('<input/>',{class:"node-input-rule-property-move-value",type:"text"})
 | 
			
		||||
                .appendTo(row4)
 | 
			
		||||
                .typedInput({default:defaultType||'msg',types:['msg','flow','global']});
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $('#node-input-rule-container').css('min-height','150px').css('min-width','450px').editableList({
 | 
			
		||||
                addItem: function(container,i,opt) {
 | 
			
		||||
                    var rule = opt;
 | 
			
		||||
                    if (!rule.hasOwnProperty('t')) {
 | 
			
		||||
                        rule = {t:"set",p:"payload",to:"",tot:"str"};
 | 
			
		||||
                    }
 | 
			
		||||
                    if (rule.t === "change" && rule.re) {
 | 
			
		||||
                        rule.fromt = 're';
 | 
			
		||||
                        delete rule.re;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (rule.t === "set" && !rule.tot) {
 | 
			
		||||
                        if (rule.to.indexOf("msg.") === 0 && !rule.tot) {
 | 
			
		||||
                            rule.to = rule.to.substring(4);
 | 
			
		||||
                            rule.tot = "msg";
 | 
			
		||||
                        } else {
 | 
			
		||||
                            rule.tot = "str";
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (rule.t === "move" && !rule.tot) {
 | 
			
		||||
                        rule.tot = "msg";
 | 
			
		||||
                    }
 | 
			
		||||
                    container.css({
 | 
			
		||||
                        overflow: 'hidden',
 | 
			
		||||
                        whiteSpace: 'nowrap'
 | 
			
		||||
                    });
 | 
			
		||||
                    let fragment = document.createDocumentFragment();
 | 
			
		||||
                    var row1 = $('<div/>',{style:"display:flex; align-items: baseline"}).appendTo(fragment);
 | 
			
		||||
                    var row2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(fragment);
 | 
			
		||||
                    var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(fragment);
 | 
			
		||||
                    var row4 = $('<div/>',{style:"display:flex;margin-top:8px;align-items: baseline"}).appendTo(fragment);
 | 
			
		||||
 | 
			
		||||
                    var selectField = $('<select/>',{class:"node-input-rule-type",style:"width:110px; margin-right:10px;"}).appendTo(row1);
 | 
			
		||||
                    var selectOptions = [{v:"set",l:set},{v:"change",l:change},{v:"delete",l:del},{v:"move",l:move}];
 | 
			
		||||
                    for (var i=0; i<4; i++) {
 | 
			
		||||
                        selectField.append($("<option></option>").val(selectOptions[i].v).text(selectOptions[i].l));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var propertyName = $('<input/>',{class:"node-input-rule-property-name",type:"text"})
 | 
			
		||||
                        .appendTo(row1)
 | 
			
		||||
                        .typedInput({types:['msg','flow','global']});
 | 
			
		||||
 | 
			
		||||
                    var row2_1 = $('<div/>', {style:"display:flex;align-items: baseline"}).appendTo(row2);
 | 
			
		||||
                    $('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
 | 
			
		||||
                        .text(toValueLabel)
 | 
			
		||||
                        .appendTo(row2_1);
 | 
			
		||||
 | 
			
		||||
                    var row2_2 = $('<div/>', {style:"margin-top: 4px;"}).appendTo(row2);
 | 
			
		||||
 | 
			
		||||
                    var row3_1 = $('<div/>', {style:"display:flex;align-items: baseline"}).appendTo(row3);
 | 
			
		||||
                    $('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
 | 
			
		||||
                        .text(search)
 | 
			
		||||
                        .appendTo(row3_1);
 | 
			
		||||
 | 
			
		||||
                    var row3_2 = $('<div/>',{style:"display:flex;margin-top:8px;align-items: baseline"}).appendTo(row3);
 | 
			
		||||
                    $('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
 | 
			
		||||
                        .text(replace)
 | 
			
		||||
                        .appendTo(row3_2);
 | 
			
		||||
 | 
			
		||||
                    $('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
 | 
			
		||||
                        .text(to)
 | 
			
		||||
                        .appendTo(row4);
 | 
			
		||||
 | 
			
		||||
                    let propertyValue = null;
 | 
			
		||||
                    let fromValue = null;
 | 
			
		||||
                    let toValue = null;
 | 
			
		||||
                    let moveValue = null;
 | 
			
		||||
 | 
			
		||||
                    selectField.on("change", function() {
 | 
			
		||||
                        var type = $(this).val();
 | 
			
		||||
                        if (propertyValue) {
 | 
			
		||||
                            propertyValue.typedInput('hide');
 | 
			
		||||
                        }
 | 
			
		||||
                        if (fromValue) {
 | 
			
		||||
                            fromValue.typedInput('hide');
 | 
			
		||||
                        }
 | 
			
		||||
                        if (toValue) {
 | 
			
		||||
                            toValue.typedInput('hide');
 | 
			
		||||
                        }
 | 
			
		||||
                        if (moveValue) {
 | 
			
		||||
                            moveValue.typedInput('hide');
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (type == "set") {
 | 
			
		||||
                            if(!propertyValue) {
 | 
			
		||||
                                var parts = createPropertyValue(row2_1, row2_2);
 | 
			
		||||
                                propertyValue = parts[0];
 | 
			
		||||
                                deepCopy = parts[1];
 | 
			
		||||
                            }
 | 
			
		||||
                            propertyValue.typedInput('show');
 | 
			
		||||
                            row2.show();
 | 
			
		||||
                            row3.hide();
 | 
			
		||||
                            row4.hide();
 | 
			
		||||
                        } else if (type == "change") {
 | 
			
		||||
                            if(!fromValue) {
 | 
			
		||||
                                fromValue = createFromValue(row3_1);
 | 
			
		||||
                            }
 | 
			
		||||
                            fromValue.typedInput('show');
 | 
			
		||||
                            if(!toValue) {
 | 
			
		||||
                                toValue = createToValue(row3_2);
 | 
			
		||||
                            }
 | 
			
		||||
                            toValue.typedInput('show');
 | 
			
		||||
                            row2.hide();
 | 
			
		||||
                            row3.show();
 | 
			
		||||
                            row4.hide();
 | 
			
		||||
                        } else if (type == "delete") {
 | 
			
		||||
                            row2.hide();
 | 
			
		||||
                            row3.hide();
 | 
			
		||||
                            row4.hide();
 | 
			
		||||
                        } else if (type == "move") {
 | 
			
		||||
                            if(!moveValue) {
 | 
			
		||||
                                moveValue = createMoveValue(row4);
 | 
			
		||||
                            }
 | 
			
		||||
                            moveValue.typedInput('show');
 | 
			
		||||
                            row2.hide();
 | 
			
		||||
                            row3.hide();
 | 
			
		||||
                            row4.show();
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    selectField.val(rule.t);
 | 
			
		||||
                    propertyName.typedInput('value',rule.p);
 | 
			
		||||
                    propertyName.typedInput('type',rule.pt);
 | 
			
		||||
                    if (rule.t == "set") {
 | 
			
		||||
                        var parts = createPropertyValue(row2_1, row2_2, rule.tot);
 | 
			
		||||
                        propertyValue = parts[0];
 | 
			
		||||
                        deepCopy = parts[1];
 | 
			
		||||
                        propertyValue.typedInput('value',rule.to);
 | 
			
		||||
                        deepCopy.prop("checked", !!rule.dc);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (rule.t == "move") {
 | 
			
		||||
                        moveValue = createMoveValue(row4,rule.tot);
 | 
			
		||||
                        moveValue.typedInput('value',rule.to);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (rule.t == "change") {
 | 
			
		||||
                        fromValue = createFromValue(row3_1, rule.fromt);
 | 
			
		||||
                        fromValue.typedInput('value',rule.from);
 | 
			
		||||
 | 
			
		||||
                        toValue = createToValue(row3_2,rule.tot);
 | 
			
		||||
                        toValue.typedInput('value',rule.to);
 | 
			
		||||
                    }
 | 
			
		||||
                    selectField.change();
 | 
			
		||||
                    container[0].appendChild(fragment);
 | 
			
		||||
                },
 | 
			
		||||
                removable: true,
 | 
			
		||||
                sortable: true
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (!this.rules) {
 | 
			
		||||
                var rule = {
 | 
			
		||||
                    t:(this.action=="replace"?"set":this.action),
 | 
			
		||||
                    p:this.property,
 | 
			
		||||
                    pt:"msg"
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                if ((rule.t === "set")||(rule.t === "move")) {
 | 
			
		||||
                    rule.to = this.to;
 | 
			
		||||
                } else if (rule.t === "change") {
 | 
			
		||||
                    rule.from = this.from;
 | 
			
		||||
                    rule.to = this.to;
 | 
			
		||||
                    rule.re = this.reg;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                delete this.to;
 | 
			
		||||
                delete this.from;
 | 
			
		||||
                delete this.reg;
 | 
			
		||||
                delete this.action;
 | 
			
		||||
                delete this.property;
 | 
			
		||||
 | 
			
		||||
                this.rules = [rule];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (var i=0; i<this.rules.length; i++) {
 | 
			
		||||
                var rule = this.rules[i];
 | 
			
		||||
                $("#node-input-rule-container").editableList('addItem',rule);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            var rules = $("#node-input-rule-container").editableList('items');
 | 
			
		||||
            var node = this;
 | 
			
		||||
            node.rules= [];
 | 
			
		||||
            rules.each(function(i) {
 | 
			
		||||
                var rule = $(this);
 | 
			
		||||
                var type = rule.find(".node-input-rule-type").val();
 | 
			
		||||
                var r = {
 | 
			
		||||
                    t:type,
 | 
			
		||||
                    p:rule.find(".node-input-rule-property-name").typedInput('value'),
 | 
			
		||||
                    pt:rule.find(".node-input-rule-property-name").typedInput('type')
 | 
			
		||||
                };
 | 
			
		||||
                if (type === "set") {
 | 
			
		||||
                    r.to = rule.find(".node-input-rule-property-value").typedInput('value');
 | 
			
		||||
                    r.tot = rule.find(".node-input-rule-property-value").typedInput('type');
 | 
			
		||||
                    if (rule.find(".node-input-rule-property-deepCopy").prop("checked")) {
 | 
			
		||||
                        r.dc = true;
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (type === "move") {
 | 
			
		||||
                    r.to = rule.find(".node-input-rule-property-move-value").typedInput('value');
 | 
			
		||||
                    r.tot = rule.find(".node-input-rule-property-move-value").typedInput('type');
 | 
			
		||||
                } else if (type === "change") {
 | 
			
		||||
                    r.from = rule.find(".node-input-rule-property-search-value").typedInput('value');
 | 
			
		||||
                    r.fromt = rule.find(".node-input-rule-property-search-value").typedInput('type');
 | 
			
		||||
                    r.to = rule.find(".node-input-rule-property-replace-value").typedInput('value');
 | 
			
		||||
                    r.tot = rule.find(".node-input-rule-property-replace-value").typedInput('type');
 | 
			
		||||
                }
 | 
			
		||||
                node.rules.push(r);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        oneditresize: function(size) {
 | 
			
		||||
            var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
 | 
			
		||||
            var height = size.height;
 | 
			
		||||
            for (var i=0; i<rows.length; i++) {
 | 
			
		||||
                height -= $(rows[i]).outerHeight(true);
 | 
			
		||||
            }
 | 
			
		||||
            var editorRow = $("#dialog-form>div.node-input-rule-container-row");
 | 
			
		||||
            height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
 | 
			
		||||
            height += 16;
 | 
			
		||||
            $("#node-input-rule-container").editableList('height',height);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
})();
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,357 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    function ChangeNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this, n);
 | 
			
		||||
        var node = this;
 | 
			
		||||
 | 
			
		||||
        this.rules = n.rules;
 | 
			
		||||
        var rule;
 | 
			
		||||
        if (!this.rules) {
 | 
			
		||||
            rule = {
 | 
			
		||||
                t:(n.action=="replace"?"set":n.action),
 | 
			
		||||
                p:n.property||""
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ((rule.t === "set")||(rule.t === "move")) {
 | 
			
		||||
                rule.to = n.to||"";
 | 
			
		||||
            } else if (rule.t === "change") {
 | 
			
		||||
                rule.from = n.from||"";
 | 
			
		||||
                rule.to = n.to||"";
 | 
			
		||||
                rule.re = (n.reg===null||n.reg);
 | 
			
		||||
            }
 | 
			
		||||
            this.rules = [rule];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var valid = true;
 | 
			
		||||
        for (var i=0;i<this.rules.length;i++) {
 | 
			
		||||
            rule = this.rules[i];
 | 
			
		||||
            // Migrate to type-aware rules
 | 
			
		||||
            if (!rule.pt) {
 | 
			
		||||
                rule.pt = "msg";
 | 
			
		||||
            }
 | 
			
		||||
            if (rule.t === "change" && rule.re) {
 | 
			
		||||
                rule.fromt = 're';
 | 
			
		||||
                delete rule.re;
 | 
			
		||||
            }
 | 
			
		||||
            if (rule.t === "set" && !rule.tot) {
 | 
			
		||||
                if (rule.to.indexOf("msg.") === 0 && !rule.tot) {
 | 
			
		||||
                    rule.to = rule.to.substring(4);
 | 
			
		||||
                    rule.tot = "msg";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (!rule.tot) {
 | 
			
		||||
                rule.tot = "str";
 | 
			
		||||
            }
 | 
			
		||||
            if (!rule.fromt) {
 | 
			
		||||
                rule.fromt = "str";
 | 
			
		||||
            }
 | 
			
		||||
            if (rule.t === "change" && rule.fromt !== 'msg' && rule.fromt !== 'flow' && rule.fromt !== 'global') {
 | 
			
		||||
                rule.fromRE = rule.from;
 | 
			
		||||
                if (rule.fromt !== 're') {
 | 
			
		||||
                    rule.fromRE = rule.fromRE.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
 | 
			
		||||
                }
 | 
			
		||||
                try {
 | 
			
		||||
                    rule.fromRE = new RegExp(rule.fromRE, "g");
 | 
			
		||||
                } catch (e) {
 | 
			
		||||
                    valid = false;
 | 
			
		||||
                    this.error(RED._("change.errors.invalid-from",{error:e.message}));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (rule.tot === 'num') {
 | 
			
		||||
                rule.to = Number(rule.to);
 | 
			
		||||
            } else if (rule.tot === 'json' || rule.tot === 'bin') {
 | 
			
		||||
                try {
 | 
			
		||||
                    // check this is parsable JSON
 | 
			
		||||
                    JSON.parse(rule.to);
 | 
			
		||||
                } catch(e2) {
 | 
			
		||||
                    valid = false;
 | 
			
		||||
                    this.error(RED._("change.errors.invalid-json"));
 | 
			
		||||
                }
 | 
			
		||||
            } else if (rule.tot === 'bool') {
 | 
			
		||||
                rule.to = /^true$/i.test(rule.to);
 | 
			
		||||
            } else if (rule.tot === 'jsonata') {
 | 
			
		||||
                try {
 | 
			
		||||
                    rule.to = RED.util.prepareJSONataExpression(rule.to,this);
 | 
			
		||||
                } catch(e) {
 | 
			
		||||
                    valid = false;
 | 
			
		||||
                    this.error(RED._("change.errors.invalid-expr",{error:e.message}));
 | 
			
		||||
                }
 | 
			
		||||
            } else if (rule.tot === 'env') {
 | 
			
		||||
                rule.to = RED.util.evaluateNodeProperty(rule.to,'env',node);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function getToValue(msg,rule,done) {
 | 
			
		||||
            var value = rule.to;
 | 
			
		||||
            if (rule.tot === 'json') {
 | 
			
		||||
                value = JSON.parse(rule.to);
 | 
			
		||||
            } else if (rule.tot === 'bin') {
 | 
			
		||||
                value = Buffer.from(JSON.parse(rule.to))
 | 
			
		||||
            }
 | 
			
		||||
            if (rule.tot === "msg") {
 | 
			
		||||
                value = RED.util.getMessageProperty(msg,rule.to);
 | 
			
		||||
            } else if ((rule.tot === 'flow') || (rule.tot === 'global')) {
 | 
			
		||||
                RED.util.evaluateNodeProperty(rule.to, rule.tot, node, msg, (err,value) => {
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        done(undefined,undefined);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        done(undefined,value);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                return
 | 
			
		||||
            } else if (rule.tot === 'date') {
 | 
			
		||||
                value = Date.now();
 | 
			
		||||
            } else if (rule.tot === 'jsonata') {
 | 
			
		||||
                RED.util.evaluateJSONataExpression(rule.to,msg, (err, value) => {
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        done(RED._("change.errors.invalid-expr",{error:err.message}))
 | 
			
		||||
                    } else {
 | 
			
		||||
                        done(undefined, value);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            done(undefined,value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function getFromValueType(fromValue, done) {
 | 
			
		||||
            var fromType;
 | 
			
		||||
            var fromRE;
 | 
			
		||||
            if (typeof fromValue === 'number' || fromValue instanceof Number) {
 | 
			
		||||
                fromType = 'num';
 | 
			
		||||
            } else if (typeof fromValue === 'boolean') {
 | 
			
		||||
                fromType = 'bool'
 | 
			
		||||
            } else if (fromValue instanceof RegExp) {
 | 
			
		||||
                fromType = 're';
 | 
			
		||||
                fromRE = fromValue;
 | 
			
		||||
            } else if (typeof fromValue === 'string') {
 | 
			
		||||
                fromType = 'str';
 | 
			
		||||
                fromRE = fromValue.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
 | 
			
		||||
                try {
 | 
			
		||||
                    fromRE = new RegExp(fromRE, "g");
 | 
			
		||||
                } catch (e) {
 | 
			
		||||
                    done(new Error(RED._("change.errors.invalid-from",{error:e.message})));
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                done(new Error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)})));
 | 
			
		||||
            }
 | 
			
		||||
            done(undefined,{
 | 
			
		||||
                fromType,
 | 
			
		||||
                fromValue,
 | 
			
		||||
                fromRE
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        function getFromValue(msg,rule, done) {
 | 
			
		||||
            var fromValue;
 | 
			
		||||
            var fromType;
 | 
			
		||||
            var fromRE;
 | 
			
		||||
            if (rule.t === 'change') {
 | 
			
		||||
                if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {
 | 
			
		||||
                    if (rule.fromt === "msg") {
 | 
			
		||||
                        return getFromValueType(RED.util.getMessageProperty(msg,rule.from),done);
 | 
			
		||||
                    } else if (rule.fromt === 'flow' || rule.fromt === 'global') {
 | 
			
		||||
                        var contextKey = RED.util.parseContextStore(rule.from);
 | 
			
		||||
                        if (/\[msg\./.test(context.key)) {
 | 
			
		||||
                            // The key has a nest msg. reference to evaluate first
 | 
			
		||||
                            context.key = RED.util.normalisePropertyExpression(contextKey.key,msg,true);
 | 
			
		||||
                        }
 | 
			
		||||
                        node.context()[rule.fromt].get(contextKey.key, contextKey.store, (err,fromValue) => {
 | 
			
		||||
                            if (err) {
 | 
			
		||||
                                done(err)
 | 
			
		||||
                            } else {
 | 
			
		||||
                                getFromValueType(fromValue,done);
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    fromType = rule.fromt;
 | 
			
		||||
                    fromValue = rule.from;
 | 
			
		||||
                    fromRE = rule.fromRE;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            done(undefined, {
 | 
			
		||||
                fromType,
 | 
			
		||||
                fromValue,
 | 
			
		||||
                fromRE
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        function applyRule(msg,rule,done) {
 | 
			
		||||
            var property = rule.p;
 | 
			
		||||
            var current;
 | 
			
		||||
            var fromValue;
 | 
			
		||||
            var fromType;
 | 
			
		||||
            var fromRE;
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                getToValue(msg,rule,(err,value) => {
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        node.error(err, msg);
 | 
			
		||||
                        return done(undefined,null);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (rule.dc) {
 | 
			
		||||
                            value = RED.util.cloneMessage(value);
 | 
			
		||||
                        }
 | 
			
		||||
                        getFromValue(msg,rule,(err,fromParts) => {
 | 
			
		||||
                            if (err) {
 | 
			
		||||
                                node.error(err, msg);
 | 
			
		||||
                                return done(undefined,null);
 | 
			
		||||
                            } else {
 | 
			
		||||
                                fromValue = fromParts.fromValue;
 | 
			
		||||
                                fromType = fromParts.fromType;
 | 
			
		||||
                                fromRE = fromParts.fromRE;
 | 
			
		||||
                                if (rule.pt === 'msg') {
 | 
			
		||||
                                    try {
 | 
			
		||||
                                        if (rule.t === 'delete') {
 | 
			
		||||
                                            RED.util.setMessageProperty(msg,property,undefined);
 | 
			
		||||
                                        } else if (rule.t === 'set') {
 | 
			
		||||
                                            if (!RED.util.setMessageProperty(msg,property,value)) {
 | 
			
		||||
                                                node.warn(RED._("change.errors.no-override",{property:property}));
 | 
			
		||||
                                            }
 | 
			
		||||
                                        } else if (rule.t === 'change') {
 | 
			
		||||
                                            current = RED.util.getMessageProperty(msg,property);
 | 
			
		||||
                                            if (typeof current === 'string') {
 | 
			
		||||
                                                if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
 | 
			
		||||
                                                    // str representation of exact from number/boolean
 | 
			
		||||
                                                    // only replace if they match exactly
 | 
			
		||||
                                                    RED.util.setMessageProperty(msg,property,value);
 | 
			
		||||
                                                } else {
 | 
			
		||||
                                                    current = current.replace(fromRE,value);
 | 
			
		||||
                                                    RED.util.setMessageProperty(msg,property,current);
 | 
			
		||||
                                                }
 | 
			
		||||
                                            } else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
 | 
			
		||||
                                                if (current == Number(fromValue)) {
 | 
			
		||||
                                                    RED.util.setMessageProperty(msg,property,value);
 | 
			
		||||
                                                }
 | 
			
		||||
                                            } else if (typeof current === 'boolean' && fromType === 'bool') {
 | 
			
		||||
                                                if (current.toString() === fromValue) {
 | 
			
		||||
                                                    RED.util.setMessageProperty(msg,property,value);
 | 
			
		||||
                                                }
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                    } catch(err) {}
 | 
			
		||||
                                    return done(undefined,msg);
 | 
			
		||||
                                } else if (rule.pt === 'flow' || rule.pt === 'global') {
 | 
			
		||||
                                    var contextKey = RED.util.parseContextStore(property);
 | 
			
		||||
                                    if (/\[msg/.test(contextKey.key)) {
 | 
			
		||||
                                        // The key has a nest msg. reference to evaluate first
 | 
			
		||||
                                        contextKey.key = RED.util.normalisePropertyExpression(contextKey.key, msg, true)
 | 
			
		||||
                                    }
 | 
			
		||||
                                    var target = node.context()[rule.pt];
 | 
			
		||||
                                    var callback = err => {
 | 
			
		||||
                                        if (err) {
 | 
			
		||||
                                            node.error(err, msg);
 | 
			
		||||
                                            return done(undefined,null);
 | 
			
		||||
                                        } else {
 | 
			
		||||
                                            done(undefined,msg);
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (rule.t === 'delete') {
 | 
			
		||||
                                        target.set(contextKey.key,undefined,contextKey.store,callback);
 | 
			
		||||
                                    } else if (rule.t === 'set') {
 | 
			
		||||
                                        target.set(contextKey.key,value,contextKey.store,callback);
 | 
			
		||||
                                    } else if (rule.t === 'change') {
 | 
			
		||||
                                        target.get(contextKey.key,contextKey.store,(err,current) => {
 | 
			
		||||
                                            if (err) {
 | 
			
		||||
                                                node.error(err, msg);
 | 
			
		||||
                                                return done(undefined,null);
 | 
			
		||||
                                            }
 | 
			
		||||
                                            if (typeof current === 'string') {
 | 
			
		||||
                                                if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
 | 
			
		||||
                                                    // str representation of exact from number/boolean
 | 
			
		||||
                                                    // only replace if they match exactly
 | 
			
		||||
                                                    target.set(contextKey.key,value,contextKey.store,callback);
 | 
			
		||||
                                                } else {
 | 
			
		||||
                                                    current = current.replace(fromRE,value);
 | 
			
		||||
                                                    target.set(contextKey.key,current,contextKey.store,callback);
 | 
			
		||||
                                                }
 | 
			
		||||
                                            } else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
 | 
			
		||||
                                                if (current == Number(fromValue)) {
 | 
			
		||||
                                                    target.set(contextKey.key,value,contextKey.store,callback);
 | 
			
		||||
                                                }
 | 
			
		||||
                                            } else if (typeof current === 'boolean' && fromType === 'bool') {
 | 
			
		||||
                                                if (current.toString() === fromValue) {
 | 
			
		||||
                                                    target.set(contextKey.key,value,contextKey.store,callback);
 | 
			
		||||
                                                }
 | 
			
		||||
                                            }
 | 
			
		||||
                                        });
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            } catch(err) {
 | 
			
		||||
                // This is an okay error
 | 
			
		||||
                done(undefined,msg);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        function completeApplyingRules(msg,currentRule,done) {
 | 
			
		||||
            if (!msg) {
 | 
			
		||||
                return done();
 | 
			
		||||
            } else if (currentRule === node.rules.length - 1) {
 | 
			
		||||
                return done(undefined, msg);
 | 
			
		||||
            } else {
 | 
			
		||||
                applyRules(msg, currentRule+1,done);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        function applyRules(msg, currentRule, done) {
 | 
			
		||||
            if (currentRule >= node.rules.length) {
 | 
			
		||||
                return done(undefined,msg);
 | 
			
		||||
            }
 | 
			
		||||
            var r = node.rules[currentRule];
 | 
			
		||||
            if (r.t === "move") {
 | 
			
		||||
                if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1)) {
 | 
			
		||||
                    applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt},(err,msg) => {
 | 
			
		||||
                        applyRule(msg,{t:"delete", p:r.p, pt:r.pt}, (err,msg) => {
 | 
			
		||||
                            completeApplyingRules(msg,currentRule,done);
 | 
			
		||||
                        })
 | 
			
		||||
                    });
 | 
			
		||||
                } else { // 2 step move if we are moving from a child
 | 
			
		||||
                    applyRule(msg,{t:"set", p:"_temp_move", pt:r.tot, to:r.p, tot:r.pt},(err,msg)=> {
 | 
			
		||||
                        applyRule(msg,{t:"delete", p:r.p, pt:r.pt},(err,msg)=> {
 | 
			
		||||
                            applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:"_temp_move", tot:r.pt},(err,msg)=> {
 | 
			
		||||
                                applyRule(msg,{t:"delete", p:"_temp_move", pt:r.pt},(err,msg)=> {
 | 
			
		||||
                                    completeApplyingRules(msg,currentRule,done);
 | 
			
		||||
                                });
 | 
			
		||||
                            });
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                applyRule(msg,r,(err,msg)=> { completeApplyingRules(msg,currentRule,done); });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (valid) {
 | 
			
		||||
            this.on('input', function(msg, send, done) {
 | 
			
		||||
                applyRules(msg, 0, (err,msg) => {
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        done(err);
 | 
			
		||||
                    } else if (msg) {
 | 
			
		||||
                        send(msg);
 | 
			
		||||
                        done();
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("change", ChangeNode);
 | 
			
		||||
};
 | 
			
		||||
@@ -1,70 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="range">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-property" style="width:calc(70% - 1px)"/>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-action"><i class="fa fa-dot-circle-o"></i> <span data-i18n="range.label.action"></span></label>
 | 
			
		||||
        <select id="node-input-action" style="width:70%;">
 | 
			
		||||
            <option value="scale" data-i18n="range.scale.payload"></option>
 | 
			
		||||
            <option value="clamp" data-i18n="range.scale.limit"></option>
 | 
			
		||||
            <option value="roll" data-i18n="range.scale.wrap"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <br/>
 | 
			
		||||
    <div class="form-row"><i class="fa fa-sign-in"></i> <span data-i18n="range.label.inputrange"></span>:</div>
 | 
			
		||||
    <div class="form-row"><label></label>
 | 
			
		||||
        <span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minin" data-i18n="[placeholder]range.placeholder.min" style="width:100px;"/>
 | 
			
		||||
          <span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxin" data-i18n="[placeholder]range.placeholder.maxin" style="width:100px;"/>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row"><i class="fa fa-sign-out"></i> <span data-i18n="range.label.resultrange"></span>:</div>
 | 
			
		||||
    <div class="form-row"><label></label>
 | 
			
		||||
        <span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minout" data-i18n="[placeholder]range.placeholder.min" style="width:100px;"/>
 | 
			
		||||
          <span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxout" data-i18n="[placeholder]range.placeholder.maxout" style="width:100px;"/>
 | 
			
		||||
    </div>
 | 
			
		||||
    <br/>
 | 
			
		||||
    <div class="form-row"><label></label>
 | 
			
		||||
        <input type="checkbox" id="node-input-round" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label style="width: auto;" for="node-input-round"><span data-i18n="range.label.roundresult"></span></label></input>
 | 
			
		||||
    </div>
 | 
			
		||||
    <br/>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-tips" id="node-tip"><span data-i18n="range.tip"></span></div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('range', {
 | 
			
		||||
        color: "#E2D96E",
 | 
			
		||||
        category: 'function',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            minin: {value:"",required:true,validate:RED.validators.number()},
 | 
			
		||||
            maxin: {value:"",required:true,validate:RED.validators.number()},
 | 
			
		||||
            minout: {value:"",required:true,validate:RED.validators.number()},
 | 
			
		||||
            maxout: {value:"",required:true,validate:RED.validators.number()},
 | 
			
		||||
            action: {value:"scale"},
 | 
			
		||||
            round: {value:false},
 | 
			
		||||
            property: {value:"payload",required:true},
 | 
			
		||||
            name: {value:""}
 | 
			
		||||
        },
 | 
			
		||||
        inputs: 1,
 | 
			
		||||
        outputs: 1,
 | 
			
		||||
        icon: "range.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            if (this.minout !== "" && this.maxout !== "") { return this.name||this.minout + " - " + this.maxout; }
 | 
			
		||||
            else { return this.name||this._("range.range"); }
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name ? "node_label_italic" : "";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            if (this.property === undefined) {
 | 
			
		||||
                $("#node-input-property").val("payload");
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-property").typedInput({default:'msg',types:['msg']});
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,55 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    function RangeNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this, n);
 | 
			
		||||
        this.action = n.action;
 | 
			
		||||
        this.round = n.round || false;
 | 
			
		||||
        this.minin = Number(n.minin);
 | 
			
		||||
        this.maxin = Number(n.maxin);
 | 
			
		||||
        this.minout = Number(n.minout);
 | 
			
		||||
        this.maxout = Number(n.maxout);
 | 
			
		||||
        this.property = n.property||"payload";
 | 
			
		||||
        var node = this;
 | 
			
		||||
 | 
			
		||||
        this.on('input', function (msg, send, done) {
 | 
			
		||||
            var value = RED.util.getMessageProperty(msg,node.property);
 | 
			
		||||
            if (value !== undefined) {
 | 
			
		||||
                var n = Number(value);
 | 
			
		||||
                if (!isNaN(n)) {
 | 
			
		||||
                    if (node.action == "clamp") {
 | 
			
		||||
                        if (n < node.minin) { n = node.minin; }
 | 
			
		||||
                        if (n > node.maxin) { n = node.maxin; }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (node.action == "roll") {
 | 
			
		||||
                        var divisor = node.maxin - node.minin;
 | 
			
		||||
                        n = ((n - node.minin) % divisor + divisor) % divisor + node.minin;
 | 
			
		||||
                    }
 | 
			
		||||
                    value = ((n - node.minin) / (node.maxin - node.minin) * (node.maxout - node.minout)) + node.minout;
 | 
			
		||||
                    if (node.round) { value = Math.round(value); }
 | 
			
		||||
                    RED.util.setMessageProperty(msg,node.property,value);
 | 
			
		||||
                    send(msg);
 | 
			
		||||
                }
 | 
			
		||||
                else { node.log(RED._("range.errors.notnumber")+": "+value); }
 | 
			
		||||
            }
 | 
			
		||||
            else { send(msg); } // If no payload - just pass it on.
 | 
			
		||||
            done();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("range", RangeNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,155 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="template">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-field"><i class="fa fa-ellipsis-h"></i> <span data-i18n="template.label.property"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-field" placeholder="payload" style="width:250px;">
 | 
			
		||||
        <input type="hidden" id="node-input-fieldType">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" style="position: relative; margin-bottom: 0px;">
 | 
			
		||||
        <label for="node-input-template"><i class="fa fa-file-code-o"></i> <span data-i18n="template.label.template"></span></label>
 | 
			
		||||
        <input type="hidden" id="node-input-template" autofocus="autofocus">
 | 
			
		||||
        <div style="position: absolute; right:0;display:inline-block; text-align: right; font-size: 0.8em;">
 | 
			
		||||
            <span data-i18n="template.label.format"></span>:
 | 
			
		||||
            <select id="node-input-format" style="width:110px; font-size: 10px !important;  height: 24px; padding:0;">
 | 
			
		||||
                <option value="handlebars">mustache</option>
 | 
			
		||||
                <option value="html">HTML</option>
 | 
			
		||||
                <option value="json">JSON</option>
 | 
			
		||||
                <option value="javascript">Javascript</option>
 | 
			
		||||
                <option value="css">CSS</option>
 | 
			
		||||
                <option value="markdown">Markdown</option>
 | 
			
		||||
                <option value="python">Python</option>
 | 
			
		||||
                <option value="sql">SQL</option>
 | 
			
		||||
                <option value="yaml">YAML</option>
 | 
			
		||||
                <option value="text" data-i18n="template.label.none"></option>
 | 
			
		||||
            </select>
 | 
			
		||||
            <button id="node-template-expand-editor" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-text-editor-row">
 | 
			
		||||
        <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-template-editor" ></div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-syntax"><i class="fa fa-code"></i> <span data-i18n="template.label.syntax"></span></label>
 | 
			
		||||
        <select id="node-input-syntax" style="width:180px;">
 | 
			
		||||
            <option value="mustache" data-i18n="template.label.mustache"></option>
 | 
			
		||||
            <option value="plain" data-i18n="template.label.plain"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-output"><i class="fa fa-long-arrow-right"></i> <span data-i18n="template.label.output"></span></label>
 | 
			
		||||
        <select id="node-input-output" style="width:180px;">
 | 
			
		||||
            <option value="str" data-i18n="template.label.plain"></option>
 | 
			
		||||
            <option value="json" data-i18n="template.label.json"></option>
 | 
			
		||||
            <option value="yaml" data-i18n="template.label.yaml"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('template',{
 | 
			
		||||
        color:"rgb(243, 181, 103)",
 | 
			
		||||
        category: 'function',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            field: {value:"payload", validate:RED.validators.typedInput("fieldType")},
 | 
			
		||||
            fieldType: {value:"msg"},
 | 
			
		||||
            format: {value:"handlebars"},
 | 
			
		||||
            syntax: {value:"mustache"},
 | 
			
		||||
            template: {value:"This is the payload: {{payload}} !"},
 | 
			
		||||
            output: {value:"str"}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "template.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||this._("template.template");;
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var that = this;
 | 
			
		||||
            if (!this.field) {
 | 
			
		||||
                this.field = 'payload';
 | 
			
		||||
                $("#node-input-field").val("payload");
 | 
			
		||||
            }
 | 
			
		||||
            if (!this.fieldType) {
 | 
			
		||||
                this.fieldType = 'msg';
 | 
			
		||||
            }
 | 
			
		||||
            if (!this.syntax) {
 | 
			
		||||
                this.syntax = 'mustache';
 | 
			
		||||
                $("#node-input-syntax").val(this.syntax);
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-field").typedInput({
 | 
			
		||||
                default: 'msg',
 | 
			
		||||
                types: ['msg','flow','global'],
 | 
			
		||||
                typeField: $("#node-input-fieldType")
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            this.editor = RED.editor.createEditor({
 | 
			
		||||
                id: 'node-input-template-editor',
 | 
			
		||||
                mode: 'ace/mode/html',
 | 
			
		||||
                value: $("#node-input-template").val()
 | 
			
		||||
            });
 | 
			
		||||
            RED.library.create({
 | 
			
		||||
                url:"templates", // where to get the data from
 | 
			
		||||
                type:"template", // the type of object the library is for
 | 
			
		||||
                editor:that.editor, // the field name the main text body goes to
 | 
			
		||||
                fields:['name','format','output','syntax'],
 | 
			
		||||
                ext: "txt"
 | 
			
		||||
            });
 | 
			
		||||
            this.editor.focus();
 | 
			
		||||
 | 
			
		||||
            $("#node-input-format").on("change", function() {
 | 
			
		||||
                var mod = "ace/mode/"+$("#node-input-format").val();
 | 
			
		||||
                that.editor.getSession().setMode({
 | 
			
		||||
                    path: mod,
 | 
			
		||||
                    v: Date.now()
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
            RED.popover.tooltip($("#node-template-expand-editor"), RED._("node-red:common.label.expand"));
 | 
			
		||||
            $("#node-template-expand-editor").on("click", function(e) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                var value = that.editor.getValue();
 | 
			
		||||
                RED.editor.editText({
 | 
			
		||||
                    mode: $("#node-input-format").val(),
 | 
			
		||||
                    value: value,
 | 
			
		||||
                    width: "Infinity",
 | 
			
		||||
                    cursor: that.editor.getCursorPosition(),
 | 
			
		||||
                    complete: function(v,cursor) {
 | 
			
		||||
                        that.editor.setValue(v, -1);
 | 
			
		||||
                        that.editor.gotoLine(cursor.row+1,cursor.column,false);
 | 
			
		||||
                        setTimeout(function() {
 | 
			
		||||
                            that.editor.focus();
 | 
			
		||||
                        },300);
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            })
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            $("#node-input-template").val(this.editor.getValue());
 | 
			
		||||
            this.editor.destroy();
 | 
			
		||||
            delete this.editor;
 | 
			
		||||
        },
 | 
			
		||||
        oneditcancel: function() {
 | 
			
		||||
            this.editor.destroy();
 | 
			
		||||
            delete this.editor;
 | 
			
		||||
        },
 | 
			
		||||
        oneditresize: function(size) {
 | 
			
		||||
            var rows = $("#dialog-form>div:not(.node-text-editor-row)");
 | 
			
		||||
            var height = $("#dialog-form").height();
 | 
			
		||||
            for (var i=0; i<rows.length; i++) {
 | 
			
		||||
                height -= $(rows[i]).outerHeight(true);
 | 
			
		||||
            }
 | 
			
		||||
            var editorRow = $("#dialog-form>div.node-text-editor-row");
 | 
			
		||||
            height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
 | 
			
		||||
            $(".node-text-editor").css("height",height+"px");
 | 
			
		||||
            this.editor.resize();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,200 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    var mustache = require("mustache");
 | 
			
		||||
    var yaml = require("js-yaml");
 | 
			
		||||
 | 
			
		||||
    function extractTokens(tokens,set) {
 | 
			
		||||
        set = set || new Set();
 | 
			
		||||
        tokens.forEach(function(token) {
 | 
			
		||||
            if (token[0] !== 'text') {
 | 
			
		||||
                set.add(token[1]);
 | 
			
		||||
                if (token.length > 4) {
 | 
			
		||||
                    extractTokens(token[4],set);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        return set;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function parseContext(key) {
 | 
			
		||||
        var match = /^(flow|global)(\[(\w+)\])?\.(.+)/.exec(key);
 | 
			
		||||
        if (match) {
 | 
			
		||||
            var parts = {};
 | 
			
		||||
            parts.type = match[1];
 | 
			
		||||
            parts.store = (match[3] === '') ? "default" : match[3];
 | 
			
		||||
            parts.field = match[4];
 | 
			
		||||
            return parts;
 | 
			
		||||
        }
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Custom Mustache Context capable to collect message property and node
 | 
			
		||||
     * flow and global context
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    function NodeContext(msg, nodeContext, parent, escapeStrings, cachedContextTokens) {
 | 
			
		||||
        this.msgContext = new mustache.Context(msg,parent);
 | 
			
		||||
        this.nodeContext = nodeContext;
 | 
			
		||||
        this.escapeStrings = escapeStrings;
 | 
			
		||||
        this.cachedContextTokens = cachedContextTokens;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    NodeContext.prototype = new mustache.Context();
 | 
			
		||||
 | 
			
		||||
    NodeContext.prototype.lookup = function (name) {
 | 
			
		||||
        // try message first:
 | 
			
		||||
        try {
 | 
			
		||||
            var value = this.msgContext.lookup(name);
 | 
			
		||||
            if (value !== undefined) {
 | 
			
		||||
                if (this.escapeStrings && typeof value === "string") {
 | 
			
		||||
                    value = value.replace(/\\/g, "\\\\");
 | 
			
		||||
                    value = value.replace(/\n/g, "\\n");
 | 
			
		||||
                    value = value.replace(/\t/g, "\\t");
 | 
			
		||||
                    value = value.replace(/\r/g, "\\r");
 | 
			
		||||
                    value = value.replace(/\f/g, "\\f");
 | 
			
		||||
                    value = value.replace(/[\b]/g, "\\b");
 | 
			
		||||
                }
 | 
			
		||||
                return value;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // try flow/global context:
 | 
			
		||||
            var context = parseContext(name);
 | 
			
		||||
            if (context) {
 | 
			
		||||
                var type = context.type;
 | 
			
		||||
                var store = context.store;
 | 
			
		||||
                var field = context.field;
 | 
			
		||||
                var target = this.nodeContext[type];
 | 
			
		||||
                if (target) {
 | 
			
		||||
                    return this.cachedContextTokens[name];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return '';
 | 
			
		||||
        }
 | 
			
		||||
        catch(err) {
 | 
			
		||||
            throw err;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    NodeContext.prototype.push = function push (view) {
 | 
			
		||||
        return new NodeContext(view, this.nodeContext, this.msgContext, undefined, this.cachedContextTokens);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    function TemplateNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.name = n.name;
 | 
			
		||||
        this.field = n.field || "payload";
 | 
			
		||||
        this.template = n.template;
 | 
			
		||||
        this.syntax = n.syntax || "mustache";
 | 
			
		||||
        this.fieldType = n.fieldType || "msg";
 | 
			
		||||
        this.outputFormat = n.output || "str";
 | 
			
		||||
 | 
			
		||||
        var node = this;
 | 
			
		||||
 | 
			
		||||
        function output(msg,value,send,done) {
 | 
			
		||||
            /* istanbul ignore else  */
 | 
			
		||||
            if (node.outputFormat === "json") {
 | 
			
		||||
                value = JSON.parse(value);
 | 
			
		||||
            }
 | 
			
		||||
            /* istanbul ignore else  */
 | 
			
		||||
            if (node.outputFormat === "yaml") {
 | 
			
		||||
                value = yaml.load(value);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (node.fieldType === 'msg') {
 | 
			
		||||
                RED.util.setMessageProperty(msg, node.field, value);
 | 
			
		||||
                send(msg);
 | 
			
		||||
                done();
 | 
			
		||||
            } else if ((node.fieldType === 'flow') ||
 | 
			
		||||
                       (node.fieldType === 'global')) {
 | 
			
		||||
                var context = RED.util.parseContextStore(node.field);
 | 
			
		||||
                var target = node.context()[node.fieldType];
 | 
			
		||||
                target.set(context.key, value, context.store, function (err) {
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        done(err);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        send(msg);
 | 
			
		||||
                        done();
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        node.on("input", function(msg, send, done) {
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                /***
 | 
			
		||||
                * Allow template contents to be defined externally
 | 
			
		||||
                * through inbound msg.template IFF node.template empty
 | 
			
		||||
                */
 | 
			
		||||
                var template = node.template;
 | 
			
		||||
                if (msg.hasOwnProperty("template")) {
 | 
			
		||||
                    if (template == "" || template === null) {
 | 
			
		||||
                        template = msg.template;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (node.syntax === "mustache") {
 | 
			
		||||
                    var is_json = (node.outputFormat === "json");
 | 
			
		||||
                    var promises = [];
 | 
			
		||||
                    var tokens = extractTokens(mustache.parse(template));
 | 
			
		||||
                    var resolvedTokens = {};
 | 
			
		||||
                    tokens.forEach(function(name) {
 | 
			
		||||
                        var context = parseContext(name);
 | 
			
		||||
                        if (context) {
 | 
			
		||||
                            var type = context.type;
 | 
			
		||||
                            var store = context.store;
 | 
			
		||||
                            var field = context.field;
 | 
			
		||||
                            var target = node.context()[type];
 | 
			
		||||
                            if (target) {
 | 
			
		||||
                                var promise = new Promise((resolve, reject) => {
 | 
			
		||||
                                    target.get(field, store, (err, val) => {
 | 
			
		||||
                                        if (err) {
 | 
			
		||||
                                            reject(err);
 | 
			
		||||
                                        } else {
 | 
			
		||||
                                            resolvedTokens[name] = val;
 | 
			
		||||
                                            resolve();
 | 
			
		||||
                                        }
 | 
			
		||||
                                    });
 | 
			
		||||
                                });
 | 
			
		||||
                                promises.push(promise);
 | 
			
		||||
                                return;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    Promise.all(promises).then(function() {
 | 
			
		||||
                        var value = mustache.render(template, new NodeContext(msg, node.context(), null, is_json, resolvedTokens));
 | 
			
		||||
                        output(msg, value, send, done);
 | 
			
		||||
                    }).catch(function (err) {
 | 
			
		||||
                        done(err.message);
 | 
			
		||||
                    });
 | 
			
		||||
                } else {
 | 
			
		||||
                    output(msg, template, send, done);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch(err) {
 | 
			
		||||
                done(err.message);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType("template",TemplateNode);
 | 
			
		||||
    RED.library.register("templates");
 | 
			
		||||
}
 | 
			
		||||
@@ -1,424 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
//Simple node to introduce a pause into a flow
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    var MILLIS_TO_NANOS = 1000000;
 | 
			
		||||
    var SECONDS_TO_NANOS = 1000000000;
 | 
			
		||||
    var _maxKeptMsgsCount;
 | 
			
		||||
 | 
			
		||||
    function maxKeptMsgsCount(node) {
 | 
			
		||||
        if (_maxKeptMsgsCount === undefined) {
 | 
			
		||||
            var name = "nodeMessageBufferMaxLength";
 | 
			
		||||
            if (RED.settings.hasOwnProperty(name)) {
 | 
			
		||||
                _maxKeptMsgsCount = RED.settings[name];
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                _maxKeptMsgsCount = 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return _maxKeptMsgsCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function DelayNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
 | 
			
		||||
        this.pauseType = n.pauseType;
 | 
			
		||||
        this.timeoutUnits = n.timeoutUnits;
 | 
			
		||||
        this.randomUnits = n.randomUnits;
 | 
			
		||||
        this.rateUnits = n.rateUnits;
 | 
			
		||||
 | 
			
		||||
        if (n.timeoutUnits === "milliseconds") {
 | 
			
		||||
            this.timeout = n.timeout;
 | 
			
		||||
        } else if (n.timeoutUnits === "minutes") {
 | 
			
		||||
            this.timeout = n.timeout * (60 * 1000);
 | 
			
		||||
        } else if (n.timeoutUnits === "hours") {
 | 
			
		||||
            this.timeout = n.timeout * (60 * 60 * 1000);
 | 
			
		||||
        } else if (n.timeoutUnits === "days") {
 | 
			
		||||
            this.timeout = n.timeout * (24 * 60 * 60 * 1000);
 | 
			
		||||
        } else {   // Default to seconds
 | 
			
		||||
            this.timeout = n.timeout * 1000;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (n.rateUnits === "minute") {
 | 
			
		||||
            this.rate = (60 * 1000)/n.rate;
 | 
			
		||||
        } else if (n.rateUnits === "hour") {
 | 
			
		||||
            this.rate = (60 * 60 * 1000)/n.rate;
 | 
			
		||||
        } else if (n.rateUnits === "day") {
 | 
			
		||||
            this.rate = (24 * 60 * 60 * 1000)/n.rate;
 | 
			
		||||
        } else {  // Default to seconds
 | 
			
		||||
            this.rate = 1000/n.rate;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.rate *= (n.nbRateUnits > 0 ? n.nbRateUnits : 1);
 | 
			
		||||
 | 
			
		||||
        if (n.randomUnits === "milliseconds") {
 | 
			
		||||
            this.randomFirst = n.randomFirst * 1;
 | 
			
		||||
            this.randomLast = n.randomLast * 1;
 | 
			
		||||
        } else if (n.randomUnits === "minutes") {
 | 
			
		||||
            this.randomFirst = n.randomFirst * (60 * 1000);
 | 
			
		||||
            this.randomLast = n.randomLast * (60 * 1000);
 | 
			
		||||
        } else if (n.randomUnits === "hours") {
 | 
			
		||||
            this.randomFirst = n.randomFirst * (60 * 60 * 1000);
 | 
			
		||||
            this.randomLast = n.randomLast * (60 * 60 * 1000);
 | 
			
		||||
        } else if (n.randomUnits === "days") {
 | 
			
		||||
            this.randomFirst = n.randomFirst * (24 * 60 * 60 * 1000);
 | 
			
		||||
            this.randomLast = n.randomLast * (24 * 60 * 60 * 1000);
 | 
			
		||||
        } else {  // Default to seconds
 | 
			
		||||
            this.randomFirst = n.randomFirst * 1000;
 | 
			
		||||
            this.randomLast = n.randomLast * 1000;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.diff = this.randomLast - this.randomFirst;
 | 
			
		||||
        this.name = n.name;
 | 
			
		||||
        this.idList = [];
 | 
			
		||||
        this.buffer = [];
 | 
			
		||||
        this.intervalID = -1;
 | 
			
		||||
        this.randomID = -1;
 | 
			
		||||
        this.lastSent = null;
 | 
			
		||||
        this.drop = n.drop;
 | 
			
		||||
        this.droppedMsgs = 0;
 | 
			
		||||
        this.allowrate = n.allowrate|| false;
 | 
			
		||||
        this.fixedrate = this.rate;
 | 
			
		||||
        this.outputs = n.outputs;
 | 
			
		||||
        var node = this;
 | 
			
		||||
 | 
			
		||||
        function ourTimeout(handler, delay, clearHandler) {
 | 
			
		||||
            var toutID = setTimeout(handler, delay);
 | 
			
		||||
            return {
 | 
			
		||||
                clear: function() { clearTimeout(toutID); clearHandler(); },
 | 
			
		||||
                trigger: function() { clearTimeout(toutID); return handler(); }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var sendMsgFromBuffer = function() {
 | 
			
		||||
            if (node.buffer.length === 0) {
 | 
			
		||||
                clearInterval(node.intervalID);
 | 
			
		||||
                node.intervalID = -1;
 | 
			
		||||
            }
 | 
			
		||||
            if (node.buffer.length > 0) {
 | 
			
		||||
                const msgInfo = node.buffer.shift();
 | 
			
		||||
                if (Object.keys(msgInfo.msg).length > 1) {
 | 
			
		||||
                    msgInfo.send(msgInfo.msg);
 | 
			
		||||
                    msgInfo.done();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            node.reportDepth();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var clearDelayList = function(s) {
 | 
			
		||||
            var len = node.idList.length;
 | 
			
		||||
            for (var i=0; i<len; i++ ) { node.idList[i].clear(); }
 | 
			
		||||
            node.idList = [];
 | 
			
		||||
            if (s) { node.status({fill:"blue",shape:"ring",text:0}); }
 | 
			
		||||
            else { node.status({}); }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var flushDelayList = function(n) {
 | 
			
		||||
            var len = node.idList.length;
 | 
			
		||||
            if (typeof(n) == 'number') { len = Math.min(Math.floor(n),len); }
 | 
			
		||||
            for (var i=0; i<len; i++ ) { node.idList[0].trigger(); }
 | 
			
		||||
            node.status({fill:"blue",shape:"dot",text:node.idList.length});
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        node.reportDepth = function() {
 | 
			
		||||
            if (!node.busy) {
 | 
			
		||||
                node.busy = setTimeout(function() {
 | 
			
		||||
                    // if (node.buffer.length > 0) { node.status({text:node.buffer.length}); }
 | 
			
		||||
                    // else { node.status({}); }
 | 
			
		||||
                    node.status({fill:"blue",shape:"dot",text:node.buffer.length});
 | 
			
		||||
                    node.busy = null;
 | 
			
		||||
                }, 500);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var loggerId = setInterval(function () {
 | 
			
		||||
            if (node.droppedMsgs !== 0) {
 | 
			
		||||
                node.debug("node.droppedMsgs = " + node.droppedMsgs);
 | 
			
		||||
                node.droppedMsgs = 0;
 | 
			
		||||
            }
 | 
			
		||||
        }, 15 * 1000);
 | 
			
		||||
        node.on("close", function() { clearInterval(loggerId); });
 | 
			
		||||
 | 
			
		||||
        // The delay type modes
 | 
			
		||||
        if (node.pauseType === "delay") {
 | 
			
		||||
            node.on("input", function(msg, send, done) {
 | 
			
		||||
                var id = ourTimeout(function() {
 | 
			
		||||
                    node.idList.splice(node.idList.indexOf(id),1);
 | 
			
		||||
                    if (node.timeout > 1000) {
 | 
			
		||||
                        node.status({fill:"blue",shape:"dot",text:node.idList.length});
 | 
			
		||||
                    }
 | 
			
		||||
                    send(msg);
 | 
			
		||||
                    done();
 | 
			
		||||
                }, node.timeout, () => done());
 | 
			
		||||
                if (Object.keys(msg).length === 2 && msg.hasOwnProperty("flush")) { id.clear(); }
 | 
			
		||||
                else { node.idList.push(id); }
 | 
			
		||||
                if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
 | 
			
		||||
                else if (msg.hasOwnProperty("flush")) { flushDelayList(msg.flush); done(); }
 | 
			
		||||
                else if (node.timeout > 1000) {
 | 
			
		||||
                    node.status({fill:"blue",shape:"dot",text:node.idList.length});
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            node.on("close", function() { clearDelayList(); });
 | 
			
		||||
        }
 | 
			
		||||
        else if (node.pauseType === "delayv") {
 | 
			
		||||
            node.on("input", function(msg, send, done) {
 | 
			
		||||
                var delayvar = Number(node.timeout);
 | 
			
		||||
                if (msg.hasOwnProperty("delay") && !isNaN(parseFloat(msg.delay))) {
 | 
			
		||||
                    delayvar = parseFloat(msg.delay);
 | 
			
		||||
                }
 | 
			
		||||
                if (delayvar < 0) { delayvar = 0; }
 | 
			
		||||
                var id = ourTimeout(function() {
 | 
			
		||||
                    node.idList.splice(node.idList.indexOf(id),1);
 | 
			
		||||
                    if (node.idList.length === 0) { node.status({}); }
 | 
			
		||||
                    send(msg);
 | 
			
		||||
                    if (delayvar >= 0) {
 | 
			
		||||
                        node.status({fill:"blue",shape:"dot",text:node.idList.length});
 | 
			
		||||
                    }
 | 
			
		||||
                    done();
 | 
			
		||||
                }, delayvar, () => done());
 | 
			
		||||
                node.idList.push(id);
 | 
			
		||||
                if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
 | 
			
		||||
                if (msg.hasOwnProperty("flush")) { flushDelayList(msg.flush); done(); }
 | 
			
		||||
                if (delayvar >= 0) {
 | 
			
		||||
                    node.status({fill:"blue",shape:"dot",text:node.idList.length});
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            node.on("close", function() { clearDelayList(); });
 | 
			
		||||
        }
 | 
			
		||||
        else if (node.pauseType === "random") {
 | 
			
		||||
            node.on("input", function(msg, send, done) {
 | 
			
		||||
                var wait = node.randomFirst + (node.diff * Math.random());
 | 
			
		||||
                var id = ourTimeout(function() {
 | 
			
		||||
                    node.idList.splice(node.idList.indexOf(id),1);
 | 
			
		||||
                    send(msg);
 | 
			
		||||
                    if (node.timeout >= 1000) {
 | 
			
		||||
                        node.status({fill:"blue",shape:"dot",text:node.idList.length});
 | 
			
		||||
                    }
 | 
			
		||||
                    done();
 | 
			
		||||
                }, wait, () => done());
 | 
			
		||||
                if (Object.keys(msg).length === 2 && msg.hasOwnProperty("flush")) { id.clear(); }
 | 
			
		||||
                else { node.idList.push(id); }
 | 
			
		||||
                if (msg.hasOwnProperty("reset")) { clearDelayList(true); }
 | 
			
		||||
                if (msg.hasOwnProperty("flush")) { flushDelayList(msg.flush); done(); }
 | 
			
		||||
                if (node.timeout >= 1000) {
 | 
			
		||||
                    node.status({fill:"blue",shape:"dot",text:node.idList.length});
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            node.on("close", function() { clearDelayList(); });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The rate limit/queue type modes
 | 
			
		||||
        else if (node.pauseType === "rate") {
 | 
			
		||||
            node.on("input", function(msg, send, done) {
 | 
			
		||||
                if (msg.hasOwnProperty("reset")) {
 | 
			
		||||
                    if (node.intervalID !== -1 ) {
 | 
			
		||||
                        clearInterval(node.intervalID);
 | 
			
		||||
                        node.intervalID = -1;
 | 
			
		||||
                    }
 | 
			
		||||
                    delete node.lastSent;
 | 
			
		||||
                    node.buffer = [];
 | 
			
		||||
                    node.rate = node.fixedrate;
 | 
			
		||||
                    node.status({fill:"blue",shape:"ring",text:0});
 | 
			
		||||
                    done();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!node.drop) {
 | 
			
		||||
                    var m = RED.util.cloneMessage(msg);
 | 
			
		||||
                    delete m.flush;
 | 
			
		||||
                    delete m.lifo;
 | 
			
		||||
                    if (Object.keys(m).length > 1) {
 | 
			
		||||
                        if (node.intervalID !== -1) {
 | 
			
		||||
                            if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
 | 
			
		||||
                                node.rate = msg.rate;
 | 
			
		||||
                                clearInterval(node.intervalID);
 | 
			
		||||
                                node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
 | 
			
		||||
                            }
 | 
			
		||||
                            var max_msgs = maxKeptMsgsCount(node);
 | 
			
		||||
                            if ((max_msgs > 0) && (node.buffer.length >= max_msgs)) {
 | 
			
		||||
                                node.buffer = [];
 | 
			
		||||
                                node.error(RED._("delay.errors.too-many"), msg);
 | 
			
		||||
                            } else if (msg.toFront === true) {
 | 
			
		||||
                                node.buffer.unshift({msg: m, send: send, done: done});
 | 
			
		||||
                                node.reportDepth();
 | 
			
		||||
                            } else {
 | 
			
		||||
                                node.buffer.push({msg: m, send: send, done: done});
 | 
			
		||||
                                node.reportDepth();
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
 | 
			
		||||
                                node.rate = msg.rate;
 | 
			
		||||
                            }
 | 
			
		||||
                            send(m);
 | 
			
		||||
                            node.reportDepth();
 | 
			
		||||
                            node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
 | 
			
		||||
                            done();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (msg.hasOwnProperty("flush")) {
 | 
			
		||||
                        var len = node.buffer.length;
 | 
			
		||||
                        if (typeof(msg.flush) == 'number') { len = Math.min(Math.floor(msg.flush),len); }
 | 
			
		||||
                        while (len > 0) {
 | 
			
		||||
                            const msgInfo = node.buffer.shift();
 | 
			
		||||
                            if (Object.keys(msgInfo.msg).length > 1) {
 | 
			
		||||
                                node.send(msgInfo.msg);
 | 
			
		||||
                                msgInfo.done();
 | 
			
		||||
                            }
 | 
			
		||||
                            len = len - 1;
 | 
			
		||||
                        }
 | 
			
		||||
                        if (node.buffer.length === 0) {
 | 
			
		||||
                            clearInterval(node.intervalID);
 | 
			
		||||
                            node.intervalID = -1;
 | 
			
		||||
                        }
 | 
			
		||||
                        node.status({fill:"blue",shape:"dot",text:node.buffer.length});
 | 
			
		||||
                        done();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    if (maxKeptMsgsCount(node) > 0) {
 | 
			
		||||
                        if (node.intervalID === -1) {
 | 
			
		||||
                            node.send(msg);
 | 
			
		||||
                            node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
 | 
			
		||||
                                node.rate = msg.rate;
 | 
			
		||||
                                clearInterval(node.intervalID);
 | 
			
		||||
                                node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
 | 
			
		||||
                            }
 | 
			
		||||
                            if (node.buffer.length < _maxKeptMsgsCount) {
 | 
			
		||||
                                var m = RED.util.cloneMessage(msg);
 | 
			
		||||
                                node.buffer.push({msg: m, send: send, done: done});
 | 
			
		||||
                            } else {
 | 
			
		||||
                                node.trace("dropped due to buffer overflow. msg._msgid = " + msg._msgid);
 | 
			
		||||
                                node.droppedMsgs++;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
 | 
			
		||||
                            node.rate = msg.rate;
 | 
			
		||||
                        }
 | 
			
		||||
                        var timeSinceLast;
 | 
			
		||||
                        if (node.lastSent) {
 | 
			
		||||
                            timeSinceLast = process.hrtime(node.lastSent);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (!node.lastSent) { // ensuring that we always send the first message
 | 
			
		||||
                            node.lastSent = process.hrtime();
 | 
			
		||||
                            send(msg);
 | 
			
		||||
                        }
 | 
			
		||||
                        else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
 | 
			
		||||
                            node.lastSent = process.hrtime();
 | 
			
		||||
                            send(msg);
 | 
			
		||||
                        } else if (node.outputs === 2) {
 | 
			
		||||
                            send([null,msg])
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            node.on("close", function() {
 | 
			
		||||
                clearInterval(node.intervalID);
 | 
			
		||||
                clearTimeout(node.busy);
 | 
			
		||||
                node.buffer.forEach((msgInfo) => msgInfo.done());
 | 
			
		||||
                node.buffer = [];
 | 
			
		||||
                node.status({});
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The topic based fair queue and last arrived on all topics queue
 | 
			
		||||
        else if ((node.pauseType === "queue") || (node.pauseType === "timed")) {
 | 
			
		||||
            node.intervalID = setInterval(function() {
 | 
			
		||||
                if (node.pauseType === "queue") {
 | 
			
		||||
                    if (node.buffer.length > 0) {
 | 
			
		||||
                        const msgInfo = node.buffer.shift();
 | 
			
		||||
                        msgInfo.send(msgInfo.msg); // send the first on the queue
 | 
			
		||||
                        msgInfo.done();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    while (node.buffer.length > 0) {    // send the whole queue
 | 
			
		||||
                        const msgInfo = node.buffer.shift();
 | 
			
		||||
                        msgInfo.send(msgInfo.msg);
 | 
			
		||||
                        msgInfo.done();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                node.reportDepth();
 | 
			
		||||
            },node.rate);
 | 
			
		||||
 | 
			
		||||
            var hit;
 | 
			
		||||
            node.on("input", function(msg, send, done) {
 | 
			
		||||
                if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
 | 
			
		||||
                    node.rate = msg.rate;
 | 
			
		||||
                    clearInterval(node.intervalID);
 | 
			
		||||
                    node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
 | 
			
		||||
                }
 | 
			
		||||
                if (!msg.hasOwnProperty("topic")) { msg.topic = "_none_"; }
 | 
			
		||||
                hit = false;
 | 
			
		||||
                for (var b in node.buffer) { // check if already in queue
 | 
			
		||||
                    if (msg.topic === node.buffer[b].msg.topic) {
 | 
			
		||||
                        if (node.outputs === 2) { send([null,node.buffer[b].msg]) }
 | 
			
		||||
                        node.buffer[b].done();
 | 
			
		||||
                        node.buffer[b] = {msg, send, done}; // if so - replace existing entry
 | 
			
		||||
                        hit = true;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (!hit) {
 | 
			
		||||
                    node.buffer.push({msg, send, done}); // if not add to end of queue
 | 
			
		||||
                    node.reportDepth();
 | 
			
		||||
                }
 | 
			
		||||
                if (msg.hasOwnProperty("reset")) {
 | 
			
		||||
                    while (node.buffer.length > 0) {
 | 
			
		||||
                        const msgInfo = node.buffer.shift();
 | 
			
		||||
                        msgInfo.done();
 | 
			
		||||
                    }
 | 
			
		||||
                    node.buffer = [];
 | 
			
		||||
                    node.rate = node.fixedrate;
 | 
			
		||||
                    node.status({text:"reset"});
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
                if (msg.hasOwnProperty("flush")) {
 | 
			
		||||
                    var len = node.buffer.length;
 | 
			
		||||
                    if (typeof(msg.flush) == 'number') { len = Math.min(Math.floor(msg.flush,len)); }
 | 
			
		||||
                    while (len > 0) {
 | 
			
		||||
                        const msgInfo = node.buffer.shift();
 | 
			
		||||
                        delete msgInfo.msg.flush;
 | 
			
		||||
                        if (Object.keys(msgInfo.msg).length > 2) {
 | 
			
		||||
                            node.send(msgInfo.msg);
 | 
			
		||||
                            msgInfo.done();
 | 
			
		||||
                        }
 | 
			
		||||
                        len = len - 1;
 | 
			
		||||
                    }
 | 
			
		||||
                    node.status({});
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            node.on("close", function() {
 | 
			
		||||
                clearInterval(node.intervalID);
 | 
			
		||||
                while (node.buffer.length > 0) {
 | 
			
		||||
                    const msgInfo = node.buffer.shift();
 | 
			
		||||
                    msgInfo.done();
 | 
			
		||||
                }
 | 
			
		||||
                node.buffer = [];
 | 
			
		||||
                node.status({});
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("delay",DelayNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,234 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="trigger">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label data-i18n="trigger.send" for="node-input-op1"></label>
 | 
			
		||||
        <input type="hidden" id="node-input-op1type">
 | 
			
		||||
        <input style="width:70%" type="text" id="node-input-op1" placeholder="1">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label data-i18n="trigger.then"></label>
 | 
			
		||||
        <select id="node-then-type" style="width:70%;">
 | 
			
		||||
            <option value="block" data-i18n="trigger.wait-reset"></option>
 | 
			
		||||
            <option value="wait" data-i18n="trigger.wait-for"></option>
 | 
			
		||||
            <option value="loop" data-i18n="trigger.wait-loop"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-type-duration">
 | 
			
		||||
        <label></label>
 | 
			
		||||
        <input type="text" id="node-input-duration" style="text-align:end; width:70px !important">
 | 
			
		||||
        <select id="node-input-units" style="width:140px !important">
 | 
			
		||||
            <option value="ms" data-i18n="trigger.duration.ms"></option>
 | 
			
		||||
            <option value="s" data-i18n="trigger.duration.s"></option>
 | 
			
		||||
            <option value="min" data-i18n="trigger.duration.m"></option>
 | 
			
		||||
            <option value="hr" data-i18n="trigger.duration.h"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-type-wait">
 | 
			
		||||
    <label></label>
 | 
			
		||||
        <input type="checkbox" id="node-input-extend" style="margin-left:0px; vertical-align:top; width:auto !important;"> <label style="width:auto !important;" for="node-input-extend" data-i18n="trigger.extend"></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-type-override">
 | 
			
		||||
    <label></label>
 | 
			
		||||
        <input type="checkbox" id="node-input-overrideDelay" style="margin-left:0px; vertical-align:top; width:auto !important;"> <label style="width:auto !important;" for="node-input-overrideDelay" data-i18n="trigger.override"></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-type-wait">
 | 
			
		||||
        <label data-i18n="trigger.then-send"></label>
 | 
			
		||||
        <input type="hidden" id="node-input-op2type">
 | 
			
		||||
        <input style="width:70%" type="text" id="node-input-op2" placeholder="0">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" id="node-second-output">
 | 
			
		||||
        <label></label>
 | 
			
		||||
            <input type="checkbox" id="node-input-second" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;" for="node-input-second" data-i18n="trigger.second"></label>
 | 
			
		||||
        </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label data-i18n="trigger.label.reset" style="width:auto"></label>
 | 
			
		||||
        <div style="display:inline-block; width:70%;vertical-align:top">
 | 
			
		||||
        <ul>
 | 
			
		||||
            <li data-i18n="trigger.label.resetMessage"></li>
 | 
			
		||||
            <li><span data-i18n="trigger.label.resetPayload"></span> <input type="text" id="node-input-reset" style="width:150px" data-i18n="[placeholder]trigger.label.resetprompt"></li>
 | 
			
		||||
        </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
    <br/>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label data-i18n="trigger.for" for="node-input-bytopic"></label>
 | 
			
		||||
        <select id="node-input-bytopic" style="width:120px;">
 | 
			
		||||
            <option value="all" data-i18n="trigger.alltopics"></option>
 | 
			
		||||
            <option value="topic" data-i18n="trigger.bytopics"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
        <span class="form-row" id="node-stream-topic">
 | 
			
		||||
            <input type="text" id="node-input-topic" style="width:46%;"/>
 | 
			
		||||
        </span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></input>
 | 
			
		||||
        <input type="hidden" id="node-input-outputs" value="1">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('trigger',{
 | 
			
		||||
        category: 'function',
 | 
			
		||||
        color:"#E6E0F8",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            op1: {value:"1", validate: RED.validators.typedInput("op1type")},
 | 
			
		||||
            op2: {value:"0", validate: RED.validators.typedInput("op2type")},
 | 
			
		||||
            op1type: {value:"val"},
 | 
			
		||||
            op2type: {value:"val"},
 | 
			
		||||
            duration: {value:"250",required:true,validate:RED.validators.number()},
 | 
			
		||||
            extend: {value:"false"},
 | 
			
		||||
            overrideDelay: {value:"false"},
 | 
			
		||||
            units: {value:"ms"},
 | 
			
		||||
            reset: {value:""},
 | 
			
		||||
            bytopic: {value:"all"},
 | 
			
		||||
            topic: {value:"topic",required:true},
 | 
			
		||||
            outputs: {value:1}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "trigger.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            if (this.duration > 0) {
 | 
			
		||||
                return this.name|| this._("trigger.label.trigger")+" "+this.duration+this.units;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.duration < 0) {
 | 
			
		||||
                return this.name|| this._("trigger.label.trigger-loop")+" "+(this.duration * -1)+this.units;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                return this.name|| this._("trigger.label.trigger-block");
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var that = this;
 | 
			
		||||
            if (this.topic === undefined) { $("#node-input-topic").val("topic"); }
 | 
			
		||||
            $("#node-input-topic").typedInput({default:'msg',types:['msg']});
 | 
			
		||||
            $("#node-input-bytopic").on("change", function() {
 | 
			
		||||
                if ($("#node-input-bytopic").val() === "all") {
 | 
			
		||||
                    $("#node-stream-topic").hide();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-stream-topic").show();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (this.outputs == 2) { $("#node-input-second").prop('checked', true) }
 | 
			
		||||
            else { $("#node-input-second").prop('checked', false) }
 | 
			
		||||
 | 
			
		||||
            $("#node-input-second").change(function() {
 | 
			
		||||
                if ($("#node-input-second").is(":checked")) {
 | 
			
		||||
                    $("#node-input-outputs").val(2);
 | 
			
		||||
                }
 | 
			
		||||
                else  {
 | 
			
		||||
                    $("#node-input-outputs").val(1);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-then-type").on("change", function() {
 | 
			
		||||
                if ($(this).val() == "block") {
 | 
			
		||||
                    $(".node-type-wait").hide();
 | 
			
		||||
                    $(".node-type-override").hide();
 | 
			
		||||
                    $(".node-type-duration").hide();
 | 
			
		||||
                    $("#node-second-output").hide();
 | 
			
		||||
                    $("#node-input-second").prop('checked', false);
 | 
			
		||||
                    $("#node-input-outputs").val(1);
 | 
			
		||||
                }
 | 
			
		||||
                else if ($(this).val() == "loop") {
 | 
			
		||||
                    if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); }
 | 
			
		||||
                    $(".node-type-wait").hide();
 | 
			
		||||
                    $(".node-type-override").show();
 | 
			
		||||
                    $(".node-type-duration").show();
 | 
			
		||||
                    $("#node-second-output").hide();
 | 
			
		||||
                    $("#node-input-second").prop('checked', false);
 | 
			
		||||
                    $("#node-input-outputs").val(1);
 | 
			
		||||
                } else {
 | 
			
		||||
                    if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); }
 | 
			
		||||
                    $(".node-type-wait").show();
 | 
			
		||||
                    $(".node-type-override").show();
 | 
			
		||||
                    $(".node-type-duration").show();
 | 
			
		||||
                    $("#node-second-output").show();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (this.op1type === 'val') {
 | 
			
		||||
                $("#node-input-op1type").val('str');
 | 
			
		||||
            }
 | 
			
		||||
            if (this.op2type === 'val') {
 | 
			
		||||
                $("#node-input-op2type").val('str');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var optionNothing = {value:"nul",label:this._("trigger.output.nothing"),hasValue:false};
 | 
			
		||||
            var optionPayload = {value:"pay",label:this._("trigger.output.existing"),hasValue:false};
 | 
			
		||||
            var optionOriginalPayload = {value:"pay",label:this._("trigger.output.original"),hasValue:false};
 | 
			
		||||
            var optionLatestPayload = {value:"payl",label:this._("trigger.output.latest"),hasValue:false};
 | 
			
		||||
 | 
			
		||||
            $("#node-input-op1").typedInput({
 | 
			
		||||
                default: 'str',
 | 
			
		||||
                typeField: $("#node-input-op1type"),
 | 
			
		||||
                types:['flow','global','str','num','bool','json','bin','date','env',
 | 
			
		||||
                    optionPayload,
 | 
			
		||||
                    optionNothing
 | 
			
		||||
                ]
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-op2").typedInput({
 | 
			
		||||
                default: 'str',
 | 
			
		||||
                typeField: $("#node-input-op2type"),
 | 
			
		||||
                types:['flow','global','str','num','bool','json','bin','date','env',
 | 
			
		||||
                    optionOriginalPayload,
 | 
			
		||||
                    optionLatestPayload,
 | 
			
		||||
                    optionNothing
 | 
			
		||||
                ]
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (this.bytopic === undefined) {
 | 
			
		||||
                $("#node-input-bytopic").val("all");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.duration == "0") {
 | 
			
		||||
                $("#node-then-type").val("block");
 | 
			
		||||
            }
 | 
			
		||||
            else if ((this.duration * 1) < 0) {
 | 
			
		||||
                $("#node-then-type").val("loop");
 | 
			
		||||
                $("#node-input-duration").val(this.duration*-1);
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-then-type").val("wait");
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-then-type").trigger("change");
 | 
			
		||||
 | 
			
		||||
            if (this.extend === "true" || this.extend === true) {
 | 
			
		||||
                $("#node-input-extend").prop("checked",true);
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-input-extend").prop("checked",false);
 | 
			
		||||
            }
 | 
			
		||||
            if (this.overrideDelay === "true" || this.overrideDelay === true) {
 | 
			
		||||
                $("#node-input-overrideDelay").prop("checked",true);
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-input-overrideDelay").prop("checked",false);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            if ($("#node-then-type").val() == "block") {
 | 
			
		||||
                $("#node-input-duration").val("0");
 | 
			
		||||
            }
 | 
			
		||||
            if ($("#node-then-type").val() == "loop") {
 | 
			
		||||
                $("#node-input-duration").val($("#node-input-duration").val() * -1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,298 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    var mustache = require("mustache");
 | 
			
		||||
    function TriggerNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.bytopic = n.bytopic || "all";
 | 
			
		||||
        this.op1 = n.op1 || "1";
 | 
			
		||||
        this.op2 = n.op2 || "0";
 | 
			
		||||
        this.op1type = n.op1type || "str";
 | 
			
		||||
        this.op2type = n.op2type || "str";
 | 
			
		||||
        this.second = (n.outputs == 2) ? true : false;
 | 
			
		||||
        this.topic = n.topic || "topic";
 | 
			
		||||
 | 
			
		||||
        if (this.op1type === 'val') {
 | 
			
		||||
            if (this.op1 === 'true' || this.op1 === 'false') {
 | 
			
		||||
                this.op1type = 'bool'
 | 
			
		||||
            } else if (this.op1 === 'null') {
 | 
			
		||||
                this.op1type = 'null';
 | 
			
		||||
                this.op1 = null;
 | 
			
		||||
            } else {
 | 
			
		||||
                this.op1type = 'str';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (this.op2type === 'val') {
 | 
			
		||||
            if (this.op2 === 'true' || this.op2 === 'false') {
 | 
			
		||||
                this.op2type = 'bool'
 | 
			
		||||
            } else if (this.op2 === 'null') {
 | 
			
		||||
                this.op2type = 'null';
 | 
			
		||||
                this.op2 = null;
 | 
			
		||||
            } else {
 | 
			
		||||
                this.op2type = 'str';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        this.extend = n.extend || "false";
 | 
			
		||||
        this.overrideDelay = n.overrideDelay || false;
 | 
			
		||||
        this.units = n.units || "ms";
 | 
			
		||||
        this.reset = n.reset || '';
 | 
			
		||||
        this.duration = parseFloat(n.duration);
 | 
			
		||||
        if (isNaN(this.duration)) {
 | 
			
		||||
            this.duration = 250;
 | 
			
		||||
        }
 | 
			
		||||
        if (this.duration < 0) {
 | 
			
		||||
            this.loop = true;
 | 
			
		||||
            this.duration = this.duration * -1;
 | 
			
		||||
            this.extend = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.units == "s") { this.duration = this.duration * 1000; }
 | 
			
		||||
        if (this.units == "min") { this.duration = this.duration * 1000 * 60; }
 | 
			
		||||
        if (this.units == "hr") { this.duration = this.duration * 1000 *60 * 60; }
 | 
			
		||||
 | 
			
		||||
        this.op1Templated = (this.op1type === 'str' && this.op1.indexOf("{{") != -1);
 | 
			
		||||
        this.op2Templated = (this.op2type === 'str' && this.op2.indexOf("{{") != -1);
 | 
			
		||||
        if ((this.op1type === "num") && (!isNaN(this.op1))) { this.op1 = Number(this.op1); }
 | 
			
		||||
        if ((this.op2type === "num") && (!isNaN(this.op2))) { this.op2 = Number(this.op2); }
 | 
			
		||||
        //if (this.op1 == "null") { this.op1 = null; }
 | 
			
		||||
        //if (this.op2 == "null") { this.op2 = null; }
 | 
			
		||||
        //try { this.op1 = JSON.parse(this.op1); }
 | 
			
		||||
        //catch(e) { this.op1 = this.op1; }
 | 
			
		||||
        //try { this.op2 = JSON.parse(this.op2); }
 | 
			
		||||
        //catch(e) { this.op2 = this.op2; }
 | 
			
		||||
 | 
			
		||||
        var node = this;
 | 
			
		||||
        node.topics = {};
 | 
			
		||||
 | 
			
		||||
        var npay = {};
 | 
			
		||||
        var pendingMessages = [];
 | 
			
		||||
        var activeMessagePromise = null;
 | 
			
		||||
        var processMessageQueue = function(msgInfo) {
 | 
			
		||||
            if (msgInfo) {
 | 
			
		||||
                // A new message has arrived - add it to the message queue
 | 
			
		||||
                pendingMessages.push(msgInfo);
 | 
			
		||||
                if (activeMessagePromise !== null) {
 | 
			
		||||
                    // The node is currently processing a message, so do nothing
 | 
			
		||||
                    // more with this message
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (pendingMessages.length === 0) {
 | 
			
		||||
                // There are no more messages to process, clear the active flag
 | 
			
		||||
                // and return
 | 
			
		||||
                activeMessagePromise = null;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // There are more messages to process. Get the next message and
 | 
			
		||||
            // start processing it. Recurse back in to check for any more
 | 
			
		||||
            var nextMsgInfo = pendingMessages.shift();
 | 
			
		||||
            activeMessagePromise = processMessage(nextMsgInfo)
 | 
			
		||||
                .then(processMessageQueue)
 | 
			
		||||
                .catch((err) => {
 | 
			
		||||
                    nextMsgInfo.done(err);
 | 
			
		||||
                    return processMessageQueue();
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.on('input', function(msg, send, done) {
 | 
			
		||||
            processMessageQueue({msg, send, done});
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var stat = function() {
 | 
			
		||||
            var l = Object.keys(node.topics).length;
 | 
			
		||||
            if (l === 0) { return {} }
 | 
			
		||||
            else if (l === 1) { return {fill:"blue",shape:"dot"} }
 | 
			
		||||
            else return {fill:"blue",shape:"dot",text:l};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var processMessage = function(msgInfo) {
 | 
			
		||||
            let msg = msgInfo.msg;
 | 
			
		||||
            var topic = RED.util.getMessageProperty(msg,node.topic) || "_none";
 | 
			
		||||
            var promise;
 | 
			
		||||
            var delayDuration = node.duration;
 | 
			
		||||
            if (node.overrideDelay && msg.hasOwnProperty("delay") && !isNaN(parseFloat(msg.delay))) {
 | 
			
		||||
                delayDuration = parseFloat(msg.delay);
 | 
			
		||||
            }
 | 
			
		||||
            if (node.bytopic === "all") { topic = "_none"; }
 | 
			
		||||
            node.topics[topic] = node.topics[topic] || {};
 | 
			
		||||
            if (msg.hasOwnProperty("reset") || ((node.reset !== '') && msg.hasOwnProperty("payload") && (msg.payload !== null) && msg.payload.toString && (msg.payload.toString() == node.reset)) ) {
 | 
			
		||||
                if (node.loop === true) { clearInterval(node.topics[topic].tout); }
 | 
			
		||||
                else { clearTimeout(node.topics[topic].tout); }
 | 
			
		||||
                delete node.topics[topic];
 | 
			
		||||
                node.status(stat());
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                if (node.op2type === "payl") { npay[topic] = RED.util.cloneMessage(msg); }
 | 
			
		||||
                if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) {
 | 
			
		||||
                    promise = Promise.resolve();
 | 
			
		||||
                    if (node.op2type === "pay") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
 | 
			
		||||
                    else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); }
 | 
			
		||||
                    else if (node.op2type !== "nul") {
 | 
			
		||||
                        promise = new Promise((resolve,reject) => {
 | 
			
		||||
                            RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
 | 
			
		||||
                                if (err) {
 | 
			
		||||
                                    reject(err);
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    node.topics[topic].m2 = value;
 | 
			
		||||
                                    resolve();
 | 
			
		||||
                                }
 | 
			
		||||
                            });
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return promise.then(() => {
 | 
			
		||||
                        promise = Promise.resolve();
 | 
			
		||||
                        if (node.op1type === "pay") { }
 | 
			
		||||
                        else if (node.op1Templated) { msg.payload = mustache.render(node.op1,msg); }
 | 
			
		||||
                        else if (node.op1type !== "nul") {
 | 
			
		||||
                            promise = new Promise((resolve,reject) => {
 | 
			
		||||
                                RED.util.evaluateNodeProperty(node.op1,node.op1type,node,msg,(err,value) => {
 | 
			
		||||
                                    if (err) {
 | 
			
		||||
                                        reject(err);
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        msg.payload = value;
 | 
			
		||||
                                        resolve();
 | 
			
		||||
                                    }
 | 
			
		||||
                                });
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
                        return promise.then(() => {
 | 
			
		||||
                            if (delayDuration === 0) { node.topics[topic].tout = 0; }
 | 
			
		||||
                            else if (node.loop === true) {
 | 
			
		||||
                                /* istanbul ignore else  */
 | 
			
		||||
                                if (node.topics[topic].tout) { clearInterval(node.topics[topic].tout); }
 | 
			
		||||
                                /* istanbul ignore else  */
 | 
			
		||||
                                if (node.op1type !== "nul") {
 | 
			
		||||
                                    var msg2 = RED.util.cloneMessage(msg);
 | 
			
		||||
                                    node.topics[topic].tout = setInterval(function() {
 | 
			
		||||
                                        if (node.op1type === "date") { msg2.payload = Date.now(); }
 | 
			
		||||
                                        msgInfo.send(RED.util.cloneMessage(msg2));
 | 
			
		||||
                                    }, delayDuration);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            else {
 | 
			
		||||
                                if (!node.topics[topic].tout) {
 | 
			
		||||
                                    node.topics[topic].tout = setTimeout(function() {
 | 
			
		||||
                                        var msg2 = null;
 | 
			
		||||
                                        if (node.op2type !== "nul") {
 | 
			
		||||
                                            var promise = Promise.resolve();
 | 
			
		||||
                                            msg2 = RED.util.cloneMessage(msg);
 | 
			
		||||
                                            if (node.op2type === "flow" || node.op2type === "global") {
 | 
			
		||||
                                                promise = new Promise((resolve,reject) => {
 | 
			
		||||
                                                    RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
 | 
			
		||||
                                                        if (err) {
 | 
			
		||||
                                                            reject(err);
 | 
			
		||||
                                                        } else {
 | 
			
		||||
                                                            node.topics[topic].m2 = value;
 | 
			
		||||
                                                            resolve();
 | 
			
		||||
                                                        }
 | 
			
		||||
                                                    });
 | 
			
		||||
                                                });
 | 
			
		||||
                                            }
 | 
			
		||||
                                            promise.then(() => {
 | 
			
		||||
                                                if (node.op2type === "payl") {
 | 
			
		||||
                                                    if (node.second === true) { msgInfo.send([null,npay[topic]]); }
 | 
			
		||||
                                                    else { msgInfo.send(npay[topic]); }
 | 
			
		||||
                                                    delete npay[topic];
 | 
			
		||||
                                                }
 | 
			
		||||
                                                else {
 | 
			
		||||
                                                    msg2.payload = node.topics[topic].m2;
 | 
			
		||||
                                                    if (node.op2type === "date") { msg2.payload = Date.now(); }
 | 
			
		||||
                                                    if (node.second === true) { msgInfo.send([null,msg2]); }
 | 
			
		||||
                                                    else { msgInfo.send(msg2); }
 | 
			
		||||
                                                }
 | 
			
		||||
                                                delete node.topics[topic];
 | 
			
		||||
                                                node.status(stat());
 | 
			
		||||
                                            }).catch(err => {
 | 
			
		||||
                                                node.error(err);
 | 
			
		||||
                                            });
 | 
			
		||||
                                        } else {
 | 
			
		||||
                                            delete node.topics[topic];
 | 
			
		||||
                                            node.status(stat());
 | 
			
		||||
                                        }
 | 
			
		||||
 | 
			
		||||
                                    }, delayDuration);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            msgInfo.done();
 | 
			
		||||
                            node.status(stat());
 | 
			
		||||
                            if (node.op1type !== "nul") { msgInfo.send(RED.util.cloneMessage(msg)); }
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                else if ((node.extend === "true" || node.extend === true) && (delayDuration > 0)) {
 | 
			
		||||
                    /* istanbul ignore else  */
 | 
			
		||||
                    if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
 | 
			
		||||
                    /* istanbul ignore else  */
 | 
			
		||||
                    if (node.topics[topic].tout) { clearTimeout(node.topics[topic].tout); }
 | 
			
		||||
                    node.topics[topic].tout = setTimeout(function() {
 | 
			
		||||
                        var msg2 = null;
 | 
			
		||||
                        var promise = Promise.resolve();
 | 
			
		||||
 | 
			
		||||
                        if (node.op2type !== "nul") {
 | 
			
		||||
                            if (node.op2type === "flow" || node.op2type === "global") {
 | 
			
		||||
                                promise = new Promise((resolve,reject) => {
 | 
			
		||||
                                    RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
 | 
			
		||||
                                        if (err) {
 | 
			
		||||
                                            reject(err);
 | 
			
		||||
                                        } else {
 | 
			
		||||
                                            node.topics[topic].m2 = value;
 | 
			
		||||
                                            resolve();
 | 
			
		||||
                                        }
 | 
			
		||||
                                    });
 | 
			
		||||
                                });
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        promise.then(() => {
 | 
			
		||||
                            if (node.op2type !== "nul") {
 | 
			
		||||
                                if (node.topics[topic] !== undefined) {
 | 
			
		||||
                                    msg2 = RED.util.cloneMessage(msg);
 | 
			
		||||
                                    msg2.payload = node.topics[topic].m2;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            delete node.topics[topic];
 | 
			
		||||
                            node.status(stat());
 | 
			
		||||
                            if (node.second === true) { msgInfo.send([null,msg2]); }
 | 
			
		||||
                            else { msgInfo.send(msg2); }
 | 
			
		||||
                        }).catch(err => {
 | 
			
		||||
                            node.error(err);
 | 
			
		||||
                        });
 | 
			
		||||
                    }, delayDuration);
 | 
			
		||||
                }
 | 
			
		||||
                // else {
 | 
			
		||||
                //     if (node.op2type === "payl") {node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
 | 
			
		||||
                // }
 | 
			
		||||
            }
 | 
			
		||||
            msgInfo.done();
 | 
			
		||||
            return Promise.resolve();
 | 
			
		||||
        }
 | 
			
		||||
        this.on("close", function() {
 | 
			
		||||
            for (var t in node.topics) {
 | 
			
		||||
                /* istanbul ignore else  */
 | 
			
		||||
                if (node.topics[t]) {
 | 
			
		||||
                    if (node.loop === true) { clearInterval(node.topics[t].tout); }
 | 
			
		||||
                    else { clearTimeout(node.topics[t].tout); }
 | 
			
		||||
                    delete node.topics[t];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            node.status(stat());
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("trigger",TriggerNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,113 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="exec">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-command"><i class="fa fa-file"></i> <span data-i18n="exec.label.command"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-command" data-i18n="[placeholder]exec.label.command">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label><i class="fa fa-plus"></i> <span data-i18n="exec.label.append"></span></label>
 | 
			
		||||
        <input type="checkbox" id="node-input-addpay-cb" style="display:inline-block; width:auto;">
 | 
			
		||||
        <input type="text" id="node-input-addpay" style="margin-left: 5px; width:160px;">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-append"> </label>
 | 
			
		||||
        <input type="text" id="node-input-append" data-i18n="[placeholder]exec.placeholder.extraparams">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label><i class="fa fa-sign-out"></i> <span data-i18n="exec.label.return"></span></label>
 | 
			
		||||
        <select type="text" id="node-input-useSpawn" style="width:70%">
 | 
			
		||||
            <option value="false" data-i18n="exec.opt.exec"></option>
 | 
			
		||||
            <option value="true" data-i18n="exec.opt.spawn"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-timer"><i class="fa fa-clock-o"></i> <span data-i18n="exec.label.timeout"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-timer" style="width:65px;" data-i18n="[placeholder]exec.label.timeoutplace">
 | 
			
		||||
        <span data-i18n="exec.label.seconds"></span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-winHide" style="width: auto !important; padding-right:10px"><i class="fa fa-windows"></i> <span data-i18n="exec.label.winHide"></span></label>
 | 
			
		||||
        <input type="checkbox" id="node-input-winHide" style="margin-top: 0; display:inline-block; width:auto;">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('exec',{
 | 
			
		||||
        category: 'function',
 | 
			
		||||
        color:"darksalmon",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            command: {value:""},
 | 
			
		||||
            addpay: {value:""},
 | 
			
		||||
            append: {value:""},
 | 
			
		||||
            useSpawn: {value:"false"},
 | 
			
		||||
            timer: {value:""},
 | 
			
		||||
            winHide: {value:false},
 | 
			
		||||
            oldrc: {value:false},
 | 
			
		||||
            name: {value:""}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:3,
 | 
			
		||||
        outputLabels: function(i) {
 | 
			
		||||
            return [
 | 
			
		||||
                this._("exec.label.stdout"),
 | 
			
		||||
                this._("exec.label.stderr"),
 | 
			
		||||
                this._("exec.label.retcode")
 | 
			
		||||
            ][i];
 | 
			
		||||
        },
 | 
			
		||||
        icon: "cog.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||this.command.replace(/\\n /g,"\\\\n ")||(this.useSpawn=="true"?this._("exec.spawn"):this._("exec.exec"));
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            if ($("#node-input-useSpawn").val() === null) {
 | 
			
		||||
                $("#node-input-useSpawn").val(this.useSpawn.toString());
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-addpay-cb").prop("checked", this.addpay === true || (this.addpay !== false && this.addpay !== ""))
 | 
			
		||||
            var addpayValue = (this.addpay === true)?"payload":((this.addpay === false || this.addpay === "")?"payload":this.addpay);
 | 
			
		||||
            $("#node-input-addpay-cb").on("change", function(evt) {
 | 
			
		||||
                $("#node-input-addpay").typedInput("disable",!$("#node-input-addpay-cb").prop("checked"));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $("#node-input-addpay").val(addpayValue);
 | 
			
		||||
            $("#node-input-addpay").typedInput({
 | 
			
		||||
                default: "msg",
 | 
			
		||||
                types: ["msg"]
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $("#node-input-addpay-cb").trigger("change")
 | 
			
		||||
 | 
			
		||||
            if (this.winHide === "true" || this.winHide === true) {
 | 
			
		||||
                $("#node-input-winHide").prop("checked",true);
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-input-winHide").prop("checked",false);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            if (!$("#node-input-addpay-cb").prop("checked")) {
 | 
			
		||||
                $("#node-input-addpay").val("");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,200 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    var spawn = require('child_process').spawn;
 | 
			
		||||
    var exec = require('child_process').exec;
 | 
			
		||||
    var fs = require('fs');
 | 
			
		||||
    var isUtf8 = require('is-utf8');
 | 
			
		||||
 | 
			
		||||
    function ExecNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.cmd = (n.command || "").trim();
 | 
			
		||||
        if (n.addpay === undefined) { n.addpay = true; }
 | 
			
		||||
        this.addpay = n.addpay;
 | 
			
		||||
        if (this.addpay === true) {
 | 
			
		||||
            this.addpay = "payload";
 | 
			
		||||
        }
 | 
			
		||||
        this.append = (n.append || "").trim();
 | 
			
		||||
        this.useSpawn = (n.useSpawn == "true");
 | 
			
		||||
        this.timer = Number(n.timer || 0)*1000;
 | 
			
		||||
        this.activeProcesses = {};
 | 
			
		||||
        this.oldrc = (n.oldrc || false).toString();
 | 
			
		||||
        this.execOpt = {encoding:'binary', maxBuffer:RED.settings.execMaxBufferSize||10000000, windowsHide: (n.winHide === true)};
 | 
			
		||||
        this.spawnOpt = {windowsHide: (n.winHide === true) }
 | 
			
		||||
        var node = this;
 | 
			
		||||
 | 
			
		||||
        if (process.platform === 'linux' && fs.existsSync('/bin/bash')) { node.execOpt.shell = '/bin/bash'; }
 | 
			
		||||
 | 
			
		||||
        var cleanup = function(p) {
 | 
			
		||||
            node.activeProcesses[p].kill();
 | 
			
		||||
            //node.status({fill:"red",shape:"dot",text:"timeout"});
 | 
			
		||||
            //node.error("Exec node timeout");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.on("input", function(msg, nodeSend, nodeDone) {
 | 
			
		||||
            if (msg.hasOwnProperty("kill")) {
 | 
			
		||||
                if (typeof msg.kill !== "string" || msg.kill.length === 0 || !msg.kill.toUpperCase().startsWith("SIG") ) { msg.kill = "SIGTERM"; }
 | 
			
		||||
                if (msg.hasOwnProperty("pid")) {
 | 
			
		||||
                    if (node.activeProcesses.hasOwnProperty(msg.pid) ) {
 | 
			
		||||
                        node.activeProcesses[msg.pid].kill(msg.kill.toUpperCase());
 | 
			
		||||
                        node.status({fill:"red",shape:"dot",text:"killed"});
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    if (Object.keys(node.activeProcesses).length === 1) {
 | 
			
		||||
                        node.activeProcesses[Object.keys(node.activeProcesses)[0]].kill(msg.kill.toUpperCase());
 | 
			
		||||
                        node.status({fill:"red",shape:"dot",text:"killed"});
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                nodeDone();
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                var child;
 | 
			
		||||
                // make the extra args into an array
 | 
			
		||||
                // then prepend with the msg.payload
 | 
			
		||||
                var arg = node.cmd;
 | 
			
		||||
                if (node.addpay) {
 | 
			
		||||
                    var value = RED.util.getMessageProperty(msg, node.addpay);
 | 
			
		||||
                    if (value !== undefined) {
 | 
			
		||||
                        arg += " " + value;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (node.append.trim() !== "") { arg += " " + node.append; }
 | 
			
		||||
                if (this.useSpawn === true) {
 | 
			
		||||
                    // slice whole line by spaces and removes any quotes since spawn can't handle them
 | 
			
		||||
                    arg = arg.match(/(?:[^\s"]+|"[^"]*")+/g).map((a) => {
 | 
			
		||||
                        if (/^".*"$/.test(a)) {
 | 
			
		||||
                            return a.slice(1,-1)
 | 
			
		||||
                        } else {
 | 
			
		||||
                            return a
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    var cmd = arg.shift();
 | 
			
		||||
                    /* istanbul ignore else  */
 | 
			
		||||
                    if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); }
 | 
			
		||||
                    child = spawn(cmd,arg,node.spawnOpt);
 | 
			
		||||
                    node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
 | 
			
		||||
                    var unknownCommand = (child.pid === undefined);
 | 
			
		||||
                    if (node.timer !== 0) {
 | 
			
		||||
                        child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer);
 | 
			
		||||
                    }
 | 
			
		||||
                    node.activeProcesses[child.pid] = child;
 | 
			
		||||
                    child.stdout.on('data', function (data) {
 | 
			
		||||
                        if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
 | 
			
		||||
                            // console.log('[exec] stdout: ' + data,child.pid);
 | 
			
		||||
                            if (isUtf8(data)) { msg.payload = data.toString(); }
 | 
			
		||||
                            else { msg.payload = data; }
 | 
			
		||||
                            nodeSend([RED.util.cloneMessage(msg),null,null]);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    child.stderr.on('data', function (data) {
 | 
			
		||||
                        if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
 | 
			
		||||
                            if (isUtf8(data)) { msg.payload = data.toString(); }
 | 
			
		||||
                            else { msg.payload = Buffer.from(data); }
 | 
			
		||||
                            nodeSend([null,RED.util.cloneMessage(msg),null]);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    child.on('close', function (code,signal) {
 | 
			
		||||
                        if (unknownCommand || (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null)) {
 | 
			
		||||
                            delete node.activeProcesses[child.pid];
 | 
			
		||||
                            if (child.tout) { clearTimeout(child.tout); }
 | 
			
		||||
                            msg.payload = code;
 | 
			
		||||
                            if (node.oldrc === "false") {
 | 
			
		||||
                                msg.payload = {code:code};
 | 
			
		||||
                                if (signal) { msg.payload.signal = signal; }
 | 
			
		||||
                            }
 | 
			
		||||
                            if (code === 0) { node.status({}); }
 | 
			
		||||
                            if (code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); }
 | 
			
		||||
                            else if (code < 0) { node.status({fill:"red",shape:"dot",text:"rc:"+code}); }
 | 
			
		||||
                            else { node.status({fill:"yellow",shape:"dot",text:"rc:"+code}); }
 | 
			
		||||
                            nodeSend([null,null,RED.util.cloneMessage(msg)]);
 | 
			
		||||
                        }
 | 
			
		||||
                        nodeDone();
 | 
			
		||||
                    });
 | 
			
		||||
                    child.on('error', function (code) {
 | 
			
		||||
                        if (child.tout) { clearTimeout(child.tout); }
 | 
			
		||||
                        delete node.activeProcesses[child.pid];
 | 
			
		||||
                        if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
 | 
			
		||||
                            node.error(code,RED.util.cloneMessage(msg));
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    /* istanbul ignore else  */
 | 
			
		||||
                    if (RED.settings.verbose) { node.log(arg); }
 | 
			
		||||
                    child = exec(arg, node.execOpt, function (error, stdout, stderr) {
 | 
			
		||||
                        var msg2, msg3;
 | 
			
		||||
                        delete msg.payload;
 | 
			
		||||
                        if (stderr) {
 | 
			
		||||
                            msg2 = RED.util.cloneMessage(msg);
 | 
			
		||||
                            msg2.payload = stderr;
 | 
			
		||||
                        }
 | 
			
		||||
                        msg.payload = Buffer.from(stdout,"binary");
 | 
			
		||||
                        if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
 | 
			
		||||
                        node.status({});
 | 
			
		||||
                        //console.log('[exec] stdout: ' + stdout);
 | 
			
		||||
                        //console.log('[exec] stderr: ' + stderr);
 | 
			
		||||
                        if (error !== null) {
 | 
			
		||||
                            msg3 = RED.util.cloneMessage(msg);
 | 
			
		||||
                            msg3.payload = {code:error.code, message:error.message};
 | 
			
		||||
                            if (error.signal) { msg3.payload.signal = error.signal; }
 | 
			
		||||
                            if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); }
 | 
			
		||||
                            else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); }
 | 
			
		||||
                            if (RED.settings.verbose) { node.log('error:' + error); }
 | 
			
		||||
                        }
 | 
			
		||||
                        else if (node.oldrc === "false") {
 | 
			
		||||
                            msg3 = RED.util.cloneMessage(msg);
 | 
			
		||||
                            msg3.payload = {code:0};
 | 
			
		||||
                        }
 | 
			
		||||
                        if (!msg3) { node.status({}); }
 | 
			
		||||
                        else {
 | 
			
		||||
                            msg.rc = msg3.payload;
 | 
			
		||||
                            if (msg2) { msg2.rc = msg3.payload; }
 | 
			
		||||
                        }
 | 
			
		||||
                        nodeSend([msg,msg2,msg3]);
 | 
			
		||||
                        if (child.tout) { clearTimeout(child.tout); }
 | 
			
		||||
                        delete node.activeProcesses[child.pid];
 | 
			
		||||
                        nodeDone();
 | 
			
		||||
                    });
 | 
			
		||||
                    node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
 | 
			
		||||
                    child.on('error',function() {});
 | 
			
		||||
                    if (node.timer !== 0) {
 | 
			
		||||
                        child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer);
 | 
			
		||||
                    }
 | 
			
		||||
                    node.activeProcesses[child.pid] = child;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.on('close',function() {
 | 
			
		||||
            for (var pid in node.activeProcesses) {
 | 
			
		||||
                /* istanbul ignore else  */
 | 
			
		||||
                if (node.activeProcesses.hasOwnProperty(pid)) {
 | 
			
		||||
                    if (node.activeProcesses[pid].tout) { clearTimeout(node.activeProcesses[pid].tout); }
 | 
			
		||||
                    // console.log("KILLING",pid);
 | 
			
		||||
                    var process = node.activeProcesses[pid];
 | 
			
		||||
                    node.activeProcesses[pid] = null;
 | 
			
		||||
                    process.kill();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            node.activeProcesses = {};
 | 
			
		||||
            node.status({});
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("exec",ExecNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,106 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="rbe">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-func"><i class="fa fa-wrench"></i> <span data-i18n="rbe.label.func"></span></label>
 | 
			
		||||
        <select type="text" id="node-input-func" style="width:70%;">
 | 
			
		||||
            <option value="rbe" data-i18n="rbe.opts.rbe"></option>
 | 
			
		||||
            <option value="rbei" data-i18n="rbe.opts.rbei"></option>
 | 
			
		||||
            <option value="deadbandEq" data-i18n="rbe.opts.deadbandEq"></option>
 | 
			
		||||
            <option value="deadband" data-i18n="rbe.opts.deadband"></option>
 | 
			
		||||
            <option value="narrowbandEq" data-i18n="rbe.opts.narrowbandEq"></option>
 | 
			
		||||
            <option value="narrowband" data-i18n="rbe.opts.narrowband"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" id="node-bandgap">
 | 
			
		||||
        <label for="node-input-gap"> </label>
 | 
			
		||||
        <input type="text" id="node-input-gap" data-i18n="[placeholder]rbe.placeholder.bandgap" style="width:95px;">
 | 
			
		||||
        <select type="text" id="node-input-inout" style="width:54%;">
 | 
			
		||||
            <option value="out" data-i18n="rbe.opts.out"></option>
 | 
			
		||||
            <option value="in" data-i18n="rbe.opts.in"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" id="node-startvalue">
 | 
			
		||||
        <label for="node-input-start"><i class="fa fa-thumb-tack"></i> <span data-i18n="rbe.label.start"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-start" data-i18n="[placeholder]rbe.placeholder.start" style="width:70%;">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-property" style="width:70%;"/>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" style="margin-bottom: 0px;">
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input type="checkbox" id="node-input-septopics" style="display:inline-block; width:20px; vertical-align:baseline;">
 | 
			
		||||
        <label style="width: auto" for="node-input-septopics" data-i18n="rbe.label.septopics"></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input type="text" id="node-input-topi"  style="width:70%;"/>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="rbe.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]rbe.label.name" style="width:70%;">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType("rbe", {
 | 
			
		||||
        color:"#E2D96E",
 | 
			
		||||
        category: 'function',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            func: {value:"rbe"},
 | 
			
		||||
            gap: {value:"",validate:RED.validators.regex(/^(\d*[.]*\d*|)(%|)$/)},
 | 
			
		||||
            start: {value:""},
 | 
			
		||||
            inout: {value:"out"},
 | 
			
		||||
            septopics: {value:true},
 | 
			
		||||
            property: {value:"payload",required:true},
 | 
			
		||||
            topi: {value:"topic",required:true}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "rbe.png",
 | 
			
		||||
        paletteLabel: "filter",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            var ll = (this.func||"").replace("Eq","").replace("rbei",this._("rbe.rbe")).replace("rbe",this._("rbe.rbe"))||this._("rbe.rbe");
 | 
			
		||||
            return this.name||ll||this._("rbe.rbe");
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            if (this.property === undefined) {
 | 
			
		||||
                $("#node-input-property").val("payload");
 | 
			
		||||
            }
 | 
			
		||||
            if (this.septopics === undefined) {
 | 
			
		||||
                $("#node-input-septopics").prop('checked', true);
 | 
			
		||||
            }
 | 
			
		||||
            if (this.topi === undefined) {
 | 
			
		||||
                $("#node-input-topi").val("topic");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $("#node-input-property").typedInput({default:'msg',types:['msg']});
 | 
			
		||||
            $("#node-input-topi").typedInput({default:'msg',types:['msg']});
 | 
			
		||||
            //$( "#node-input-gap" ).spinner({min:0});
 | 
			
		||||
            if ($("#node-input-inout").val() === null) {
 | 
			
		||||
                $("#node-input-inout").val("out");
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-func").on("change",function() {
 | 
			
		||||
                if (($("#node-input-func").val() === "rbe")||($("#node-input-func").val() === "rbei")) {
 | 
			
		||||
                    $("#node-bandgap").hide();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-bandgap").show();
 | 
			
		||||
                }
 | 
			
		||||
                if (($("#node-input-func").val() === "narrowband")||($("#node-input-func").val() === "narrowbandEq")) {
 | 
			
		||||
                    $("#node-startvalue").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-startvalue").hide();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-septopics").on("change", function() {
 | 
			
		||||
                $("#node-input-topi").typedInput("disable",!this.checked);
 | 
			
		||||
            })
 | 
			
		||||
            $("#node-input-topi").typedInput("disable",!!!this.septopics);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,197 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="tls-config">
 | 
			
		||||
    <div class="form-row" class="hide" id="node-config-row-uselocalfiles">
 | 
			
		||||
        <input type="checkbox" id="node-config-input-uselocalfiles" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-config-input-uselocalfiles" style="width: 70%;"><span data-i18n="tls.label.use-local-files"></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label style="width: 120px;"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.cert"></span></label>
 | 
			
		||||
        <span class="tls-config-input-data">
 | 
			
		||||
            <label class="red-ui-button" for="node-config-input-certfile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
 | 
			
		||||
            <input class="hide" type="file" id="node-config-input-certfile">
 | 
			
		||||
            <span id="tls-config-certname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
 | 
			
		||||
            <button class="red-ui-button red-ui-button-small" id="tls-config-button-cert-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
 | 
			
		||||
        </span>
 | 
			
		||||
        <input type="hidden" id="node-config-input-certname">
 | 
			
		||||
        <input type="hidden" id="node-config-input-certdata">
 | 
			
		||||
        <input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-cert" data-i18n="[placeholder]tls.placeholder.cert">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label style="width: 120px;" for="node-config-input-key"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.key"></span></label>
 | 
			
		||||
        <span class="tls-config-input-data">
 | 
			
		||||
            <label class="red-ui-button" for="node-config-input-keyfile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
 | 
			
		||||
            <input class="hide" type="file" id="node-config-input-keyfile">
 | 
			
		||||
            <span id="tls-config-keyname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
 | 
			
		||||
            <button class="red-ui-button red-ui-button-small" id="tls-config-button-key-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
 | 
			
		||||
        </span>
 | 
			
		||||
        <input type="hidden" id="node-config-input-keyname">
 | 
			
		||||
        <input type="hidden" id="node-config-input-keydata">
 | 
			
		||||
        <input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-key" data-i18n="[placeholder]tls.placeholder.key">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label style="width: 100px; margin-left: 20px;" for="node-config-input-passphrase"> <span data-i18n="tls.label.passphrase"></span></label>
 | 
			
		||||
        <input type="password" style="width: calc(100% - 170px);" id="node-config-input-passphrase" data-i18n="[placeholder]tls.placeholder.passphrase">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label style="width: 120px;" for="node-config-input-ca"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.ca"></span></label>
 | 
			
		||||
        <span class="tls-config-input-data">
 | 
			
		||||
            <label class="red-ui-button" for="node-config-input-cafile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
 | 
			
		||||
            <input class="hide" type="file" title=" " id="node-config-input-cafile">
 | 
			
		||||
            <span id="tls-config-caname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
 | 
			
		||||
            <button class="red-ui-button red-ui-button-small" id="tls-config-button-ca-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
 | 
			
		||||
        </span>
 | 
			
		||||
        <input type="hidden" id="node-config-input-caname">
 | 
			
		||||
        <input type="hidden" id="node-config-input-cadata">
 | 
			
		||||
        <input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-ca" data-i18n="[placeholder]tls.placeholder.ca">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <input type="checkbox" id="node-config-input-verifyservercert" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-config-input-verifyservercert" style="width: calc(100% - 170px);" data-i18n="tls.label.verify-server-cert"></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label style="width: 126px;" for="node-config-input-servername"><i class="fa fa-server"></i> <span data-i18n="tls.label.servername"></span></label>
 | 
			
		||||
        <input style="width: calc(100% - 176px);" type="text" id="node-config-input-servername" data-i18n="[placeholder]tls.placeholder.servername">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label style="width: 126px;" for="node-config-input-alpnprotocol"><i class="fa fa-cogs"></i> <span data-i18n="tls.label.alpnprotocol"></span></label>
 | 
			
		||||
        <input style="width: calc(100% - 176px);" type="text" id="node-config-input-alpnprotocol" data-i18n="[placeholder]tls.placeholder.alpnprotocol">
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label style="width: 120px;" for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input style="width: calc(100% - 170px);" type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('tls-config',{
 | 
			
		||||
        category: 'config',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            cert: {value:"", validate: function(v) {
 | 
			
		||||
                var currentKey = $("#node-config-input-key").val();
 | 
			
		||||
                if (currentKey === undefined) {
 | 
			
		||||
                    currentKey = this.key;
 | 
			
		||||
                }
 | 
			
		||||
                return currentKey === '' || v != '';
 | 
			
		||||
            }},
 | 
			
		||||
            key: {value:"", validate: function(v) {
 | 
			
		||||
                var currentCert = $("#node-config-input-cert").val();
 | 
			
		||||
                if (currentCert === undefined) {
 | 
			
		||||
                    currentCert = this.cert;
 | 
			
		||||
                }
 | 
			
		||||
                return currentCert === '' || v != '';
 | 
			
		||||
            }},
 | 
			
		||||
            ca: {value:""},
 | 
			
		||||
            certname: {value:""},
 | 
			
		||||
            keyname: {value:""},
 | 
			
		||||
            caname: {value:""},
 | 
			
		||||
            servername: {value:""},
 | 
			
		||||
            verifyservercert: {value: true},
 | 
			
		||||
            alpnprotocol: {value: ""}
 | 
			
		||||
        },
 | 
			
		||||
        credentials: {
 | 
			
		||||
            certdata: {type:"text"},
 | 
			
		||||
            keydata: {type:"text"},
 | 
			
		||||
            cadata: {type:"text"},
 | 
			
		||||
            passphrase: {type:"password"}
 | 
			
		||||
        },
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name || this._("tls.tls");
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            function updateFileUpload() {
 | 
			
		||||
                if ($("#node-config-input-uselocalfiles").is(':checked')) {
 | 
			
		||||
                    $(".tls-config-input-path").show();
 | 
			
		||||
                    $(".tls-config-input-data").hide();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $(".tls-config-input-data").show();
 | 
			
		||||
                    $(".tls-config-input-path").hide();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-config-input-uselocalfiles").on("click",function() {
 | 
			
		||||
                updateFileUpload();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            function saveFile(property, file) {
 | 
			
		||||
                var dataInputId = "#node-config-input-"+property+"data";
 | 
			
		||||
                var filenameInputId = "#node-config-input-"+property+"name";
 | 
			
		||||
                var filename = file.name;
 | 
			
		||||
                var reader = new FileReader();
 | 
			
		||||
                reader.onload = function(event) {
 | 
			
		||||
                    $("#tls-config-"+property+"name").text(filename);
 | 
			
		||||
                    $(filenameInputId).val(filename);
 | 
			
		||||
                    $(dataInputId).val(event.target.result);
 | 
			
		||||
                }
 | 
			
		||||
                reader.readAsText(file,"UTF-8");
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-config-input-certfile" ).on("change", function() {
 | 
			
		||||
                saveFile("cert", this.files[0]);
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-config-input-keyfile" ).on("change", function() {
 | 
			
		||||
                saveFile("key", this.files[0]);
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-config-input-cafile" ).on("change", function() {
 | 
			
		||||
                saveFile("ca", this.files[0]);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            function clearNameData(prop) {
 | 
			
		||||
                $("#tls-config-"+prop+"name").text("");
 | 
			
		||||
                $("#node-config-input-"+prop+"data").val("");
 | 
			
		||||
                $("#node-config-input-"+prop+"name").val("");
 | 
			
		||||
            }
 | 
			
		||||
            $("#tls-config-button-cert-clear").on("click", function() {
 | 
			
		||||
                clearNameData("cert");
 | 
			
		||||
            });
 | 
			
		||||
            $("#tls-config-button-key-clear").on("click", function() {
 | 
			
		||||
                clearNameData("key");
 | 
			
		||||
            });
 | 
			
		||||
            $("#tls-config-button-ca-clear").on("click", function() {
 | 
			
		||||
                clearNameData("ca");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (RED.settings.tlsConfigDisableLocalFiles) {
 | 
			
		||||
                $("#node-config-row-uselocalfiles").hide();
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-config-row-uselocalfiles").show();
 | 
			
		||||
            }
 | 
			
		||||
            // in case paths were set from old TLS config
 | 
			
		||||
            if(this.cert || this.key || this.ca) {
 | 
			
		||||
                $("#node-config-input-uselocalfiles").prop('checked',true);
 | 
			
		||||
            }
 | 
			
		||||
            $("#tls-config-certname").text(this.certname);
 | 
			
		||||
            $("#tls-config-keyname").text(this.keyname);
 | 
			
		||||
            $("#tls-config-caname").text(this.caname);
 | 
			
		||||
            updateFileUpload();
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            if ($("#node-config-input-uselocalfiles").is(':checked')) {
 | 
			
		||||
                clearNameData("ca");
 | 
			
		||||
                clearNameData("cert");
 | 
			
		||||
                clearNameData("key");
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-config-input-ca").val("");
 | 
			
		||||
                $("#node-config-input-cert").val("");
 | 
			
		||||
                $("#node-config-input-key").val("");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,118 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
var fs = require('fs');
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    function TLSConfig(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.valid = true;
 | 
			
		||||
        this.verifyservercert = n.verifyservercert;
 | 
			
		||||
        var certPath = n.cert.trim();
 | 
			
		||||
        var keyPath = n.key.trim();
 | 
			
		||||
        var caPath = n.ca.trim();
 | 
			
		||||
        this.servername = (n.servername||"").trim();
 | 
			
		||||
        this.alpnprotocol = (n.alpnprotocol||"").trim();
 | 
			
		||||
 | 
			
		||||
        if ((certPath.length > 0) || (keyPath.length > 0) || (caPath.length > 0)) {
 | 
			
		||||
 | 
			
		||||
            if ( (certPath.length > 0) !== (keyPath.length > 0)) {
 | 
			
		||||
                this.valid = false;
 | 
			
		||||
                this.error(RED._("tls.error.missing-file"));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                if (certPath) {
 | 
			
		||||
                    this.cert = fs.readFileSync(certPath);
 | 
			
		||||
                }
 | 
			
		||||
                if (keyPath) {
 | 
			
		||||
                    this.key = fs.readFileSync(keyPath);
 | 
			
		||||
                }
 | 
			
		||||
                if (caPath) {
 | 
			
		||||
                    this.ca = fs.readFileSync(caPath);
 | 
			
		||||
                }
 | 
			
		||||
            } catch(err) {
 | 
			
		||||
                this.valid = false;
 | 
			
		||||
                this.error(err.toString());
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (this.credentials) {
 | 
			
		||||
                var certData = this.credentials.certdata || "";
 | 
			
		||||
                var keyData = this.credentials.keydata || "";
 | 
			
		||||
                var caData = this.credentials.cadata || "";
 | 
			
		||||
 | 
			
		||||
                if ((certData.length > 0) !== (keyData.length > 0)) {
 | 
			
		||||
                    this.valid = false;
 | 
			
		||||
                    this.error(RED._("tls.error.missing-file"));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (certData) {
 | 
			
		||||
                    this.cert = certData;
 | 
			
		||||
                }
 | 
			
		||||
                if (keyData) {
 | 
			
		||||
                    this.key = keyData;
 | 
			
		||||
                }
 | 
			
		||||
                if (caData) {
 | 
			
		||||
                    this.ca = caData;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("tls-config", TLSConfig, {
 | 
			
		||||
        credentials: {
 | 
			
		||||
            certdata: {type:"text"},
 | 
			
		||||
            keydata: {type:"text"},
 | 
			
		||||
            cadata: {type:"text"},
 | 
			
		||||
            passphrase: {type:"password"}
 | 
			
		||||
        },
 | 
			
		||||
        settings: {
 | 
			
		||||
            tlsConfigDisableLocalFiles: {
 | 
			
		||||
                value: false,
 | 
			
		||||
                exportable: true
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    TLSConfig.prototype.addTLSOptions = function(opts) {
 | 
			
		||||
        if (this.valid) {
 | 
			
		||||
            if (this.key) {
 | 
			
		||||
                opts.key = this.key;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.cert) {
 | 
			
		||||
                opts.cert = this.cert;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.ca) {
 | 
			
		||||
                opts.ca = this.ca;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.credentials && this.credentials.passphrase) {
 | 
			
		||||
                opts.passphrase = this.credentials.passphrase;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.servername) {
 | 
			
		||||
                opts.servername = this.servername;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.alpnprotocol) {
 | 
			
		||||
                opts.ALPNProtocols = [this.alpnprotocol];
 | 
			
		||||
            }
 | 
			
		||||
            opts.rejectUnauthorized = this.verifyservercert;
 | 
			
		||||
        }
 | 
			
		||||
        return opts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,129 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="http proxy">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-config-input-name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-config-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
 | 
			
		||||
        <input type="text" id="node-config-input-url" placeholder="http://hostname:port">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <input type="checkbox" id="node-config-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-config-input-useAuth" style="width: 70%;"><span data-i18n="httpin.use-proxyauth"></span></label>
 | 
			
		||||
        <div style="margin-left: 20px" class="node-config-input-useAuth-row hide">
 | 
			
		||||
            <div class="form-row">
 | 
			
		||||
                <label for="node-config-input-username"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
 | 
			
		||||
                <input type="text" id="node-config-input-username">
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-row">
 | 
			
		||||
                <label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
 | 
			
		||||
                <input type="password" id="node-config-input-password">
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" style="margin-bottom:0;">
 | 
			
		||||
        <label><i class="fa fa-list"></i> <span data-i18n="httpin.noproxy-hosts"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-config-input-noproxy-container-row">
 | 
			
		||||
        <ol id="node-config-input-noproxy-container"></ol>
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('http proxy', {
 | 
			
		||||
        category: 'config',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:''},
 | 
			
		||||
            url: {value:'',validate:function(v) { return (v && (v.indexOf('://') !== -1) && (v.trim().indexOf('http') === 0)); }},
 | 
			
		||||
            noproxy: {value:[]}
 | 
			
		||||
        },
 | 
			
		||||
        credentials: {
 | 
			
		||||
            username: {type:'text'},
 | 
			
		||||
            password: {type:'password'}
 | 
			
		||||
        },
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name || this.url || ('http proxy:' + this.id);
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            $('#node-config-input-useAuth').on("change", function() {
 | 
			
		||||
                if ($(this).is(":checked")) {
 | 
			
		||||
                    $('.node-config-input-useAuth-row').show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $('.node-config-input-useAuth-row').hide();
 | 
			
		||||
                    $('#node-config-input-username').val('');
 | 
			
		||||
                    $('#node-config-input-password').val('');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            if (this.credentials.username || this.credentials.has_password) {
 | 
			
		||||
                $('#node-config-input-useAuth').prop('checked', true);
 | 
			
		||||
            } else {
 | 
			
		||||
                $('#node-config-input-useAuth').prop('checked', false);
 | 
			
		||||
            }
 | 
			
		||||
            $('#node-config-input-useAuth').change();
 | 
			
		||||
 | 
			
		||||
            var hostList = $('#node-config-input-noproxy-container')
 | 
			
		||||
                .css({'min-height':'150px','min-width':'450px'})
 | 
			
		||||
                .editableList({
 | 
			
		||||
                    addItem: function(container, index, data) {
 | 
			
		||||
                        var row = $('<div/>')
 | 
			
		||||
                            .css({overflow: 'hidden',whiteSpace: 'nowrap'})
 | 
			
		||||
                            .appendTo(container);
 | 
			
		||||
 | 
			
		||||
                        var hostField = $('<input/>',{class:'node-config-input-host',type:'text',placeholder:'hostname'})
 | 
			
		||||
                            .css({width:'100%'})
 | 
			
		||||
                            .appendTo(row);
 | 
			
		||||
                        if (data.host) {
 | 
			
		||||
                            hostField.val(data.host);
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    removable: true
 | 
			
		||||
                });
 | 
			
		||||
            if (this.noproxy) {
 | 
			
		||||
                for (var i in this.noproxy) {
 | 
			
		||||
                    hostList.editableList('addItem', {host:this.noproxy[i]});
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (hostList.editableList('items').length == 0) {
 | 
			
		||||
                hostList.editableList('addItem', {host:''});
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            var hosts = $('#node-config-input-noproxy-container').editableList('items');
 | 
			
		||||
            var node = this;
 | 
			
		||||
            node.noproxy = [];
 | 
			
		||||
            hosts.each(function(i) {
 | 
			
		||||
                var host = $(this).find('.node-config-input-host').val().trim();
 | 
			
		||||
                if (host) {
 | 
			
		||||
                    node.noproxy.push(host);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        oneditresize: function(size) {
 | 
			
		||||
            var rows = $('#node-config-dialog-edit-form>div:not(.node-config-input-noproxy-container-row)');
 | 
			
		||||
            var height = size.height;
 | 
			
		||||
            for (var i = 0; i < rows.length; i++) {
 | 
			
		||||
                height -= $(rows[i]).outerHeight(true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var editorRow = $('#node-config-dialog-edit-form>div.node-config-input-noproxy-container-row');
 | 
			
		||||
            height -= (parseInt(editorRow.css('margin-top')) + parseInt(editorRow.css('margin-bottom')));
 | 
			
		||||
            $('#node-config-input-noproxy-container').editableList('height',height);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    'use strict';
 | 
			
		||||
 | 
			
		||||
    function HTTPProxyConfig(n) {
 | 
			
		||||
        RED.nodes.createNode(this, n);
 | 
			
		||||
        this.name = n.name;
 | 
			
		||||
        this.url = n.url;
 | 
			
		||||
        this.noproxy = n.noproxy;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('http proxy', HTTPProxyConfig, {
 | 
			
		||||
        credentials: {
 | 
			
		||||
            username: {type:'text'},
 | 
			
		||||
            password: {type:'password'}
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
@@ -1,908 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
<style>
 | 
			
		||||
 | 
			
		||||
    .mqtt-form-row-cols2 > input.mqtt-form-row-col1 {
 | 
			
		||||
        width: calc(35% - 75px);
 | 
			
		||||
    }
 | 
			
		||||
    .mqtt-form-row-cols2 > select.mqtt-form-row-col1 {
 | 
			
		||||
        width: calc(35% - 75px);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .mqtt-form-row-cols2 > label.mqtt-form-row-col2 {
 | 
			
		||||
        width: 100px;
 | 
			
		||||
        margin-left: 42px;
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
    }
 | 
			
		||||
    .mqtt-form-row-cols2 > input.mqtt-form-row-col2 {
 | 
			
		||||
        width: calc(35% - 75px);
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
    }
 | 
			
		||||
    .mqtt-form-row-cols2 > select.mqtt-form-row-col2 {
 | 
			
		||||
        width: calc(35% - 75px);
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
    }
 | 
			
		||||
    .form-row.mqtt5-out > label {
 | 
			
		||||
        width: 130px;
 | 
			
		||||
    }
 | 
			
		||||
    .form-row.mqtt-flags-row > label {
 | 
			
		||||
        vertical-align: top;
 | 
			
		||||
    }
 | 
			
		||||
    .form-row.mqtt-flags-row > .mqtt-flags {
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        width: 70%
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .form-row.mqtt-flags-row > .mqtt-flags > .mqtt-flag > label {
 | 
			
		||||
        display: block;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
    .form-row.mqtt-flags-row > .mqtt-flags > .mqtt-flag > label > input {
 | 
			
		||||
        position: relative;
 | 
			
		||||
        vertical-align: bottom;
 | 
			
		||||
        top: -2px;
 | 
			
		||||
        width: 15px;
 | 
			
		||||
        height: 15px;
 | 
			
		||||
    }
 | 
			
		||||
    .form-row-mqtt5 {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
    .form-row-mqtt5.form-row-mqtt5-active:not(.form-row-mqtt-static-disabled) {
 | 
			
		||||
        display: block
 | 
			
		||||
    }
 | 
			
		||||
    .form-row-mqtt-static-disabled {
 | 
			
		||||
        display: none;
 | 
			
		||||
        /* opacity: 0.3;
 | 
			
		||||
        pointer-events: none; */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="mqtt in">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-broker">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-topicType" data-i18n="mqtt.label.action"></label>
 | 
			
		||||
        <select id="node-input-topicType" style="width: 70%">
 | 
			
		||||
            <option value="topic" data-i18n="mqtt.label.staticTopic"></option>
 | 
			
		||||
            <option value="dynamic" data-i18n="mqtt.label.dynamicTopic"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
        <input type="hidden" id="node-input-inputs">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row form-row-mqtt-static">
 | 
			
		||||
        <label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row form-row-mqtt-static">
 | 
			
		||||
        <label for="node-input-qos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
 | 
			
		||||
        <select id="node-input-qos" style="width:125px !important">
 | 
			
		||||
            <option value="0">0</option>
 | 
			
		||||
            <option value="1">1</option>
 | 
			
		||||
            <option value="2">2</option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row mqtt-flags-row form-row-mqtt5 form-row-mqtt-static">
 | 
			
		||||
        <label for="node-input-nl" ><i class="fa fa-flag"></i> <span data-i18n="mqtt.label.flags">Flags</span></label>
 | 
			
		||||
        <div class="mqtt-flags">
 | 
			
		||||
            <div class="mqtt-flag">
 | 
			
		||||
                <label for="node-input-nl">
 | 
			
		||||
                    <input type="checkbox" id="node-input-nl">
 | 
			
		||||
                    <span data-i18n="mqtt.label.nl"></span>
 | 
			
		||||
                </label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="mqtt-flag">
 | 
			
		||||
                <label for="node-input-rap">
 | 
			
		||||
                    <input type="checkbox" id="node-input-rap">
 | 
			
		||||
                    <span data-i18n="mqtt.label.rap"></span>
 | 
			
		||||
                </label>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row form-row-mqtt5 form-row-mqtt-static">
 | 
			
		||||
        <label for="node-input-rh" style="width:100%"><i class="fa fa-tag"></i> <span data-i18n="mqtt.label.rh"></span></label>
 | 
			
		||||
        <select id="node-input-rh" style="margin-left: 104px; width: 70%">
 | 
			
		||||
            <option value="0" data-i18n="mqtt.label.rh0"></option>
 | 
			
		||||
            <option value="1" data-i18n="mqtt.label.rh1"></option>
 | 
			
		||||
            <option value="2" data-i18n="mqtt.label.rh2"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-datatype"><i class="fa fa-sign-out"></i> <span data-i18n="mqtt.label.output"></span></label>
 | 
			
		||||
        <select id="node-input-datatype" style="width:70%;">
 | 
			
		||||
            <option value="auto" data-i18n="mqtt.output.auto"></option>
 | 
			
		||||
            <option value="buffer" data-i18n="mqtt.output.buffer"></option>
 | 
			
		||||
            <option value="utf8" data-i18n="mqtt.output.string"></option>
 | 
			
		||||
            <option value="json" data-i18n="mqtt.output.json"></option>
 | 
			
		||||
            <option value="base64" data-i18n="mqtt.output.base64"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="mqtt out">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-broker">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row mqtt-form-row-cols2">
 | 
			
		||||
        <label for="node-input-qos" class="mqtt-form-row-col1"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
 | 
			
		||||
        <select id="node-input-qos" class="mqtt-form-row-col1">
 | 
			
		||||
            <option value=""></option>
 | 
			
		||||
            <option value="0">0</option>
 | 
			
		||||
            <option value="1">1</option>
 | 
			
		||||
            <option value="2">2</option>
 | 
			
		||||
        </select>
 | 
			
		||||
 | 
			
		||||
        <label for="node-input-retain" class="mqtt-form-row-col2"><i class="fa fa-history"></i> <span data-i18n="mqtt.retain"></span></label>
 | 
			
		||||
        <select id="node-input-retain" class="mqtt-form-row-col2" >
 | 
			
		||||
            <option value=""></option>
 | 
			
		||||
            <option value="false" data-i18n="mqtt.false"></option>
 | 
			
		||||
            <option value="true" data-i18n="mqtt.true"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
        <label for="node-input-userProps"><span data-i18n="mqtt.label.userProperties"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-userProps" style="width: calc(100% - 166px);">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
        <label for="node-input-respTopic"><span data-i18n="mqtt.label.responseTopic"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-respTopic" style="width: calc(100% - 166px);">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
        <label for="node-input-correl"><span data-i18n="mqtt.label.correlationData"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-correl" style="width: calc(100% - 166px);">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
        <label for="node-input-contentType"><span data-i18n="mqtt.label.contentType"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-contentType" style="width: calc(100% - 166px);">
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row mqtt-form-row-cols2 mqtt5 mqtt5-out">
 | 
			
		||||
        <label for="node-input-expiry" class="mqtt-form-row-col1"><span data-i18n="mqtt.label.expiry"></span></label>
 | 
			
		||||
        <input id="node-input-expiry"  style="width: calc(100% - 166px);" class="mqtt-form-row-col1" >
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-tips"><span data-i18n="mqtt.tip"></span></div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="mqtt-broker">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <ul style="min-width: 600px; margin-bottom: 20px;" id="node-config-mqtt-broker-tabs"></ul>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id="node-config-mqtt-broker-tabs-content" style="min-height:150px;">
 | 
			
		||||
        <div id="mqtt-broker-tab-connection" style="display:none">
 | 
			
		||||
            <div class="form-row node-input-broker">
 | 
			
		||||
                <label for="node-config-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
 | 
			
		||||
                <input type="text" id="node-config-input-broker" style="width: calc(100% - 300px);" data-i18n="[placeholder]mqtt.label.example">
 | 
			
		||||
                <label for="node-config-input-port" style="margin-left:20px; width:43px; "> <span data-i18n="mqtt.label.port"></span></label>
 | 
			
		||||
                <input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:55px">
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-row" style="margin-bottom:0">
 | 
			
		||||
                <input type="checkbox" id="node-config-input-autoConnect" style="margin: 0 5px 0 104px; display: inline-block; width: auto;">
 | 
			
		||||
                <label for="node-config-input-autoConnect" style="width: auto"><span data-i18n="mqtt.label.auto-connect"></span></label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-row" style="height: 34px;">
 | 
			
		||||
                <input type="checkbox" id="node-config-input-usetls" style="height: 34px; margin: 0 5px 0 104px; display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
                <label for="node-config-input-usetls" style="width: 100px; line-height: 34px;"><span data-i18n="mqtt.label.use-tls"></span></label>
 | 
			
		||||
                <span id="node-config-row-tls" class="hide"><input style="width: 320px;" type="text" id="node-config-input-tls"></span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-row">
 | 
			
		||||
                <label for="node-config-input-protocolVersion"><i class="fa fa-cog"></i> <span data-i18n="mqtt.label.protocolVersion"></span></label>
 | 
			
		||||
                <select id="node-config-input-protocolVersion" style="width:70%;">
 | 
			
		||||
                    <option value="3" data-i18n="mqtt.label.protocolVersion3"></option>
 | 
			
		||||
                    <option value="4" data-i18n="mqtt.label.protocolVersion4"></option>
 | 
			
		||||
                    <option value="5" data-i18n="mqtt.label.protocolVersion5"></option>
 | 
			
		||||
                </select>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-row">
 | 
			
		||||
                <label for="node-config-input-clientid"><i class="fa fa-tag"></i> <span data-i18n="mqtt.label.clientid"></span></label>
 | 
			
		||||
                <input type="text" id="node-config-input-clientid" data-i18n="[placeholder]mqtt.placeholder.clientid">
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-row">
 | 
			
		||||
                <label for="node-config-input-keepalive"><i class="fa fa-heartbeat"></i> <span data-i18n="mqtt.label.keepalive"></span></label>
 | 
			
		||||
                <input type="number" min="0" id="node-config-input-keepalive" style="width: 100px">
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-row" style="margin-bottom:0">
 | 
			
		||||
                <label style="vertical-align:top;"><i class="fa fa-info"></i> <span data-i18n="mqtt.label.session"></span></label>
 | 
			
		||||
                <div style="display: inline-block; width:calc(100% - 110px)">
 | 
			
		||||
                    <div class="form-row">
 | 
			
		||||
                        <label for="node-config-input-cleansession" style="width: auto;">
 | 
			
		||||
                            <input type="checkbox" id="node-config-input-cleansession" style="position: relative;vertical-align: bottom; top: -2px; width: 15px;height: 15px;">
 | 
			
		||||
                            <span id="node-config-input-cleansession-label" data-i18n="mqtt.label.cleansession"></span>
 | 
			
		||||
                        </label>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5">
 | 
			
		||||
                        <label style="width:auto" for="node-config-input-sessionExpiry"><span data-i18n="mqtt.label.sessionExpiry"></span></label>
 | 
			
		||||
                        <input type="number" min="0" id="node-config-input-sessionExpiry" style="width: 100px" >
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-row mqtt5">
 | 
			
		||||
                <label style="width: 125px;" for="node-config-input-userProps"><span data-i18n="mqtt.label.userProperties"></span></label>
 | 
			
		||||
                <input type="text" id="node-config-input-userProps" style="width: calc(100% - 166px);">
 | 
			
		||||
            </div>
 | 
			
		||||
            <br>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="mqtt-broker-tab-security" style="display:none">
 | 
			
		||||
            <div class="form-row">
 | 
			
		||||
                <label for="node-config-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
 | 
			
		||||
                <input type="text" id="node-config-input-user">
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-row">
 | 
			
		||||
                <label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
 | 
			
		||||
                <input type="password" id="node-config-input-password">
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="mqtt-broker-tab-messages" style="display:none">
 | 
			
		||||
            <div id="mqtt-broker-section-birth">
 | 
			
		||||
                <div class="red-ui-palette-header">
 | 
			
		||||
                    <i class="fa fa-angle-down"></i><span data-i18n="mqtt.sections-label.birth-message"></span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="section-content" style="padding:10px 0 0 10px">
 | 
			
		||||
                    <div class="form-row">
 | 
			
		||||
                        <label style="width: 100px !important;" for="node-config-input-birthTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
 | 
			
		||||
                        <input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-birthTopic" data-i18n="[placeholder]mqtt.placeholder.birth-topic">
 | 
			
		||||
                        <label style="margin-left: 10px; width: 90px !important;" for="node-config-input-birthRetain"><i class="fa fa-history"></i> <span data-i18n="mqtt.label.retain"></span></label>
 | 
			
		||||
                        <select id="node-config-input-birthRetain" style="width:75px !important">
 | 
			
		||||
                            <option value="false" data-i18n="mqtt.false"></option>
 | 
			
		||||
                            <option value="true" data-i18n="mqtt.true"></option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row">
 | 
			
		||||
                        <label style="width: 100px !important;" for="node-config-input-birthPayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
 | 
			
		||||
                        <input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-birthPayload" style="width:300px" data-i18n="[placeholder]common.label.payload">
 | 
			
		||||
                        <label style="margin-left: 10px; width: 90px !important;" for="node-config-input-birthQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
 | 
			
		||||
                        <select id="node-config-input-birthQos" style="width:75px !important">
 | 
			
		||||
                            <option value="0">0</option>
 | 
			
		||||
                            <option value="1">1</option>
 | 
			
		||||
                            <option value="2">2</option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-birth-contentType" data-i18n="mqtt.label.contentType"></label>
 | 
			
		||||
                        <input type="text" style="width:calc(100% - 200px);" id="node-config-input-birth-contentType">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-birth-props" data-i18n="mqtt.label.userProperties"></label>
 | 
			
		||||
                        <input type="text" style="width:calc(100% - 200px);" id="node-config-input-birth-props">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-birth-respTopic"><span data-i18n="mqtt.label.responseTopic"></span></label>
 | 
			
		||||
                        <input type="text" id="node-config-input-birth-respTopic" style="width: calc(100% - 200px);">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-birth-correl"><span data-i18n="mqtt.label.correlationData"></span></label>
 | 
			
		||||
                        <input type="text" id="node-config-input-birth-correl" style="width: calc(100% - 200px);">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-birth-expiry"><span data-i18n="mqtt.label.expiry"></span></label>
 | 
			
		||||
                        <input id="node-config-input-birth-expiry"  style="width: calc(100% - 200px);">
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div id="mqtt-broker-section-close">
 | 
			
		||||
                <div class="red-ui-palette-header">
 | 
			
		||||
                    <i class="fa fa-angle-down"></i><span data-i18n="mqtt.sections-label.close-message"></span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="section-content" style="padding:10px 0 0 10px">
 | 
			
		||||
                    <div class="form-row">
 | 
			
		||||
                        <label style="width: 100px !important;" for="node-config-input-closeTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
 | 
			
		||||
                        <input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-closeTopic" style="width:300px" data-i18n="[placeholder]mqtt.placeholder.close-topic">
 | 
			
		||||
                        <label style="margin-left: 10px; width: 90px !important;" for="node-config-input-closeRetain"><i class="fa fa-history"></i> <span data-i18n="mqtt.label.retain"></span></label>
 | 
			
		||||
                        <select id="node-config-input-closeRetain" style="width:75px !important">
 | 
			
		||||
                            <option value="false" data-i18n="mqtt.false"></option>
 | 
			
		||||
                            <option value="true" data-i18n="mqtt.true"></option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row">
 | 
			
		||||
                        <label style="width: 100px !important;" for="node-config-input-closePayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
 | 
			
		||||
                        <input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-closePayload" style="width:300px" data-i18n="[placeholder]common.label.payload">
 | 
			
		||||
                        <label style="margin-left: 10px; width: 90px !important;" for="node-config-input-closeQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
 | 
			
		||||
                        <select id="node-config-input-closeQos" style="width:75px !important">
 | 
			
		||||
                            <option value="0">0</option>
 | 
			
		||||
                            <option value="1">1</option>
 | 
			
		||||
                            <option value="2">2</option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-close-contentType" data-i18n="mqtt.label.contentType"></label>
 | 
			
		||||
                        <input type="text" style="width:calc(100% - 200px);" id="node-config-input-close-contentType">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-close-props" data-i18n="mqtt.label.userProperties"></label>
 | 
			
		||||
                        <input type="text" style="width:calc(100% - 200px);" id="node-config-input-close-props">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-close-respTopic"><span data-i18n="mqtt.label.responseTopic"></span></label>
 | 
			
		||||
                        <input type="text" id="node-config-input-close-respTopic" style="width: calc(100% - 200px);">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-close-correl"><span data-i18n="mqtt.label.correlationData"></span></label>
 | 
			
		||||
                        <input type="text" id="node-config-input-close-correl" style="width: calc(100% - 200px);">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-close-expiry"><span data-i18n="mqtt.label.expiry"></span></label>
 | 
			
		||||
                        <input id="node-config-input-close-expiry"  style="width: calc(100% - 200px);">
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div id="mqtt-broker-section-will">
 | 
			
		||||
                <div class="red-ui-palette-header">
 | 
			
		||||
                    <i class="fa fa-angle-down"></i><span data-i18n="mqtt.sections-label.will-message"></span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="section-content" style="padding:10px 0 0 10px">
 | 
			
		||||
                    <div class="form-row">
 | 
			
		||||
                        <label style="width: 100px !important;" for="node-config-input-willTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
 | 
			
		||||
                        <input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-willTopic" style="width:300px" data-i18n="[placeholder]mqtt.placeholder.will-topic">
 | 
			
		||||
                        <label style="margin-left: 10px; width: 90px !important;" for="node-config-input-willRetain"><i class="fa fa-history"></i> <span data-i18n="mqtt.label.retain"></span></label>
 | 
			
		||||
                        <select id="node-config-input-willRetain" style="width:75px !important">
 | 
			
		||||
                            <option value="false" data-i18n="mqtt.false"></option>
 | 
			
		||||
                            <option value="true" data-i18n="mqtt.true"></option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row">
 | 
			
		||||
                        <label style="width: 100px !important;" for="node-config-input-willPayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
 | 
			
		||||
                        <input style="width: calc(100% - 300px) !important" type="text" id="node-config-input-willPayload" style="width:300px" data-i18n="[placeholder]common.label.payload">
 | 
			
		||||
                        <label style="margin-left: 10px; width: 90px !important;" for="node-config-input-willQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
 | 
			
		||||
                        <select id="node-config-input-willQos" style="width:75px !important">
 | 
			
		||||
                            <option value="0">0</option>
 | 
			
		||||
                            <option value="1">1</option>
 | 
			
		||||
                            <option value="2">2</option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5">
 | 
			
		||||
                        <label><span data-i18n="mqtt.label.delay"></span></label>
 | 
			
		||||
                        <input type="number" min="0" id="node-config-input-will-delay" style="width: 100px" >
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-will-contentType" data-i18n="mqtt.label.contentType"></label>
 | 
			
		||||
                        <input type="text" style="width:calc(100% - 200px);" id="node-config-input-will-contentType">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-will-props" data-i18n="mqtt.label.userProperties"></label>
 | 
			
		||||
                        <input type="text" style="width:calc(100% - 200px);" id="node-config-input-will-props">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-will-respTopic"><span data-i18n="mqtt.label.responseTopic"></span></label>
 | 
			
		||||
                        <input type="text" id="node-config-input-will-respTopic" style="width: calc(100% - 200px);">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-will-correl"><span data-i18n="mqtt.label.correlationData"></span></label>
 | 
			
		||||
                        <input type="text" id="node-config-input-will-correl" style="width: calc(100% - 200px);">
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-row mqtt5 mqtt5-out">
 | 
			
		||||
                        <label for="node-config-input-will-expiry"><span data-i18n="mqtt.label.expiry"></span></label>
 | 
			
		||||
                        <input id="node-config-input-will-expiry"  style="width: calc(100% - 200px);">
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
(function() {
 | 
			
		||||
 | 
			
		||||
    var typedInputNoneOpt = { value: 'none', label: '', hasValue: false };
 | 
			
		||||
    var makeTypedInputOpt = function(value){
 | 
			
		||||
        return {
 | 
			
		||||
            value: value,
 | 
			
		||||
            label:value,
 | 
			
		||||
            hasValue: false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    var contentTypeOpts = [
 | 
			
		||||
        typedInputNoneOpt,
 | 
			
		||||
        makeTypedInputOpt("application/json"),
 | 
			
		||||
        makeTypedInputOpt("application/octet-stream"),
 | 
			
		||||
        makeTypedInputOpt("text/csv"),
 | 
			
		||||
        makeTypedInputOpt("text/html"),
 | 
			
		||||
        makeTypedInputOpt("text/plain"),
 | 
			
		||||
        {value:"other", label:""}
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    function getDefaultContentType(value) {
 | 
			
		||||
        var defaultContentType;
 | 
			
		||||
        var matchedContentType = contentTypeOpts.filter(function(v) {
 | 
			
		||||
            return v.value === value;
 | 
			
		||||
        })
 | 
			
		||||
        if (matchedContentType.length > 0) {
 | 
			
		||||
            defaultContentType = matchedContentType[0].value;
 | 
			
		||||
        }
 | 
			
		||||
        if (value && !defaultContentType) {
 | 
			
		||||
            defaultContentType = 'other';
 | 
			
		||||
        }
 | 
			
		||||
        return defaultContentType || 'none'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('mqtt-broker',{
 | 
			
		||||
        category: 'config',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            broker: {value:"",required:true},
 | 
			
		||||
            port: {value:1883,required:false,validate:RED.validators.number(true)},
 | 
			
		||||
            tls: {type:"tls-config",required: false},
 | 
			
		||||
            clientid: {value:"", validate: function(v) {
 | 
			
		||||
                if ($("#node-config-input-clientid").length) {
 | 
			
		||||
                    // Currently editing the node
 | 
			
		||||
                    return $("#node-config-input-cleansession").is(":checked") || (v||"").length > 0;
 | 
			
		||||
                } else {
 | 
			
		||||
                    return (this.cleansession===undefined || this.cleansession) || (v||"").length > 0;
 | 
			
		||||
                }
 | 
			
		||||
            }},
 | 
			
		||||
            autoConnect: {value: true},
 | 
			
		||||
            usetls: {value: false},
 | 
			
		||||
            verifyservercert: { value: false},
 | 
			
		||||
            compatmode: { value: false},
 | 
			
		||||
            protocolVersion: { value: 4},
 | 
			
		||||
            keepalive: {value:60,validate:RED.validators.number()},
 | 
			
		||||
            cleansession: {value: true},
 | 
			
		||||
            birthTopic: {value:""},
 | 
			
		||||
            birthQos: {value:"0"},
 | 
			
		||||
            birthRetain: {value:false},
 | 
			
		||||
            birthPayload: {value:""},
 | 
			
		||||
            birthMsg: { value: {}},
 | 
			
		||||
            closeTopic: {value:""},
 | 
			
		||||
            closeQos: {value:"0"},
 | 
			
		||||
            closeRetain: {value:false},
 | 
			
		||||
            closePayload: {value:""},
 | 
			
		||||
            closeMsg: { value: {}},
 | 
			
		||||
            willTopic: {value:""},
 | 
			
		||||
            willQos: {value:"0"},
 | 
			
		||||
            willRetain: {value:false},
 | 
			
		||||
            willPayload: {value:""},
 | 
			
		||||
            willMsg: { value: {}},
 | 
			
		||||
            sessionExpiry: {value:0}
 | 
			
		||||
        },
 | 
			
		||||
        credentials: {
 | 
			
		||||
            user: {type:"text"},
 | 
			
		||||
            password: {type: "password"}
 | 
			
		||||
        },
 | 
			
		||||
        label: function() {
 | 
			
		||||
            if (this.name) {
 | 
			
		||||
                return this.name;
 | 
			
		||||
            }
 | 
			
		||||
            var b = this.broker;
 | 
			
		||||
            if (!b) { b = "undefined"; }
 | 
			
		||||
            var lab = "";
 | 
			
		||||
            lab = (this.clientid?this.clientid+"@":"")+b;
 | 
			
		||||
            if (b.indexOf("://") === -1){
 | 
			
		||||
                if (!this.port){ lab = lab + ":1883"; }
 | 
			
		||||
                else { lab = lab + ":" + this.port; }
 | 
			
		||||
            }
 | 
			
		||||
            return lab;
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function () {
 | 
			
		||||
            var tabs = RED.tabs.create({
 | 
			
		||||
                id: "node-config-mqtt-broker-tabs",
 | 
			
		||||
                onchange: function(tab) {
 | 
			
		||||
                    $("#node-config-mqtt-broker-tabs-content").children().hide();
 | 
			
		||||
                    $("#" + tab.id).show();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            tabs.addTab({
 | 
			
		||||
                id: "mqtt-broker-tab-connection",
 | 
			
		||||
                label: this._("mqtt.tabs-label.connection")
 | 
			
		||||
            });
 | 
			
		||||
            tabs.addTab({
 | 
			
		||||
                id: "mqtt-broker-tab-security",
 | 
			
		||||
                label: this._("mqtt.tabs-label.security")
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            tabs.addTab({
 | 
			
		||||
                id: "mqtt-broker-tab-messages",
 | 
			
		||||
                label: this._("mqtt.tabs-label.messages")
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            function setUpSection(sectionId, v5Opts, isExpanded) {
 | 
			
		||||
                var birthMessageSection = $("#mqtt-broker-section-"+sectionId);
 | 
			
		||||
                var paletteHeader = birthMessageSection.find('.red-ui-palette-header');
 | 
			
		||||
                var twistie = paletteHeader.find('i');
 | 
			
		||||
                var sectionContent = birthMessageSection.find('.section-content');
 | 
			
		||||
 | 
			
		||||
                function toggleSection(expanded) {
 | 
			
		||||
                    twistie.toggleClass('expanded', expanded);
 | 
			
		||||
                    sectionContent.toggle(expanded);
 | 
			
		||||
                }
 | 
			
		||||
                paletteHeader.on("click", function(e) {
 | 
			
		||||
                    e.preventDefault();
 | 
			
		||||
                    var isExpanded = twistie.hasClass('expanded');
 | 
			
		||||
                    toggleSection(!isExpanded);
 | 
			
		||||
                });
 | 
			
		||||
                toggleSection(isExpanded);
 | 
			
		||||
                $("#node-config-input-"+sectionId+"-contentType").val(v5Opts?v5Opts.contentType:"").typedInput({
 | 
			
		||||
                    default: getDefaultContentType(v5Opts?v5Opts.contentType:""),
 | 
			
		||||
                    types: contentTypeOpts,
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                $("#node-config-input-"+sectionId+"-props").val(v5Opts?v5Opts.userProps:"").typedInput({
 | 
			
		||||
                    default:  !(v5Opts?v5Opts.userProps:null)? 'none':'json',
 | 
			
		||||
                    types: [typedInputNoneOpt, 'json'],
 | 
			
		||||
                });
 | 
			
		||||
                $("#node-config-input-"+sectionId+"-respTopic").val(v5Opts?v5Opts.respTopic:"").typedInput({
 | 
			
		||||
                    default:  !(v5Opts?v5Opts.respTopic:null)? 'none':'str',
 | 
			
		||||
                    types: [typedInputNoneOpt, 'str'],
 | 
			
		||||
                });
 | 
			
		||||
                $("#node-config-input-"+sectionId+"-correl").val(v5Opts?v5Opts.correl:"").typedInput({
 | 
			
		||||
                    default:  !(v5Opts?v5Opts.correl:null)? 'none':'str',
 | 
			
		||||
                    types: [typedInputNoneOpt, 'str'],
 | 
			
		||||
                });
 | 
			
		||||
                $("#node-config-input-"+sectionId+"-expiry").val(v5Opts?v5Opts.expiry:"").typedInput({
 | 
			
		||||
                    default:  !(v5Opts?v5Opts.expiry:null)? 'none':'num',
 | 
			
		||||
                    types: [typedInputNoneOpt, 'num'],
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // show first section if none are set so the user gets the idea
 | 
			
		||||
            var showBirthSection = this.birthTopic !== ""
 | 
			
		||||
                || this.willTopic === ""
 | 
			
		||||
                && this.birthTopic === ""
 | 
			
		||||
                && this.closeTopic == "";
 | 
			
		||||
            setUpSection('birth', this.birthMsg, showBirthSection);
 | 
			
		||||
            setUpSection('close', this.closeMsg, this.closeTopic !== "");
 | 
			
		||||
            setUpSection('will', this.willMsg, this.willTopic !== "");
 | 
			
		||||
 | 
			
		||||
            if (this.willMsg) {
 | 
			
		||||
                $("#node-config-input-will-delay").val(this.willMsg.delay);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            setTimeout(function() { tabs.resize(); },0);
 | 
			
		||||
            if (typeof this.cleansession === 'undefined') {
 | 
			
		||||
                this.cleansession = true;
 | 
			
		||||
                $("#node-config-input-cleansession").prop("checked",true);
 | 
			
		||||
            }
 | 
			
		||||
            if (typeof this.usetls === 'undefined') {
 | 
			
		||||
                this.usetls = false;
 | 
			
		||||
                $("#node-config-input-usetls").prop("checked",false);
 | 
			
		||||
            }
 | 
			
		||||
            if (typeof this.autoConnect === 'undefined') {
 | 
			
		||||
                this.autoConnect = true;
 | 
			
		||||
                $("#node-config-input-autoConnect").prop("checked",true);
 | 
			
		||||
            }
 | 
			
		||||
            if (this.compatmode === 'true' || this.compatmode === true) {
 | 
			
		||||
                delete this.compatmode;
 | 
			
		||||
                this.protocolVersion = 4;
 | 
			
		||||
            }
 | 
			
		||||
            if (typeof this.protocolVersion === 'undefined') {
 | 
			
		||||
                this.protocolVersion = 4;
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-config-input-protocolVersion").on("change", function() {
 | 
			
		||||
                var v5 = $("#node-config-input-protocolVersion").val() == "5";
 | 
			
		||||
                if(v5) {
 | 
			
		||||
                    $("#node-config-input-cleansession-label").text(RED._("node-red:mqtt.label.cleanstart"))
 | 
			
		||||
                    $("div.form-row.mqtt5").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-config-input-cleansession-label").text(RED._("node-red:mqtt.label.cleansession"))
 | 
			
		||||
                    $("div.form-row.mqtt5").hide();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-config-input-protocolVersion").val(this.protocolVersion);
 | 
			
		||||
            $("#node-config-input-userProps").typedInput({
 | 
			
		||||
                default:  !this.userProps ? 'none':'json',
 | 
			
		||||
                types: [typedInputNoneOpt, 'json']
 | 
			
		||||
            });
 | 
			
		||||
            if (typeof this.keepalive === 'undefined') {
 | 
			
		||||
                this.keepalive = 15;
 | 
			
		||||
                $("#node-config-input-keepalive").val(this.keepalive);
 | 
			
		||||
            }
 | 
			
		||||
            if (typeof this.birthQos === 'undefined') {
 | 
			
		||||
                this.birthQos = "0";
 | 
			
		||||
                $("#node-config-input-birthQos").val("0");
 | 
			
		||||
            }
 | 
			
		||||
            if (typeof this.closeQos === 'undefined') {
 | 
			
		||||
                this.willQos = "0";
 | 
			
		||||
                $("#node-config-input-willQos").val("0");
 | 
			
		||||
            }
 | 
			
		||||
            if (typeof this.willQos === 'undefined') {
 | 
			
		||||
                this.willQos = "0";
 | 
			
		||||
                $("#node-config-input-willQos").val("0");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            function updateTLSOptions() {
 | 
			
		||||
                if ($("#node-config-input-usetls").is(':checked')) {
 | 
			
		||||
                    $("#node-config-row-tls").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-config-row-tls").hide();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            updateTLSOptions();
 | 
			
		||||
            $("#node-config-input-usetls").on("click",function() {
 | 
			
		||||
                updateTLSOptions();
 | 
			
		||||
            });
 | 
			
		||||
            var node = this;
 | 
			
		||||
            function updateClientId() {
 | 
			
		||||
                if ($("#node-config-input-cleansession").is(":checked")) {
 | 
			
		||||
                    $("#node-config-input-clientid").attr("placeholder",node._("mqtt.placeholder.clientid"));
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-config-input-clientid").attr("placeholder",node._("mqtt.placeholder.clientid-nonclean"));
 | 
			
		||||
                }
 | 
			
		||||
                $("#node-config-input-clientid").trigger("change");
 | 
			
		||||
            }
 | 
			
		||||
            setTimeout(updateClientId,0);
 | 
			
		||||
            $("#node-config-input-cleansession").on("click",function() {
 | 
			
		||||
                updateClientId();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            function updatePortEntry(){
 | 
			
		||||
                var disabled = $("#node-config-input-port").prop("disabled");
 | 
			
		||||
                if ($("#node-config-input-broker").val().indexOf("://") === -1){
 | 
			
		||||
                    if (disabled){
 | 
			
		||||
                        $("#node-config-input-port").prop("disabled", false);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    if (!disabled){
 | 
			
		||||
                        $("#node-config-input-port").prop("disabled", true);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-config-input-broker").on("change", function() {
 | 
			
		||||
                updatePortEntry();
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-config-input-broker").on( "keyup", function() {
 | 
			
		||||
                updatePortEntry();
 | 
			
		||||
            });
 | 
			
		||||
            setTimeout(updatePortEntry,50);
 | 
			
		||||
            setTimeout(function() {
 | 
			
		||||
                $("#node-config-input-protocolVersion").trigger("change");
 | 
			
		||||
            },50);
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            if (!$("#node-config-input-usetls").is(':checked')) {
 | 
			
		||||
                $("#node-config-input-tls").val("");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var v5 = $("#node-config-input-protocolVersion").val() == "5";
 | 
			
		||||
 | 
			
		||||
            function saveV5Message(section) {
 | 
			
		||||
                var msg = {};
 | 
			
		||||
                if ($("#node-config-input-"+section+"Topic").val().trim().length > 0) {
 | 
			
		||||
                    var contentType = $("#node-config-input-"+section+"-contentType").val().trim();
 | 
			
		||||
                    if (contentType === '') {
 | 
			
		||||
                        contentType = $("#node-config-input-"+section+"-contentType").typedInput('type');
 | 
			
		||||
                        if (contentType === 'none' || contentType === 'other') {
 | 
			
		||||
                            contentType = "";
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (contentType) {
 | 
			
		||||
                        msg.contentType = contentType;
 | 
			
		||||
                    }
 | 
			
		||||
                    var props = $("#node-config-input-"+section+"-props").val().trim();
 | 
			
		||||
                    if (props) {
 | 
			
		||||
                        msg.userProps = props;
 | 
			
		||||
                    }
 | 
			
		||||
                    var resp = $("#node-config-input-"+section+"-respTopic").val().trim();
 | 
			
		||||
                    if (props) {
 | 
			
		||||
                        msg.respTopic = resp;
 | 
			
		||||
                    }
 | 
			
		||||
                    var correl = $("#node-config-input-"+section+"-correl").val().trim();
 | 
			
		||||
                    if (correl) {
 | 
			
		||||
                        msg.correl = correl;
 | 
			
		||||
                    }
 | 
			
		||||
                    var expiry = $("#node-config-input-"+section+"-expiry").val().trim();
 | 
			
		||||
                    if (expiry) {
 | 
			
		||||
                        msg.expiry = expiry;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return msg;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (v5) {
 | 
			
		||||
                this.birthMsg = saveV5Message("birth");
 | 
			
		||||
                this.closeMsg = saveV5Message("close");
 | 
			
		||||
                this.willMsg = saveV5Message("will");
 | 
			
		||||
                var willDelay = $("#node-config-input-will-delay").val();
 | 
			
		||||
                if (willDelay) {
 | 
			
		||||
                    this.willMsg.delay = willDelay;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                this.willMsg = {};
 | 
			
		||||
                this.birthMsg = {};
 | 
			
		||||
                this.closeMsg = {};
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('mqtt in',{
 | 
			
		||||
        category: 'network',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            topic: {
 | 
			
		||||
                value:"",
 | 
			
		||||
                validate: function(v) {
 | 
			
		||||
                    var isDynamic = this.inputs === 1;
 | 
			
		||||
                    var topicTypeSelect = $("#node-input-topicType");
 | 
			
		||||
                    if (topicTypeSelect.length) {
 | 
			
		||||
                        isDynamic = topicTypeSelect.val()==='dynamic'
 | 
			
		||||
                    }
 | 
			
		||||
                    return isDynamic || ((!!v) && RED.validators.regex(/^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/)(v));
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            qos: {value: "2"},
 | 
			
		||||
            datatype: {value:"auto",required:true},
 | 
			
		||||
            broker: {type:"mqtt-broker", required:true},
 | 
			
		||||
            // subscriptionIdentifier: {value:0},
 | 
			
		||||
            nl: {value:false},
 | 
			
		||||
            rap: {value:true},
 | 
			
		||||
            rh: {value:0},
 | 
			
		||||
            inputs: {value:0},
 | 
			
		||||
        },
 | 
			
		||||
        color:"#d8bfd8",
 | 
			
		||||
        inputs:0,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "bridge.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            var label = "mqtt";
 | 
			
		||||
            if(this.topicType !== "dynamic" && this.topic) {
 | 
			
		||||
                label = this.topic;
 | 
			
		||||
            }
 | 
			
		||||
            return this.name || label;
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            const node = this;
 | 
			
		||||
            const isV5Broker = function() {
 | 
			
		||||
                var confNode = RED.nodes.node($("#node-input-broker").val());
 | 
			
		||||
                return confNode && confNode.protocolVersion === "5";
 | 
			
		||||
            }
 | 
			
		||||
            const isDynamic = function() {
 | 
			
		||||
                return $('#node-input-topicType').val() === "dynamic";
 | 
			
		||||
            }
 | 
			
		||||
            const updateVisibility = function() {
 | 
			
		||||
                var v5 = isV5Broker();
 | 
			
		||||
                var dynamic = isDynamic();
 | 
			
		||||
                $("div.form-row-mqtt5").toggleClass("form-row-mqtt5-active",!!v5);
 | 
			
		||||
                $("div.form-row.form-row-mqtt-static").toggleClass("form-row-mqtt-static-disabled", !!dynamic)
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-broker").on("change",function(d){
 | 
			
		||||
                updateVisibility();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $('#node-input-topicType').on("change", function () {
 | 
			
		||||
                $("#node-input-inputs").val(isDynamic() ? 1 : 0);
 | 
			
		||||
                updateVisibility();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (this.inputs === 1) {
 | 
			
		||||
                $('#node-input-topicType').val('dynamic')
 | 
			
		||||
            } else {
 | 
			
		||||
                $('#node-input-topicType').val('topic')
 | 
			
		||||
            }
 | 
			
		||||
            $('#node-input-topicType').trigger("change");
 | 
			
		||||
 | 
			
		||||
            if (this.qos === undefined) {
 | 
			
		||||
                $("#node-input-qos").val("2");
 | 
			
		||||
            }
 | 
			
		||||
            if (this.datatype === undefined) {
 | 
			
		||||
                $("#node-input-datatype").val("auto");
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            if ($('#node-input-topicType').val() === "dynamic") {
 | 
			
		||||
                $('#node-input-topic').val("");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('mqtt out',{
 | 
			
		||||
        category: 'network',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            topic: {value:""},
 | 
			
		||||
            qos: {value:""},
 | 
			
		||||
            retain: {value:""},
 | 
			
		||||
            respTopic: {value:""},
 | 
			
		||||
            contentType: {value:""},
 | 
			
		||||
            userProps: {value:''},
 | 
			
		||||
            correl: {value:''},
 | 
			
		||||
            expiry: {value:''},
 | 
			
		||||
            broker: {type:"mqtt-broker", required:true}
 | 
			
		||||
        },
 | 
			
		||||
        color:"#d8bfd8",
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:0,
 | 
			
		||||
        icon: "bridge.svg",
 | 
			
		||||
        align: "right",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||this.topic||"mqtt";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var that = this;
 | 
			
		||||
 | 
			
		||||
            function showHideDynamicFields() {
 | 
			
		||||
                var confNode = RED.nodes.node($("#node-input-broker").val());
 | 
			
		||||
                var v5 = confNode && confNode.protocolVersion == "5";
 | 
			
		||||
                if(v5) {
 | 
			
		||||
                    $("div.form-row.mqtt5").show();
 | 
			
		||||
                    var t = $("#node-input-respTopic").typedInput("type");
 | 
			
		||||
                    if (t == 'none') {
 | 
			
		||||
                        $("#node-input-correl").parent().hide();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $("#node-input-correl").parent().show();
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("div.form-row.mqtt5").hide();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $("#node-input-broker").on("change",function(d){
 | 
			
		||||
                showHideDynamicFields();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var respTopicTI = $("#node-input-respTopic").typedInput({
 | 
			
		||||
                default: !this.respTopic ? 'none':'str',
 | 
			
		||||
                types: [typedInputNoneOpt,  'str'],
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var correlTI = $("#node-input-correl").typedInput({
 | 
			
		||||
                default: !this.correl ? 'none':'str',
 | 
			
		||||
                types: [typedInputNoneOpt,  'str']
 | 
			
		||||
            });
 | 
			
		||||
            //show / hide correlation data depending on respTopic
 | 
			
		||||
            respTopicTI.on("change", showHideDynamicFields);
 | 
			
		||||
            respTopicTI.triggerHandler("change");
 | 
			
		||||
 | 
			
		||||
            $("#node-input-userProps").typedInput({
 | 
			
		||||
                default:  !this.userProps ? 'none':'json',
 | 
			
		||||
                types: [typedInputNoneOpt, 'json'],
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-expiry").typedInput({
 | 
			
		||||
                default: !this.expiry ? 'none':'num',
 | 
			
		||||
                types: [typedInputNoneOpt,  'num']
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-contentType").typedInput({
 | 
			
		||||
                default: getDefaultContentType(this.contentType),
 | 
			
		||||
                types: contentTypeOpts
 | 
			
		||||
            })
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
 | 
			
		||||
            var contentType = $("#node-input-contentType").val().trim();
 | 
			
		||||
            if (contentType === '') {
 | 
			
		||||
                contentType = $("#node-input-contentType").typedInput('type');
 | 
			
		||||
                if (contentType === 'none' || contentType === 'other') {
 | 
			
		||||
                    contentType = "";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-contentType").val(contentType)
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
})();
 | 
			
		||||
</script>
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,273 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="http in">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
 | 
			
		||||
        <select type="text" id="node-input-method" style="width:70%;">
 | 
			
		||||
        <option value="get">GET</option>
 | 
			
		||||
        <option value="post">POST</option>
 | 
			
		||||
        <option value="put">PUT</option>
 | 
			
		||||
        <option value="delete">DELETE</option>
 | 
			
		||||
        <option value="patch">PATCH</option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row form-row-http-in-upload hide">
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input type="checkbox" id="node-input-upload" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-input-upload" style="width: 70%;" data-i18n="httpin.label.upload"></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
 | 
			
		||||
        <input id="node-input-url" type="text" placeholder="/url">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row row-swagger-doc">
 | 
			
		||||
        <label for="node-input-swaggerDoc"><i class="fa fa-file-text-o"></i> <span data-i18n="httpin.label.doc"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-swaggerDoc">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id="node-input-tip" class="form-tips"><span data-i18n="httpin.tip.in"></span><code><span id="node-input-path"></span></code>.</div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="http response">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-statusCode"><i class="fa fa-long-arrow-left"></i> <span data-i18n="httpin.label.status"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-statusCode" placeholder="msg.statusCode">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" style="margin-bottom:0;">
 | 
			
		||||
        <label><i class="fa fa-list"></i> <span data-i18n="httpin.label.headers"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-input-headers-container-row">
 | 
			
		||||
        <ol id="node-input-headers-container"></ol>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-tips"><span data-i18n="[html]httpin.tip.res"></span></div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
(function() {
 | 
			
		||||
    RED.nodes.registerType('http in',{
 | 
			
		||||
        category: 'network',
 | 
			
		||||
        color:"rgb(231, 231, 174)",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            url: {value:"",required:true},
 | 
			
		||||
            method: {value:"get",required:true},
 | 
			
		||||
            upload: {value:false},
 | 
			
		||||
            swaggerDoc: {type:"swagger-doc", required:false}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:0,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "white-globe.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            if (this.name) {
 | 
			
		||||
                return this.name;
 | 
			
		||||
            } else if (this.url) {
 | 
			
		||||
                var root = RED.settings.httpNodeRoot;
 | 
			
		||||
                if (root.slice(-1) != "/") {
 | 
			
		||||
                    root = root+"/";
 | 
			
		||||
                }
 | 
			
		||||
                if (this.url.charAt(0) == "/") {
 | 
			
		||||
                    root += this.url.slice(1);
 | 
			
		||||
                } else {
 | 
			
		||||
                    root += this.url;
 | 
			
		||||
                }
 | 
			
		||||
                return "["+this.method+"] "+root;
 | 
			
		||||
            } else {
 | 
			
		||||
                return "http";
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var root = RED.settings.httpNodeRoot;
 | 
			
		||||
            if (root.slice(-1) == "/") {
 | 
			
		||||
                root = root.slice(0,-1);
 | 
			
		||||
            }
 | 
			
		||||
            if (root == "") {
 | 
			
		||||
                $("#node-input-tip").hide();
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-input-path").html(root);
 | 
			
		||||
                $("#node-input-tip").show();
 | 
			
		||||
            }
 | 
			
		||||
            if(!RED.nodes.getType("swagger-doc")){
 | 
			
		||||
                $('.row-swagger-doc').hide();
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-method").on("change", function() {
 | 
			
		||||
                if ($(this).val() === "post") {
 | 
			
		||||
                    $(".form-row-http-in-upload").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $(".form-row-http-in-upload").hide();
 | 
			
		||||
                }
 | 
			
		||||
            }).change();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
    var headerTypes = [
 | 
			
		||||
        {value:"content-type",label:"Content-Type",hasValue: false},
 | 
			
		||||
        {value:"location",label:"Location",hasValue: false},
 | 
			
		||||
        {value:"other",label:RED._("node-red:httpin.label.other"),icon:"red/images/typedInput/az.png"}
 | 
			
		||||
       ]
 | 
			
		||||
    var contentTypes = [
 | 
			
		||||
        {value:"application/json",label:"application/json",hasValue: false},
 | 
			
		||||
        {value:"application/xml",label:"application/xml",hasValue: false},
 | 
			
		||||
        {value:"text/css",label:"text/css",hasValue: false},
 | 
			
		||||
        {value:"text/html",label:"text/html",hasValue: false},
 | 
			
		||||
        {value:"text/plain",label:"text/plain",hasValue: false},
 | 
			
		||||
        {value:"image/gif",label:"image/gif",hasValue: false},
 | 
			
		||||
        {value:"image/png",label:"image/png",hasValue: false},
 | 
			
		||||
        {value:"other",label:RED._("node-red:httpin.label.other"),icon:"red/images/typedInput/az.png"}
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('http response',{
 | 
			
		||||
        category: 'network',
 | 
			
		||||
        color:"rgb(231, 231, 174)",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            statusCode: {value:"",validate: RED.validators.number(true)},
 | 
			
		||||
            headers: {value:{}}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:0,
 | 
			
		||||
        align: "right",
 | 
			
		||||
        icon: "white-globe.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||("http"+(this.statusCode?" ("+this.statusCode+")":""));
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var headerList = $("#node-input-headers-container").css('min-height','150px').css('min-width','450px').editableList({
 | 
			
		||||
                addItem: function(container,i,header) {
 | 
			
		||||
                    var row = $('<div/>').css({
 | 
			
		||||
                        overflow: 'hidden',
 | 
			
		||||
                        whiteSpace: 'nowrap',
 | 
			
		||||
                        display: 'flex'
 | 
			
		||||
                    }).appendTo(container);
 | 
			
		||||
                    var propertNameCell = $('<div/>').css({'flex-grow':1}).appendTo(row);
 | 
			
		||||
                    var propertyName = $('<input/>',{class:"node-input-header-name",type:"text", style:"width: 100%"})
 | 
			
		||||
                        .appendTo(propertNameCell)
 | 
			
		||||
                        .typedInput({types:headerTypes});
 | 
			
		||||
 | 
			
		||||
                    var propertyValueCell = $('<div/>').css({'flex-grow':1,'margin-left':'10px'}).appendTo(row);
 | 
			
		||||
                    var propertyValue = $('<input/>',{class:"node-input-header-value",type:"text",style:"width: 100%"})
 | 
			
		||||
                        .appendTo(propertyValueCell)
 | 
			
		||||
                        .typedInput({types:
 | 
			
		||||
                            header.h === 'content-type'?contentTypes:[{value:"other",label:"other",icon:"red/images/typedInput/az.png"}]
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                    var matchedType = headerTypes.filter(function(ht) {
 | 
			
		||||
                        return ht.value === header.h
 | 
			
		||||
                    });
 | 
			
		||||
                    if (matchedType.length === 0) {
 | 
			
		||||
                        propertyName.typedInput('type','other');
 | 
			
		||||
                        propertyName.typedInput('value',header.h);
 | 
			
		||||
                        propertyValue.typedInput('value',header.v);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        propertyName.typedInput('type',header.h);
 | 
			
		||||
 | 
			
		||||
                        if (header.h === "content-type") {
 | 
			
		||||
                            matchedType = contentTypes.filter(function(ct) {
 | 
			
		||||
                                return ct.value === header.v;
 | 
			
		||||
                            });
 | 
			
		||||
                            if (matchedType.length === 0) {
 | 
			
		||||
                                propertyValue.typedInput('type','other');
 | 
			
		||||
                                propertyValue.typedInput('value',header.v);
 | 
			
		||||
                            } else {
 | 
			
		||||
                                propertyValue.typedInput('type',header.v);
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            propertyValue.typedInput('value',header.v);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    matchedType = headerTypes.filter(function(ht) {
 | 
			
		||||
                        return ht.value === header.h
 | 
			
		||||
                    });
 | 
			
		||||
                    if (matchedType.length === 0) {
 | 
			
		||||
                        propertyName.typedInput('type','other');
 | 
			
		||||
                        propertyName.typedInput('value',header.h);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        propertyName.typedInput('type',header.h);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    propertyName.on('change',function(event) {
 | 
			
		||||
                        var type = propertyName.typedInput('type');
 | 
			
		||||
                        if (type === 'content-type') {
 | 
			
		||||
                            propertyValue.typedInput('types',contentTypes);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            propertyValue.typedInput('types',[{value:"other",label:"other",icon:"red/images/typedInput/az.png"}]);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                },
 | 
			
		||||
                removable: true
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (this.headers) {
 | 
			
		||||
                for (var key in this.headers) {
 | 
			
		||||
                    if (this.headers.hasOwnProperty(key)) {
 | 
			
		||||
                        headerList.editableList('addItem',{h:key,v:this.headers[key]});
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            var headers = $("#node-input-headers-container").editableList('items');
 | 
			
		||||
            var node = this;
 | 
			
		||||
            node.headers = {};
 | 
			
		||||
            headers.each(function(i) {
 | 
			
		||||
                var header = $(this);
 | 
			
		||||
                var keyType = header.find(".node-input-header-name").typedInput('type');
 | 
			
		||||
                var keyValue = header.find(".node-input-header-name").typedInput('value');
 | 
			
		||||
                var valueType = header.find(".node-input-header-value").typedInput('type');
 | 
			
		||||
                var valueValue = header.find(".node-input-header-value").typedInput('value');
 | 
			
		||||
                var key = keyType;
 | 
			
		||||
                var value = valueType;
 | 
			
		||||
                if (keyType === 'other') {
 | 
			
		||||
                    key = keyValue;
 | 
			
		||||
                }
 | 
			
		||||
                if (valueType === 'other') {
 | 
			
		||||
                    value = valueValue;
 | 
			
		||||
                }
 | 
			
		||||
                if (key !== '') {
 | 
			
		||||
                    node.headers[key] = value;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        oneditresize: function(size) {
 | 
			
		||||
            var rows = $("#dialog-form>div:not(.node-input-headers-container-row)");
 | 
			
		||||
            var height = size.height;
 | 
			
		||||
            for (var i=0; i<rows.length; i++) {
 | 
			
		||||
                height -= $(rows[i]).outerHeight(true);
 | 
			
		||||
            }
 | 
			
		||||
            var editorRow = $("#dialog-form>div.node-input-headers-container-row");
 | 
			
		||||
            height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
 | 
			
		||||
 | 
			
		||||
            $("#node-input-headers-container").editableList('height',height);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
})();
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,356 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    var bodyParser = require("body-parser");
 | 
			
		||||
    var multer = require("multer");
 | 
			
		||||
    var cookieParser = require("cookie-parser");
 | 
			
		||||
    var getBody = require('raw-body');
 | 
			
		||||
    var cors = require('cors');
 | 
			
		||||
    var onHeaders = require('on-headers');
 | 
			
		||||
    var typer = require('content-type');
 | 
			
		||||
    var mediaTyper = require('media-typer');
 | 
			
		||||
    var isUtf8 = require('is-utf8');
 | 
			
		||||
    var hashSum = require("hash-sum");
 | 
			
		||||
 | 
			
		||||
    function rawBodyParser(req, res, next) {
 | 
			
		||||
        if (req.skipRawBodyParser) { next(); } // don't parse this if told to skip
 | 
			
		||||
        if (req._body) { return next(); }
 | 
			
		||||
        req.body = "";
 | 
			
		||||
        req._body = true;
 | 
			
		||||
 | 
			
		||||
        var isText = true;
 | 
			
		||||
        var checkUTF = false;
 | 
			
		||||
 | 
			
		||||
        if (req.headers['content-type']) {
 | 
			
		||||
            var contentType = typer.parse(req.headers['content-type'])
 | 
			
		||||
            if (contentType.type) {
 | 
			
		||||
                var parsedType = mediaTyper.parse(contentType.type);
 | 
			
		||||
                if (parsedType.type === "text") {
 | 
			
		||||
                    isText = true;
 | 
			
		||||
                } else if (parsedType.subtype === "xml" || parsedType.suffix === "xml") {
 | 
			
		||||
                    isText = true;
 | 
			
		||||
                } else if (parsedType.type !== "application") {
 | 
			
		||||
                    isText = false;
 | 
			
		||||
                } else if ((parsedType.subtype !== "octet-stream") 
 | 
			
		||||
                    && (parsedType.subtype !== "cbor")
 | 
			
		||||
                    && (parsedType.subtype !== "x-protobuf")) {
 | 
			
		||||
                    checkUTF = true;
 | 
			
		||||
                } else {
 | 
			
		||||
                    // application/octet-stream or application/cbor
 | 
			
		||||
                    isText = false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        getBody(req, {
 | 
			
		||||
            length: req.headers['content-length'],
 | 
			
		||||
            encoding: isText ? "utf8" : null
 | 
			
		||||
        }, function (err, buf) {
 | 
			
		||||
            if (err) { return next(err); }
 | 
			
		||||
            if (!isText && checkUTF && isUtf8(buf)) {
 | 
			
		||||
                buf = buf.toString()
 | 
			
		||||
            }
 | 
			
		||||
            req.body = buf;
 | 
			
		||||
            next();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var corsSetup = false;
 | 
			
		||||
 | 
			
		||||
    function createRequestWrapper(node,req) {
 | 
			
		||||
        // This misses a bunch of properties (eg headers). Before we use this function
 | 
			
		||||
        // need to ensure it captures everything documented by Express and HTTP modules.
 | 
			
		||||
        var wrapper = {
 | 
			
		||||
            _req: req
 | 
			
		||||
        };
 | 
			
		||||
        var toWrap = [
 | 
			
		||||
            "param",
 | 
			
		||||
            "get",
 | 
			
		||||
            "is",
 | 
			
		||||
            "acceptsCharset",
 | 
			
		||||
            "acceptsLanguage",
 | 
			
		||||
            "app",
 | 
			
		||||
            "baseUrl",
 | 
			
		||||
            "body",
 | 
			
		||||
            "cookies",
 | 
			
		||||
            "fresh",
 | 
			
		||||
            "hostname",
 | 
			
		||||
            "ip",
 | 
			
		||||
            "ips",
 | 
			
		||||
            "originalUrl",
 | 
			
		||||
            "params",
 | 
			
		||||
            "path",
 | 
			
		||||
            "protocol",
 | 
			
		||||
            "query",
 | 
			
		||||
            "route",
 | 
			
		||||
            "secure",
 | 
			
		||||
            "signedCookies",
 | 
			
		||||
            "stale",
 | 
			
		||||
            "subdomains",
 | 
			
		||||
            "xhr",
 | 
			
		||||
            "socket" // TODO: tidy this up
 | 
			
		||||
        ];
 | 
			
		||||
        toWrap.forEach(function(f) {
 | 
			
		||||
            if (typeof req[f] === "function") {
 | 
			
		||||
                wrapper[f] = function() {
 | 
			
		||||
                    node.warn(RED._("httpin.errors.deprecated-call",{method:"msg.req."+f}));
 | 
			
		||||
                    var result = req[f].apply(req,arguments);
 | 
			
		||||
                    if (result === req) {
 | 
			
		||||
                        return wrapper;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return result;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                wrapper[f] = req[f];
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return wrapper;
 | 
			
		||||
    }
 | 
			
		||||
    function createResponseWrapper(node,res) {
 | 
			
		||||
        var wrapper = {
 | 
			
		||||
            _res: res
 | 
			
		||||
        };
 | 
			
		||||
        var toWrap = [
 | 
			
		||||
            "append",
 | 
			
		||||
            "attachment",
 | 
			
		||||
            "cookie",
 | 
			
		||||
            "clearCookie",
 | 
			
		||||
            "download",
 | 
			
		||||
            "end",
 | 
			
		||||
            "format",
 | 
			
		||||
            "get",
 | 
			
		||||
            "json",
 | 
			
		||||
            "jsonp",
 | 
			
		||||
            "links",
 | 
			
		||||
            "location",
 | 
			
		||||
            "redirect",
 | 
			
		||||
            "render",
 | 
			
		||||
            "send",
 | 
			
		||||
            "sendfile",
 | 
			
		||||
            "sendFile",
 | 
			
		||||
            "sendStatus",
 | 
			
		||||
            "set",
 | 
			
		||||
            "status",
 | 
			
		||||
            "type",
 | 
			
		||||
            "vary"
 | 
			
		||||
        ];
 | 
			
		||||
        toWrap.forEach(function(f) {
 | 
			
		||||
            wrapper[f] = function() {
 | 
			
		||||
                node.warn(RED._("httpin.errors.deprecated-call",{method:"msg.res."+f}));
 | 
			
		||||
                var result = res[f].apply(res,arguments);
 | 
			
		||||
                if (result === res) {
 | 
			
		||||
                    return wrapper;
 | 
			
		||||
                } else {
 | 
			
		||||
                    return result;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        return wrapper;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var corsHandler = function(req,res,next) { next(); }
 | 
			
		||||
 | 
			
		||||
    if (RED.settings.httpNodeCors) {
 | 
			
		||||
        corsHandler = cors(RED.settings.httpNodeCors);
 | 
			
		||||
        RED.httpNode.options("*",corsHandler);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function HTTPIn(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        if (RED.settings.httpNodeRoot !== false) {
 | 
			
		||||
 | 
			
		||||
            if (!n.url) {
 | 
			
		||||
                this.warn(RED._("httpin.errors.missing-path"));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            this.url = n.url;
 | 
			
		||||
            if (this.url[0] !== '/') {
 | 
			
		||||
                this.url = '/'+this.url;
 | 
			
		||||
            }
 | 
			
		||||
            this.method = n.method;
 | 
			
		||||
            this.upload = n.upload;
 | 
			
		||||
            this.swaggerDoc = n.swaggerDoc;
 | 
			
		||||
 | 
			
		||||
            var node = this;
 | 
			
		||||
 | 
			
		||||
            this.errorHandler = function(err,req,res,next) {
 | 
			
		||||
                node.warn(err);
 | 
			
		||||
                res.sendStatus(500);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.callback = function(req,res) {
 | 
			
		||||
                var msgid = RED.util.generateId();
 | 
			
		||||
                res._msgid = msgid;
 | 
			
		||||
                if (node.method.match(/^(post|delete|put|options|patch)$/)) {
 | 
			
		||||
                    node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res),payload:req.body});
 | 
			
		||||
                } else if (node.method == "get") {
 | 
			
		||||
                    node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res),payload:req.query});
 | 
			
		||||
                } else {
 | 
			
		||||
                    node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res)});
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var httpMiddleware = function(req,res,next) { next(); }
 | 
			
		||||
 | 
			
		||||
            if (RED.settings.httpNodeMiddleware) {
 | 
			
		||||
                if (typeof RED.settings.httpNodeMiddleware === "function" || Array.isArray(RED.settings.httpNodeMiddleware)) {
 | 
			
		||||
                    httpMiddleware = RED.settings.httpNodeMiddleware;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var maxApiRequestSize = RED.settings.apiMaxLength || '5mb';
 | 
			
		||||
            var jsonParser = bodyParser.json({limit:maxApiRequestSize});
 | 
			
		||||
            var urlencParser = bodyParser.urlencoded({limit:maxApiRequestSize,extended:true});
 | 
			
		||||
 | 
			
		||||
            var metricsHandler = function(req,res,next) { next(); }
 | 
			
		||||
            if (this.metric()) {
 | 
			
		||||
                metricsHandler = function(req, res, next) {
 | 
			
		||||
                    var startAt = process.hrtime();
 | 
			
		||||
                    onHeaders(res, function() {
 | 
			
		||||
                        if (res._msgid) {
 | 
			
		||||
                            var diff = process.hrtime(startAt);
 | 
			
		||||
                            var ms = diff[0] * 1e3 + diff[1] * 1e-6;
 | 
			
		||||
                            var metricResponseTime = ms.toFixed(3);
 | 
			
		||||
                            var metricContentLength = res.getHeader("content-length");
 | 
			
		||||
                            //assuming that _id has been set for res._metrics in HttpOut node!
 | 
			
		||||
                            node.metric("response.time.millis", {_msgid:res._msgid} , metricResponseTime);
 | 
			
		||||
                            node.metric("response.content-length.bytes", {_msgid:res._msgid} , metricContentLength);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    next();
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var multipartParser = function(req,res,next) { next(); }
 | 
			
		||||
            if (this.upload) {
 | 
			
		||||
                var mp = multer({ storage: multer.memoryStorage() }).any();
 | 
			
		||||
                multipartParser = function(req,res,next) {
 | 
			
		||||
                    mp(req,res,function(err) {
 | 
			
		||||
                        req._body = true;
 | 
			
		||||
                        next(err);
 | 
			
		||||
                    })
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.method == "get") {
 | 
			
		||||
                RED.httpNode.get(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,this.callback,this.errorHandler);
 | 
			
		||||
            } else if (this.method == "post") {
 | 
			
		||||
                RED.httpNode.post(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,multipartParser,rawBodyParser,this.callback,this.errorHandler);
 | 
			
		||||
            } else if (this.method == "put") {
 | 
			
		||||
                RED.httpNode.put(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
 | 
			
		||||
            } else if (this.method == "patch") {
 | 
			
		||||
                RED.httpNode.patch(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
 | 
			
		||||
            } else if (this.method == "delete") {
 | 
			
		||||
                RED.httpNode.delete(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.on("close",function() {
 | 
			
		||||
                var node = this;
 | 
			
		||||
                RED.httpNode._router.stack.forEach(function(route,i,routes) {
 | 
			
		||||
                    if (route.route && route.route.path === node.url && route.route.methods[node.method]) {
 | 
			
		||||
                        routes.splice(i,1);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            this.warn(RED._("httpin.errors.not-created"));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("http in",HTTPIn);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function HTTPOut(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        var node = this;
 | 
			
		||||
        this.headers = n.headers||{};
 | 
			
		||||
        this.statusCode = n.statusCode;
 | 
			
		||||
        this.on("input",function(msg,_send,done) {
 | 
			
		||||
            if (msg.res) {
 | 
			
		||||
                var headers = RED.util.cloneMessage(node.headers);
 | 
			
		||||
                if (msg.headers) {
 | 
			
		||||
                    if (msg.headers.hasOwnProperty('x-node-red-request-node')) {
 | 
			
		||||
                        var headerHash = msg.headers['x-node-red-request-node'];
 | 
			
		||||
                        delete msg.headers['x-node-red-request-node'];
 | 
			
		||||
                        var hash = hashSum(msg.headers);
 | 
			
		||||
                        if (hash === headerHash) {
 | 
			
		||||
                            delete msg.headers;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (msg.headers) {
 | 
			
		||||
                        for (var h in msg.headers) {
 | 
			
		||||
                            if (msg.headers.hasOwnProperty(h) && !headers.hasOwnProperty(h)) {
 | 
			
		||||
                                headers[h] = msg.headers[h];
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (Object.keys(headers).length > 0) {
 | 
			
		||||
                    msg.res._res.set(headers);
 | 
			
		||||
                }
 | 
			
		||||
                if (msg.cookies) {
 | 
			
		||||
                    for (var name in msg.cookies) {
 | 
			
		||||
                        if (msg.cookies.hasOwnProperty(name)) {
 | 
			
		||||
                            if (msg.cookies[name] === null || msg.cookies[name].value === null) {
 | 
			
		||||
                                if (msg.cookies[name]!==null) {
 | 
			
		||||
                                    msg.res._res.clearCookie(name,msg.cookies[name]);
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    msg.res._res.clearCookie(name);
 | 
			
		||||
                                }
 | 
			
		||||
                            } else if (typeof msg.cookies[name] === 'object') {
 | 
			
		||||
                                msg.res._res.cookie(name,msg.cookies[name].value,msg.cookies[name]);
 | 
			
		||||
                            } else {
 | 
			
		||||
                                msg.res._res.cookie(name,msg.cookies[name]);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                var statusCode = node.statusCode || msg.statusCode || 200;
 | 
			
		||||
                if (typeof msg.payload == "object" && !Buffer.isBuffer(msg.payload)) {
 | 
			
		||||
                    msg.res._res.status(statusCode).jsonp(msg.payload);
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (msg.res._res.get('content-length') == null) {
 | 
			
		||||
                        var len;
 | 
			
		||||
                        if (msg.payload == null) {
 | 
			
		||||
                            len = 0;
 | 
			
		||||
                        } else if (Buffer.isBuffer(msg.payload)) {
 | 
			
		||||
                            len = msg.payload.length;
 | 
			
		||||
                        } else if (typeof msg.payload == "number") {
 | 
			
		||||
                            len = Buffer.byteLength(""+msg.payload);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            len = Buffer.byteLength(msg.payload);
 | 
			
		||||
                        }
 | 
			
		||||
                        msg.res._res.set('content-length', len);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (typeof msg.payload === "number") {
 | 
			
		||||
                        msg.payload = ""+msg.payload;
 | 
			
		||||
                    }
 | 
			
		||||
                    msg.res._res.status(statusCode).send(msg.payload);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                node.warn(RED._("httpin.errors.no-response"));
 | 
			
		||||
            }
 | 
			
		||||
            done();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("http response",HTTPOut);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,249 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="http request">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
 | 
			
		||||
        <select type="text" id="node-input-method" style="width:70%;">
 | 
			
		||||
        <option value="GET">GET</option>
 | 
			
		||||
        <option value="POST">POST</option>
 | 
			
		||||
        <option value="PUT">PUT</option>
 | 
			
		||||
        <option value="DELETE">DELETE</option>
 | 
			
		||||
        <option value="HEAD">HEAD</option>
 | 
			
		||||
        <option value="use" data-i18n="httpin.setby"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
 | 
			
		||||
        <input id="node-input-url" type="text" placeholder="http://">
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row node-input-paytoqs-row">
 | 
			
		||||
        <label for="node-input-paytoqs"><span data-i18n="common.label.payload"></span></label>
 | 
			
		||||
        <select id="node-input-paytoqs" style="width: 70%;">
 | 
			
		||||
            <option value="ignore" data-i18n="httpin.label.paytoqs.ignore"></option>
 | 
			
		||||
            <option value="query" data-i18n="httpin.label.paytoqs.query"></option>
 | 
			
		||||
            <option value="body" data-i18n="httpin.label.paytoqs.body"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <input type="checkbox" id="node-input-usetls" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-input-usetls" style="width: auto" data-i18n="httpin.use-tls"></label>
 | 
			
		||||
        <div id="node-row-tls" class="hide">
 | 
			
		||||
            <label style="width: auto; margin-left: 20px; margin-right: 10px;" for="node-input-tls"><span data-i18n="httpin.tls-config"></span></label><input type="text" style="width: 300px" id="node-input-tls">
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <input type="checkbox" id="node-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-input-useAuth" style="width: 70%;"><span data-i18n="httpin.basicauth"></span></label>
 | 
			
		||||
        <div style="margin-left: 20px" class="node-input-useAuth-row hide">
 | 
			
		||||
            <div class="form-row">
 | 
			
		||||
                <label for="node-input-authType-select"><i class="fa fa-user-secret "></i> <span data-i18n="httpin.label.authType"></span></label>
 | 
			
		||||
                <select type="text" id="node-input-authType-select" style="width:70%;">
 | 
			
		||||
                    <option value="basic"  data-i18n="httpin.basic"></option>
 | 
			
		||||
                    <option value="digest" data-i18n="httpin.digest"></option>
 | 
			
		||||
                    <option value="bearer" data-i18n="httpin.bearer"></option>
 | 
			
		||||
                </select>
 | 
			
		||||
                <input type="hidden" id="node-input-authType">
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-row node-input-basic-row">
 | 
			
		||||
                <label for="node-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
 | 
			
		||||
                <input type="text" id="node-input-user">
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-row">
 | 
			
		||||
                <label for="node-input-password"> <i class="fa fa-lock"></i> <span data-i18n="common.label.password" id="node-span-password"></span><span data-i18n="httpin.label.bearerToken" id="node-span-token" style="display:none"></span></label>
 | 
			
		||||
                <input type="password" id="node-input-password">
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <input type="checkbox" id="node-input-persist" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-input-persist" style="width: auto" data-i18n="httpin.persist"></label>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <input type="checkbox" id="node-input-useProxy" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-input-useProxy" style="width: auto;"><span data-i18n="httpin.use-proxy"></span></label>
 | 
			
		||||
        <div id="node-input-useProxy-row" class="hide">
 | 
			
		||||
            <label style="width: auto; margin-left: 20px; margin-right: 10px;" for="node-input-proxy"><i class="fa fa-globe"></i> <span data-i18n="httpin.proxy-config"></span></label><input type="text" style="width: 270px" id="node-input-proxy">
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <input type="checkbox" id="node-input-senderr" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-input-senderr" style="width: auto" data-i18n="httpin.senderr"></label>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-ret"><i class="fa fa-arrow-left"></i> <span data-i18n="httpin.label.return"></span></label>
 | 
			
		||||
        <select type="text" id="node-input-ret" style="width:70%;">
 | 
			
		||||
        <option value="txt" data-i18n="httpin.utf8"></option>
 | 
			
		||||
        <option value="bin" data-i18n="httpin.binary"></option>
 | 
			
		||||
        <option value="obj" data-i18n="httpin.json"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-tips" id="tip-json" hidden><span data-i18n="httpin.tip.req"></span></div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('http request',{
 | 
			
		||||
        category: 'network',
 | 
			
		||||
        color:"rgb(231, 231, 174)",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            method:{value:"GET"},
 | 
			
		||||
            ret: {value:"txt"},
 | 
			
		||||
            paytoqs: {value: false},
 | 
			
		||||
            url:{value:"",validate:function(v) { return (v.trim().length === 0) || (v.indexOf("://") === -1) || (v.trim().indexOf("http") === 0)} },
 | 
			
		||||
            tls: {type:"tls-config",required: false},
 | 
			
		||||
            persist: {value:false},
 | 
			
		||||
            proxy: {type:"http proxy",required: false},
 | 
			
		||||
            authType: {value: ""},
 | 
			
		||||
            senderr: {value: false}
 | 
			
		||||
        },
 | 
			
		||||
        credentials: {
 | 
			
		||||
            user: {type:"text"},
 | 
			
		||||
            password: {type: "password"}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        outputLabels: function(i) {
 | 
			
		||||
            return ({
 | 
			
		||||
                txt: this._("httpin.label.utf8String"),
 | 
			
		||||
                bin: this._("httpin.label.binaryBuffer"),
 | 
			
		||||
                obj: this._("httpin.label.jsonObject")
 | 
			
		||||
            }[this.ret]);
 | 
			
		||||
        },
 | 
			
		||||
        icon: "white-globe.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||this._("httpin.httpreq");
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            $("#node-input-useAuth").on("change", function() {
 | 
			
		||||
                if ($(this).is(":checked")) {
 | 
			
		||||
                    $(".node-input-useAuth-row").show();
 | 
			
		||||
                    // Nodes (< version 0.20.x) with credentials but without authentication type, need type 'basic'
 | 
			
		||||
                    if (!$('#node-input-authType').val()) {
 | 
			
		||||
                        $("#node-input-authType-select").val('basic').trigger("change");
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    $(".node-input-useAuth-row").hide();
 | 
			
		||||
                    $('#node-input-authType').val('');
 | 
			
		||||
                    $('#node-input-user').val('');
 | 
			
		||||
                    $('#node-input-password').val('');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-authType-select").on("change", function() {
 | 
			
		||||
                var val = $(this).val();
 | 
			
		||||
                $("#node-input-authType").val(val);
 | 
			
		||||
                if (val === "basic" || val === "digest") {
 | 
			
		||||
                    $(".node-input-basic-row").show();
 | 
			
		||||
                    $('#node-span-password').show();
 | 
			
		||||
                    $('#node-span-token').hide();
 | 
			
		||||
                } else if (val === "bearer") {
 | 
			
		||||
                    $(".node-input-basic-row").hide();
 | 
			
		||||
                    $('#node-span-password').hide();
 | 
			
		||||
                    $('#node-span-token').show();
 | 
			
		||||
                    $('#node-input-user').val('');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-method").on("change", function() {
 | 
			
		||||
                if ($(this).val() == "GET") {
 | 
			
		||||
                    $(".node-input-paytoqs-row").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $(".node-input-paytoqs-row").hide();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            if (this.paytoqs === true || this.paytoqs == "query") {
 | 
			
		||||
                $("#node-input-paytoqs").val("query");
 | 
			
		||||
            } else if (this.paytoqs === "body") {
 | 
			
		||||
                $("#node-input-paytoqs").val("body");
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-input-paytoqs").val("ignore");
 | 
			
		||||
            }
 | 
			
		||||
            if (this.authType) {
 | 
			
		||||
                $('#node-input-useAuth').prop('checked', true);
 | 
			
		||||
                $("#node-input-authType-select").val(this.authType);
 | 
			
		||||
                $("#node-input-authType-select").change();
 | 
			
		||||
            } else {
 | 
			
		||||
                $('#node-input-useAuth').prop('checked', false);
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-useAuth").change();
 | 
			
		||||
 | 
			
		||||
            function updateTLSOptions() {
 | 
			
		||||
                if ($("#node-input-usetls").is(':checked')) {
 | 
			
		||||
                    $("#node-row-tls").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-row-tls").hide();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (this.tls) {
 | 
			
		||||
                $('#node-input-usetls').prop('checked', true);
 | 
			
		||||
            } else {
 | 
			
		||||
                $('#node-input-usetls').prop('checked', false);
 | 
			
		||||
            }
 | 
			
		||||
            updateTLSOptions();
 | 
			
		||||
            $("#node-input-usetls").on("click",function() {
 | 
			
		||||
                updateTLSOptions();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            function updateProxyOptions() {
 | 
			
		||||
                if ($("#node-input-useProxy").is(":checked")) {
 | 
			
		||||
                    $("#node-input-useProxy-row").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-input-useProxy-row").hide();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (this.proxy) {
 | 
			
		||||
                $("#node-input-useProxy").prop("checked", true);
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-input-useProxy").prop("checked", false);
 | 
			
		||||
            }
 | 
			
		||||
            updateProxyOptions();
 | 
			
		||||
            $("#node-input-useProxy").on("click", function() {
 | 
			
		||||
                updateProxyOptions();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $("#node-input-ret").on("change", function() {
 | 
			
		||||
                if ($("#node-input-ret").val() === "obj") {
 | 
			
		||||
                    $("#tip-json").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#tip-json").hide();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            if (!$("#node-input-usetls").is(':checked')) {
 | 
			
		||||
                $("#node-input-tls").val("_ADD_");
 | 
			
		||||
            }
 | 
			
		||||
            if (!$("#node-input-useProxy").is(":checked")) {
 | 
			
		||||
                $("#node-input-proxy").val("_ADD_");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,288 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
<!-- WebSocket Input Node -->
 | 
			
		||||
<script type="text/html" data-template-name="websocket in">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
 | 
			
		||||
        <select id="node-input-mode">
 | 
			
		||||
            <option value="server" data-i18n="websocket.listenon"></option>
 | 
			
		||||
            <option value="client" data-i18n="websocket.connectto"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" id="websocket-server-row">
 | 
			
		||||
        <label for="node-input-server"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-server">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" id="websocket-client-row">
 | 
			
		||||
        <label for="node-input-client"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-client">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
 | 
			
		||||
(function() {
 | 
			
		||||
 | 
			
		||||
    function ws_oneditprepare() {
 | 
			
		||||
        $("#websocket-client-row").hide();
 | 
			
		||||
        $("#node-input-mode").on("change", function() {
 | 
			
		||||
            if ( $("#node-input-mode").val() === 'client') {
 | 
			
		||||
                $("#websocket-server-row").hide();
 | 
			
		||||
                $("#websocket-client-row").show();
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                $("#websocket-server-row").show();
 | 
			
		||||
                $("#websocket-client-row").hide();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (this.client) {
 | 
			
		||||
            $("#node-input-mode").val('client').change();
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            $("#node-input-mode").val('server').change();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function ws_oneditsave() {
 | 
			
		||||
        if ($("#node-input-mode").val() === 'client') {
 | 
			
		||||
            $("#node-input-server").append('<option value="">Dummy</option>');
 | 
			
		||||
            $("#node-input-server").val('');
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            $("#node-input-client").append('<option value="">Dummy</option>');
 | 
			
		||||
            $("#node-input-client").val('');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function ws_label() {
 | 
			
		||||
        var nodeid = (this.client)?this.client:this.server;
 | 
			
		||||
        var wsNode = RED.nodes.node(nodeid);
 | 
			
		||||
        return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function ws_validateserver() {
 | 
			
		||||
        if ($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            return RED.nodes.node(this.server) != null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function ws_validateclient() {
 | 
			
		||||
        if ($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
 | 
			
		||||
            return RED.nodes.node(this.client) != null;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('websocket in',{
 | 
			
		||||
        category: 'network',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            server: {type:"websocket-listener", validate: ws_validateserver},
 | 
			
		||||
            client: {type:"websocket-client", validate: ws_validateclient}
 | 
			
		||||
        },
 | 
			
		||||
        color:"rgb(215, 215, 160)",
 | 
			
		||||
        inputs:0,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "white-globe.svg",
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        label: ws_label,
 | 
			
		||||
        oneditsave: ws_oneditsave,
 | 
			
		||||
        oneditprepare: ws_oneditprepare
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('websocket out',{
 | 
			
		||||
        category: 'network',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            server: {type:"websocket-listener", validate: ws_validateserver},
 | 
			
		||||
            client: {type:"websocket-client", validate: ws_validateclient}
 | 
			
		||||
        },
 | 
			
		||||
        color:"rgb(215, 215, 160)",
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:0,
 | 
			
		||||
        icon: "white-globe.svg",
 | 
			
		||||
        align: "right",
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        label: ws_label,
 | 
			
		||||
        oneditsave: ws_oneditsave,
 | 
			
		||||
        oneditprepare: ws_oneditprepare
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('websocket-listener',{
 | 
			
		||||
        category: 'config',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/)},
 | 
			
		||||
            wholemsg: {value:"false"}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:0,
 | 
			
		||||
        outputs:0,
 | 
			
		||||
        label: function() {
 | 
			
		||||
            var root = RED.settings.httpNodeRoot;
 | 
			
		||||
            if (root.slice(-1) != "/") {
 | 
			
		||||
                root = root+"/";
 | 
			
		||||
            }
 | 
			
		||||
            if (this.path) {
 | 
			
		||||
                if (this.path.charAt(0) == "/") {
 | 
			
		||||
                    root += this.path.slice(1);
 | 
			
		||||
                } else {
 | 
			
		||||
                    root += this.path;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return root;
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var root = RED.settings.httpNodeRoot;
 | 
			
		||||
            if (root.slice(-1) == "/") {
 | 
			
		||||
                root = root.slice(0,-1);
 | 
			
		||||
            }
 | 
			
		||||
            if (root === "") {
 | 
			
		||||
                $("#node-config-ws-tip").hide();
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-config-ws-path").html(RED._("node-red:websocket.tip.path2", { path: root }));
 | 
			
		||||
                $("#node-config-ws-tip").show();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('websocket-client',{
 | 
			
		||||
        category: 'config',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/)},
 | 
			
		||||
            tls: {type:"tls-config",required: false},
 | 
			
		||||
            wholemsg: {value:"false"},
 | 
			
		||||
            hb: {value: "", validate: RED.validators.number(/*blank allowed*/true) }
 | 
			
		||||
        },
 | 
			
		||||
        inputs:0,
 | 
			
		||||
        outputs:0,
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.path;
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            $("#node-config-input-path").on("change keyup paste",function() {
 | 
			
		||||
                $(".node-config-row-tls").toggle(/^wss:/i.test($(this).val()))
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-config-input-path").change();
 | 
			
		||||
 | 
			
		||||
            var heartbeatActive = (this.hb && this.hb != "0");
 | 
			
		||||
            $("#node-config-input-hb-cb").prop("checked",heartbeatActive);
 | 
			
		||||
            $("#node-config-input-hb-cb").on("change", function(evt) {
 | 
			
		||||
                $("#node-config-input-hb-row").toggle(this.checked);
 | 
			
		||||
            })
 | 
			
		||||
            $("#node-config-input-hb-cb").trigger("change");
 | 
			
		||||
            if (!heartbeatActive) {
 | 
			
		||||
                $("#node-config-input-hb").val("");
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            if (!/^wss:/i.test($("#node-config-input-path").val())) {
 | 
			
		||||
                $("#node-config-input-tls").val("_ADD_");
 | 
			
		||||
            }
 | 
			
		||||
            if (!$("#node-config-input-hb-cb").prop("checked")) {
 | 
			
		||||
                $("#node-config-input-hb").val("0");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<!-- WebSocket out Node -->
 | 
			
		||||
<script type="text/html" data-template-name="websocket out">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
 | 
			
		||||
        <select id="node-input-mode">
 | 
			
		||||
            <option value="server" data-i18n="websocket.listenon"></option>
 | 
			
		||||
            <option value="client" data-i18n="websocket.connectto"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" id="websocket-server-row">
 | 
			
		||||
        <label for="node-input-server"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-server">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" id="websocket-client-row">
 | 
			
		||||
        <label for="node-input-client"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-client">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<!-- WebSocket Server configuration node -->
 | 
			
		||||
<script type="text/html" data-template-name="websocket-listener">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
 | 
			
		||||
        <input id="node-config-input-path" type="text" placeholder="/ws/example">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-config-input-wholemsg" data-i18n="websocket.sendrec"></label>
 | 
			
		||||
        <select type="text" id="node-config-input-wholemsg" style="width: 70%;">
 | 
			
		||||
            <option value="false" data-i18n="websocket.payload"></option>
 | 
			
		||||
            <option value="true" data-i18n="websocket.message"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-tips">
 | 
			
		||||
        <span data-i18n="[html]websocket.tip.path1"></span>
 | 
			
		||||
        <p id="node-config-ws-tip"><span id="node-config-ws-path"></span></p>
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<!-- WebSocket Client configuration node -->
 | 
			
		||||
<script type="text/html" data-template-name="websocket-client">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
 | 
			
		||||
        <input id="node-config-input-path" type="text" placeholder="ws://example.com/ws">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-config-row-tls hide">
 | 
			
		||||
        <label for="node-config-input-tls" data-i18n="httpin.tls-config"></label>
 | 
			
		||||
        <input type="text" id="node-config-input-tls">
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-config-input-wholemsg" data-i18n="websocket.sendrec"></label>
 | 
			
		||||
        <select type="text" id="node-config-input-wholemsg" style="width: 70%;">
 | 
			
		||||
            <option value="false" data-i18n="websocket.payload"></option>
 | 
			
		||||
            <option value="true" data-i18n="websocket.message"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" style="display: flex; align-items: center; min-height: 34px">
 | 
			
		||||
        <label for="node-config-input-hb-cb" data-i18n="websocket.sendheartbeat"></label>
 | 
			
		||||
        <input type="checkbox" style="margin: 0 8px; width:auto" id="node-config-input-hb-cb">
 | 
			
		||||
        <span id="node-config-input-hb-row" class="hide" >
 | 
			
		||||
            <input type="text" style="width: 70px; margin-right: 3px" id="node-config-input-hb">
 | 
			
		||||
            <span  data-i18n="inject.seconds"></span>
 | 
			
		||||
        </span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-tips">
 | 
			
		||||
        <p><span data-i18n="[html]websocket.tip.url1"></span></p>
 | 
			
		||||
        <span data-i18n="[html]websocket.tip.url2"></span>
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,277 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="tcp in">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-server"><i class="fa fa-dot-circle-o"></i> <span data-i18n="tcpin.label.type"></span></label>
 | 
			
		||||
        <select id="node-input-server" style="width:120px; margin-right:5px;">
 | 
			
		||||
            <option value="server" data-i18n="tcpin.type.listen"></option>
 | 
			
		||||
            <option value="client" data-i18n="tcpin.type.connect"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
        <span data-i18n="tcpin.label.port"></span> <input type="text" id="node-input-port" style="width:65px">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;">
 | 
			
		||||
        <span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" placeholder="localhost" style="width: 60%;">
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.output"></span></label>
 | 
			
		||||
        <select id="node-input-datamode" style="width:110px;">
 | 
			
		||||
            <option value="stream" data-i18n="tcpin.output.stream"></option>
 | 
			
		||||
            <option value="single" data-i18n="tcpin.output.single"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
        <select id="node-input-datatype" style="width:140px;">
 | 
			
		||||
            <option value="buffer" data-i18n="tcpin.output.buffer"></option>
 | 
			
		||||
            <option value="utf8" data-i18n="tcpin.output.string"></option>
 | 
			
		||||
            <option value="base64" data-i18n="tcpin.output.base64"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
        <span data-i18n="tcpin.label.payload"></span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div id="node-row-newline" class="form-row hidden" style="padding-left:110px;">
 | 
			
		||||
        <span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;">
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('tcp in',{
 | 
			
		||||
        category: 'network',
 | 
			
		||||
        color:"Silver",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            server: {value:"server",required:true},
 | 
			
		||||
            host: {value:"",validate:function(v) { return (this.server == "server")||v.length > 0;} },
 | 
			
		||||
            port: {value:"",required:true,validate:RED.validators.number()},
 | 
			
		||||
            datamode:{value:"stream"},
 | 
			
		||||
            datatype:{value:"buffer"},
 | 
			
		||||
            newline:{value:""},
 | 
			
		||||
            topic: {value:""},
 | 
			
		||||
            base64: {/*deprecated*/ value:false,required:true}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:0,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "bridge-dash.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name || "tcp:"+(this.host?this.host+":":"")+this.port;
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var updateOptions = function() {
 | 
			
		||||
                var sockettype = $("#node-input-server").val();
 | 
			
		||||
                if (sockettype == "client") {
 | 
			
		||||
                    $("#node-input-host-row").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-input-host-row").hide();
 | 
			
		||||
                }
 | 
			
		||||
                var datamode = $("#node-input-datamode").val();
 | 
			
		||||
                var datatype = $("#node-input-datatype").val();
 | 
			
		||||
                if (datamode == "stream") {
 | 
			
		||||
                    if (datatype == "utf8") {
 | 
			
		||||
                        $("#node-row-newline").show();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $("#node-row-newline").hide();
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-row-newline").hide();
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            updateOptions();
 | 
			
		||||
            $("#node-input-server").change(updateOptions);
 | 
			
		||||
            $("#node-input-datatype").change(updateOptions);
 | 
			
		||||
            $("#node-input-datamode").change(updateOptions);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="tcp out">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-beserver"><i class="fa fa-dot-circle-o"></i> <span data-i18n="tcpin.label.type"></span></label>
 | 
			
		||||
        <select id="node-input-beserver" style="width:150px; margin-right:5px;">
 | 
			
		||||
            <option value="server" data-i18n="tcpin.type.listen"></option>
 | 
			
		||||
            <option value="client" data-i18n="tcpin.type.connect"></option>
 | 
			
		||||
            <option value="reply" data-i18n="tcpin.type.reply"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
        <span id="node-input-port-row"><span data-i18n="tcpin.label.port"></span> <input type="text" id="node-input-port" style="width: 65px"></span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;">
 | 
			
		||||
        <span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" style="width: 60%;">
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row hidden" id="node-input-end-row">
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input type="checkbox" id="node-input-end" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-input-end" style="width: 70%;"><span data-i18n="tcpin.label.close-connection"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input type="checkbox" id="node-input-base64" placeholder="base64" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-input-base64" style="width: 70%;"><span data-i18n="tcpin.label.decode-base64"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('tcp out',{
 | 
			
		||||
        category: 'network',
 | 
			
		||||
        color:"Silver",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            host: {value:"",validate:function(v) { return (this.beserver != "client")||v.length > 0;} },
 | 
			
		||||
            port: {value:"",validate:function(v) { return (this.beserver == "reply")||RED.validators.number()(v); } },
 | 
			
		||||
            beserver: {value:"client",required:true},
 | 
			
		||||
            base64: {value:false,required:true},
 | 
			
		||||
            end: {value:false,required:true},
 | 
			
		||||
            name: {value:""}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:0,
 | 
			
		||||
        icon: "bridge-dash.svg",
 | 
			
		||||
        align: "right",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name || "tcp:"+(this.host?this.host+":":"")+this.port;
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return (this.name)?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var updateOptions = function() {
 | 
			
		||||
                var sockettype = $("#node-input-beserver").val();
 | 
			
		||||
                if (sockettype == "reply") {
 | 
			
		||||
                    $("#node-input-port-row").hide();
 | 
			
		||||
                    $("#node-input-host-row").hide();
 | 
			
		||||
                    $("#node-input-end-row").hide();
 | 
			
		||||
                } else if (sockettype == "client"){
 | 
			
		||||
                    $("#node-input-port-row").show();
 | 
			
		||||
                    $("#node-input-host-row").show();
 | 
			
		||||
                    $("#node-input-end-row").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-input-port-row").show();
 | 
			
		||||
                    $("#node-input-host-row").hide();
 | 
			
		||||
                    $("#node-input-end-row").show();
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            updateOptions();
 | 
			
		||||
            $("#node-input-beserver").change(updateOptions);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="tcp request">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-server"><i class="fa fa-globe"></i> <span data-i18n="tcpin.label.server"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-server" placeholder="ip.address" style="width:45%">
 | 
			
		||||
        <span data-i18n="tcpin.label.port"></span>
 | 
			
		||||
        <input type="text" id="node-input-port" style="width:60px">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-out"><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.return"></span></label>
 | 
			
		||||
        <select type="text" id="node-input-ret" style="width:54%;">
 | 
			
		||||
            <option value="buffer" data-i18n="tcpin.output.buffer"></option>
 | 
			
		||||
            <option value="string" data-i18n="tcpin.output.string"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-out"> </label>
 | 
			
		||||
        <select type="text" id="node-input-out" style="width:54%;">
 | 
			
		||||
            <option value="time" data-i18n="tcpin.return.timeout"></option>
 | 
			
		||||
            <option value="char" data-i18n="tcpin.return.character"></option>
 | 
			
		||||
            <option value="count" data-i18n="tcpin.return.number"></option>
 | 
			
		||||
            <option value="sit" data-i18n="tcpin.return.never"></option>
 | 
			
		||||
            <option value="immed" data-i18n="tcpin.return.immed"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
        <input type="text" id="node-input-splitc" style="width:50px;">
 | 
			
		||||
        <span id="node-units"></span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('tcp request',{
 | 
			
		||||
        category: 'network',
 | 
			
		||||
        color:"Silver",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            server: {value:""},
 | 
			
		||||
            port: {value:"",validate:RED.validators.regex(/^(\d*|)$/)},
 | 
			
		||||
            out: {value:"time",required:true},
 | 
			
		||||
            ret: {value:"buffer"},
 | 
			
		||||
            splitc: {value:"0",required:true},
 | 
			
		||||
            name: {value:""}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "bridge-dash.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name || "tcp:"+(this.server?this.server+":":"")+this.port;
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var previous = null;
 | 
			
		||||
            if ($("#node-input-ret").val() == undefined) {
 | 
			
		||||
                $("#node-input-ret").val("buffer");
 | 
			
		||||
                this.ret = "buffer";
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-out").on('focus', function () { previous = this.value; }).on("change", function() {
 | 
			
		||||
                $("#node-input-splitc").show();
 | 
			
		||||
                if (previous === null) { previous = $("#node-input-out").val(); }
 | 
			
		||||
                if ($("#node-input-out").val() == "char") {
 | 
			
		||||
                    if (previous != "char") { $("#node-input-splitc").val("\\n"); }
 | 
			
		||||
                    $("#node-units").text("");
 | 
			
		||||
                }
 | 
			
		||||
                else if ($("#node-input-out").val() == "time") {
 | 
			
		||||
                    if (previous != "time") { $("#node-input-splitc").val("0"); }
 | 
			
		||||
                    $("#node-units").text(RED._("node-red:tcpin.label.ms"));
 | 
			
		||||
                }
 | 
			
		||||
                else if ($("#node-input-out").val() == "immed") {
 | 
			
		||||
                    if (previous != "immed") { $("#node-input-splitc").val(" "); }
 | 
			
		||||
                    $("#node-units").text("");
 | 
			
		||||
                    $("#node-input-splitc").hide();
 | 
			
		||||
                }
 | 
			
		||||
                else if ($("#node-input-out").val() == "count") {
 | 
			
		||||
                    if (previous != "count") { $("#node-input-splitc").val("12"); }
 | 
			
		||||
                    $("#node-units").text(RED._("node-red:tcpin.label.chars"));
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    if (previous != "sit") { $("#node-input-splitc").val(" "); }
 | 
			
		||||
                    $("#node-units").text("");
 | 
			
		||||
                    $("#node-input-splitc").hide();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,719 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    var reconnectTime = RED.settings.socketReconnectTime||10000;
 | 
			
		||||
    var socketTimeout = RED.settings.socketTimeout||null;
 | 
			
		||||
    const msgQueueSize = RED.settings.tcpMsgQueueSize || 1000;
 | 
			
		||||
    const Denque = require('denque');
 | 
			
		||||
    var net = require('net');
 | 
			
		||||
 | 
			
		||||
    var connectionPool = {};
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enqueue `item` in `queue`
 | 
			
		||||
     * @param {Denque} queue - Queue
 | 
			
		||||
     * @param {*} item - Item to enqueue
 | 
			
		||||
     * @private
 | 
			
		||||
     * @returns {Denque} `queue`
 | 
			
		||||
     */
 | 
			
		||||
    const enqueue = (queue, item) => {
 | 
			
		||||
        // drop msgs from front of queue if size is going to be exceeded
 | 
			
		||||
        if (queue.length === msgQueueSize) { queue.shift(); }
 | 
			
		||||
        queue.push(item);
 | 
			
		||||
        return queue;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shifts item off front of queue
 | 
			
		||||
     * @param {Deque} queue - Queue
 | 
			
		||||
     * @private
 | 
			
		||||
     * @returns {*} Item previously at front of queue
 | 
			
		||||
     */
 | 
			
		||||
    const dequeue = queue => queue.shift();
 | 
			
		||||
 | 
			
		||||
    function TcpIn(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.host = n.host;
 | 
			
		||||
        this.port = n.port * 1;
 | 
			
		||||
        this.topic = n.topic;
 | 
			
		||||
        this.stream = (!n.datamode||n.datamode=='stream'); /* stream,single*/
 | 
			
		||||
        this.datatype = n.datatype||'buffer'; /* buffer,utf8,base64 */
 | 
			
		||||
        this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r");
 | 
			
		||||
        this.base64 = n.base64;
 | 
			
		||||
        this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server");
 | 
			
		||||
        this.closing = false;
 | 
			
		||||
        this.connected = false;
 | 
			
		||||
        var node = this;
 | 
			
		||||
        var count = 0;
 | 
			
		||||
 | 
			
		||||
        if (!node.server) {
 | 
			
		||||
            var buffer = null;
 | 
			
		||||
            var client;
 | 
			
		||||
            var reconnectTimeout;
 | 
			
		||||
            var end = false;
 | 
			
		||||
            var setupTcpClient = function() {
 | 
			
		||||
                node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
 | 
			
		||||
                node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
 | 
			
		||||
                var id = RED.util.generateId();
 | 
			
		||||
                client = net.connect(node.port, node.host, function() {
 | 
			
		||||
                    buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
 | 
			
		||||
                    node.connected = true;
 | 
			
		||||
                    node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
 | 
			
		||||
                    node.status({fill:"green",shape:"dot",text:"common.status.connected",_session:{type:"tcp",id:id}});
 | 
			
		||||
                });
 | 
			
		||||
                client.setKeepAlive(true,120000);
 | 
			
		||||
                connectionPool[id] = client;
 | 
			
		||||
 | 
			
		||||
                client.on('data', function (data) {
 | 
			
		||||
                    if (node.datatype != 'buffer') {
 | 
			
		||||
                        data = data.toString(node.datatype);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (node.stream) {
 | 
			
		||||
                        var msg;
 | 
			
		||||
                        if ((node.datatype) === "utf8" && node.newline !== "") {
 | 
			
		||||
                            buffer = buffer+data;
 | 
			
		||||
                            var parts = buffer.split(node.newline);
 | 
			
		||||
                            for (var i = 0; i<parts.length-1; i+=1) {
 | 
			
		||||
                                msg = {topic:node.topic, payload:parts[i]};
 | 
			
		||||
                                msg._session = {type:"tcp",id:id};
 | 
			
		||||
                                node.send(msg);
 | 
			
		||||
                            }
 | 
			
		||||
                            buffer = parts[parts.length-1];
 | 
			
		||||
                        } else {
 | 
			
		||||
                            msg = {topic:node.topic, payload:data};
 | 
			
		||||
                            msg._session = {type:"tcp",id:id};
 | 
			
		||||
                            node.send(msg);
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if ((typeof data) === "string") {
 | 
			
		||||
                            buffer = buffer+data;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            buffer = Buffer.concat([buffer,data],buffer.length+data.length);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                client.on('end', function() {
 | 
			
		||||
                    if (!node.stream || (node.datatype == "utf8" && node.newline !== "" && buffer.length > 0)) {
 | 
			
		||||
                        var msg = {topic:node.topic, payload:buffer};
 | 
			
		||||
                        msg._session = {type:"tcp",id:id};
 | 
			
		||||
                        if (buffer.length !== 0) {
 | 
			
		||||
                            end = true; // only ask for fast re-connect if we actually got something
 | 
			
		||||
                            node.send(msg);
 | 
			
		||||
                        }
 | 
			
		||||
                        buffer = null;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                client.on('close', function() {
 | 
			
		||||
                    delete connectionPool[id];
 | 
			
		||||
                    node.connected = false;
 | 
			
		||||
                    node.status({fill:"red",shape:"ring",text:"common.status.disconnected",_session:{type:"tcp",id:id}});
 | 
			
		||||
                    if (!node.closing) {
 | 
			
		||||
                        if (end) { // if we were asked to close then try to reconnect once very quick.
 | 
			
		||||
                            end = false;
 | 
			
		||||
                            reconnectTimeout = setTimeout(setupTcpClient, 20);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            node.log(RED._("tcpin.errors.connection-lost",{host:node.host,port:node.port}));
 | 
			
		||||
                            reconnectTimeout = setTimeout(setupTcpClient, reconnectTime);
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (node.doneClose) { node.doneClose(); }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                client.on('error', function(err) {
 | 
			
		||||
                    node.log(err);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            setupTcpClient();
 | 
			
		||||
 | 
			
		||||
            this.on('close', function(done) {
 | 
			
		||||
                node.doneClose = done;
 | 
			
		||||
                this.closing = true;
 | 
			
		||||
                if (client) { client.destroy(); }
 | 
			
		||||
                clearTimeout(reconnectTimeout);
 | 
			
		||||
                if (!node.connected) { done(); }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            var server = net.createServer(function (socket) {
 | 
			
		||||
                socket.setKeepAlive(true,120000);
 | 
			
		||||
                if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
 | 
			
		||||
                var id = RED.util.generateId();
 | 
			
		||||
                var fromi;
 | 
			
		||||
                var fromp;
 | 
			
		||||
                connectionPool[id] = socket;
 | 
			
		||||
                count++;
 | 
			
		||||
                node.status({
 | 
			
		||||
                    text:RED._("tcpin.status.connections",{count:count}),
 | 
			
		||||
                    event:"connect",
 | 
			
		||||
                    ip:socket.remoteAddress,
 | 
			
		||||
                    port:socket.remotePort,
 | 
			
		||||
                    _session: {type:"tcp",id:id}
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                var buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
 | 
			
		||||
                socket.on('data', function (data) {
 | 
			
		||||
                    if (node.datatype != 'buffer') {
 | 
			
		||||
                        data = data.toString(node.datatype);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (node.stream) {
 | 
			
		||||
                        var msg;
 | 
			
		||||
                        if ((typeof data) === "string" && node.newline !== "") {
 | 
			
		||||
                            buffer = buffer+data;
 | 
			
		||||
                            var parts = buffer.split(node.newline);
 | 
			
		||||
                            for (var i = 0; i<parts.length-1; i+=1) {
 | 
			
		||||
                                msg = {topic:node.topic, payload:parts[i], ip:socket.remoteAddress, port:socket.remotePort};
 | 
			
		||||
                                msg._session = {type:"tcp",id:id};
 | 
			
		||||
                                node.send(msg);
 | 
			
		||||
                            }
 | 
			
		||||
                            buffer = parts[parts.length-1];
 | 
			
		||||
                        } else {
 | 
			
		||||
                            msg = {topic:node.topic, payload:data, ip:socket.remoteAddress, port:socket.remotePort};
 | 
			
		||||
                            msg._session = {type:"tcp",id:id};
 | 
			
		||||
                            node.send(msg);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        if ((typeof data) === "string") {
 | 
			
		||||
                            buffer = buffer+data;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            buffer = Buffer.concat([buffer,data],buffer.length+data.length);
 | 
			
		||||
                        }
 | 
			
		||||
                        fromi = socket.remoteAddress;
 | 
			
		||||
                        fromp = socket.remotePort;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                socket.on('end', function() {
 | 
			
		||||
                    if (!node.stream || (node.datatype === "utf8" && node.newline !== "") || (node.datatype === "base64")) {
 | 
			
		||||
                        if (buffer.length > 0) {
 | 
			
		||||
                            var msg = {topic:node.topic, payload:buffer, ip:fromi, port:fromp};
 | 
			
		||||
                            msg._session = {type:"tcp",id:id};
 | 
			
		||||
                            node.send(msg);
 | 
			
		||||
                        }
 | 
			
		||||
                        buffer = null;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                socket.on('timeout', function() {
 | 
			
		||||
                    node.log(RED._("tcpin.errors.timeout",{port:node.port}));
 | 
			
		||||
                    socket.end();
 | 
			
		||||
                });
 | 
			
		||||
                socket.on('close', function() {
 | 
			
		||||
                    delete connectionPool[id];
 | 
			
		||||
                    count--;
 | 
			
		||||
                    node.status({
 | 
			
		||||
                        text:RED._("tcpin.status.connections",{count:count}),
 | 
			
		||||
                        event:"disconnect",
 | 
			
		||||
                        ip:socket.remoteAddress,
 | 
			
		||||
                        port:socket.remotePort,
 | 
			
		||||
                        _session: {type:"tcp",id:id}
 | 
			
		||||
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
                socket.on('error',function(err) {
 | 
			
		||||
                    node.log(err);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            server.on('error', function(err) {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            server.listen(node.port, function(err) {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
 | 
			
		||||
                } else {
 | 
			
		||||
                    node.log(RED._("tcpin.status.listening-port",{port:node.port}));
 | 
			
		||||
                    node.on('close', function() {
 | 
			
		||||
                        for (var c in connectionPool) {
 | 
			
		||||
                            if (connectionPool.hasOwnProperty(c)) {
 | 
			
		||||
                                connectionPool[c].end();
 | 
			
		||||
                                connectionPool[c].unref();
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        node.closing = true;
 | 
			
		||||
                        server.close();
 | 
			
		||||
                        node.log(RED._("tcpin.status.stopped-listening",{port:node.port}));
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("tcp in",TcpIn);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function TcpOut(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.host = n.host;
 | 
			
		||||
        this.port = n.port * 1;
 | 
			
		||||
        this.base64 = n.base64;
 | 
			
		||||
        this.doend = n.end || false;
 | 
			
		||||
        this.beserver = n.beserver;
 | 
			
		||||
        this.name = n.name;
 | 
			
		||||
        this.closing = false;
 | 
			
		||||
        this.connected = false;
 | 
			
		||||
        var node = this;
 | 
			
		||||
 | 
			
		||||
        if (!node.beserver||node.beserver=="client") {
 | 
			
		||||
            var reconnectTimeout;
 | 
			
		||||
            var client = null;
 | 
			
		||||
            var end = false;
 | 
			
		||||
 | 
			
		||||
            var setupTcpClient = function() {
 | 
			
		||||
                node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
 | 
			
		||||
                node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
 | 
			
		||||
                client = net.connect(node.port, node.host, function() {
 | 
			
		||||
                    node.connected = true;
 | 
			
		||||
                    node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
 | 
			
		||||
                    node.status({fill:"green",shape:"dot",text:"common.status.connected"});
 | 
			
		||||
                });
 | 
			
		||||
                client.setKeepAlive(true,120000);
 | 
			
		||||
                client.on('error', function (err) {
 | 
			
		||||
                    node.log(RED._("tcpin.errors.error",{error:err.toString()}));
 | 
			
		||||
                });
 | 
			
		||||
                client.on('end', function (err) {
 | 
			
		||||
                    node.status({});
 | 
			
		||||
                    node.connected = false;
 | 
			
		||||
                });
 | 
			
		||||
                client.on('close', function() {
 | 
			
		||||
                    node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
 | 
			
		||||
                    node.connected = false;
 | 
			
		||||
                    client.destroy();
 | 
			
		||||
                    if (!node.closing) {
 | 
			
		||||
                        if (end) {
 | 
			
		||||
                            end = false;
 | 
			
		||||
                            reconnectTimeout = setTimeout(setupTcpClient,20);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            node.log(RED._("tcpin.errors.connection-lost",{host:node.host,port:node.port}));
 | 
			
		||||
                            reconnectTimeout = setTimeout(setupTcpClient,reconnectTime);
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (node.doneClose) { node.doneClose(); }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            setupTcpClient();
 | 
			
		||||
 | 
			
		||||
            node.on("input", function(msg, nodeSend, nodeDone) {
 | 
			
		||||
                if (node.connected && msg.payload != null) {
 | 
			
		||||
                    if (Buffer.isBuffer(msg.payload)) {
 | 
			
		||||
                        client.write(msg.payload);
 | 
			
		||||
                    } else if (typeof msg.payload === "string" && node.base64) {
 | 
			
		||||
                        client.write(Buffer.from(msg.payload,'base64'));
 | 
			
		||||
                    } else {
 | 
			
		||||
                        client.write(Buffer.from(""+msg.payload));
 | 
			
		||||
                    }
 | 
			
		||||
                    if (node.doend === true) {
 | 
			
		||||
                        end = true;
 | 
			
		||||
                        if (client) { node.status({}); client.destroy(); }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                nodeDone();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            node.on("close", function(done) {
 | 
			
		||||
                node.doneClose = done;
 | 
			
		||||
                this.closing = true;
 | 
			
		||||
                if (client) { client.destroy(); }
 | 
			
		||||
                clearTimeout(reconnectTimeout);
 | 
			
		||||
                if (!node.connected) { done(); }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        else if (node.beserver == "reply") {
 | 
			
		||||
            node.on("input",function(msg, nodeSend, nodeDone) {
 | 
			
		||||
                if (msg._session && msg._session.type == "tcp") {
 | 
			
		||||
                    var client = connectionPool[msg._session.id];
 | 
			
		||||
                    if (client) {
 | 
			
		||||
                        if (Buffer.isBuffer(msg.payload)) {
 | 
			
		||||
                            client.write(msg.payload);
 | 
			
		||||
                        } else if (typeof msg.payload === "string" && node.base64) {
 | 
			
		||||
                            client.write(Buffer.from(msg.payload,'base64'));
 | 
			
		||||
                        } else {
 | 
			
		||||
                            client.write(Buffer.from(""+msg.payload));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    for (var i in connectionPool) {
 | 
			
		||||
                        if (Buffer.isBuffer(msg.payload)) {
 | 
			
		||||
                            connectionPool[i].write(msg.payload);
 | 
			
		||||
                        } else if (typeof msg.payload === "string" && node.base64) {
 | 
			
		||||
                            connectionPool[i].write(Buffer.from(msg.payload,'base64'));
 | 
			
		||||
                        } else {
 | 
			
		||||
                            connectionPool[i].write(Buffer.from(""+msg.payload));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                nodeDone();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            var connectedSockets = [];
 | 
			
		||||
            node.status({text:RED._("tcpin.status.connections",{count:0})});
 | 
			
		||||
            var server = net.createServer(function (socket) {
 | 
			
		||||
                socket.setKeepAlive(true,120000);
 | 
			
		||||
                if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
 | 
			
		||||
                node.log(RED._("tcpin.status.connection-from",{host:socket.remoteAddress, port:socket.remotePort}));
 | 
			
		||||
                socket.on('timeout', function() {
 | 
			
		||||
                    node.log(RED._("tcpin.errors.timeout",{port:node.port}));
 | 
			
		||||
                    socket.end();
 | 
			
		||||
                });
 | 
			
		||||
                socket.on('data', function(d) {
 | 
			
		||||
                    // console.log("DATA",d)
 | 
			
		||||
                });
 | 
			
		||||
                socket.on('close',function() {
 | 
			
		||||
                    node.log(RED._("tcpin.status.connection-closed",{host:socket.remoteAddress, port:socket.remotePort}));
 | 
			
		||||
                    connectedSockets.splice(connectedSockets.indexOf(socket),1);
 | 
			
		||||
                    node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
 | 
			
		||||
                });
 | 
			
		||||
                socket.on('error',function() {
 | 
			
		||||
                    node.log(RED._("tcpin.errors.socket-error",{host:socket.remoteAddress, port:socket.remotePort}));
 | 
			
		||||
                    connectedSockets.splice(connectedSockets.indexOf(socket),1);
 | 
			
		||||
                    node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
 | 
			
		||||
                });
 | 
			
		||||
                connectedSockets.push(socket);
 | 
			
		||||
                node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            node.on("input", function(msg, nodeSend, nodeDone) {
 | 
			
		||||
                if (msg.payload != null) {
 | 
			
		||||
                    var buffer;
 | 
			
		||||
                    if (Buffer.isBuffer(msg.payload)) {
 | 
			
		||||
                        buffer = msg.payload;
 | 
			
		||||
                    } else if (typeof msg.payload === "string" && node.base64) {
 | 
			
		||||
                        buffer = Buffer.from(msg.payload,'base64');
 | 
			
		||||
                    } else {
 | 
			
		||||
                        buffer = Buffer.from(""+msg.payload);
 | 
			
		||||
                    }
 | 
			
		||||
                    for (var i = 0; i < connectedSockets.length; i += 1) {
 | 
			
		||||
                        if (node.doend === true) { connectedSockets[i].end(buffer); }
 | 
			
		||||
                        else { connectedSockets[i].write(buffer); }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                nodeDone();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            server.on('error', function(err) {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            server.listen(node.port, function(err) {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
 | 
			
		||||
                } else {
 | 
			
		||||
                    node.log(RED._("tcpin.status.listening-port",{port:node.port}));
 | 
			
		||||
                    node.on('close', function() {
 | 
			
		||||
                        for (var c in connectedSockets) {
 | 
			
		||||
                            if (connectedSockets.hasOwnProperty(c)) {
 | 
			
		||||
                                connectedSockets[c].end();
 | 
			
		||||
                                connectedSockets[c].unref();
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        server.close();
 | 
			
		||||
                        node.log(RED._("tcpin.status.stopped-listening",{port:node.port}));
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("tcp out",TcpOut);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function TcpGet(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.server = n.server;
 | 
			
		||||
        this.port = Number(n.port);
 | 
			
		||||
        this.out = n.out;
 | 
			
		||||
        this.ret = n.ret || "buffer";
 | 
			
		||||
        this.splitc = n.splitc;
 | 
			
		||||
 | 
			
		||||
        if (this.out === "immed") { this.splitc = -1; this.out = "time"; }
 | 
			
		||||
        if (this.out !== "char") { this.splitc = Number(this.splitc); }
 | 
			
		||||
        else {
 | 
			
		||||
            if (this.splitc[0] == '\\') {
 | 
			
		||||
                this.splitc = parseInt(this.splitc.replace("\\n",0x0A).replace("\\r",0x0D).replace("\\t",0x09).replace("\\e",0x1B).replace("\\f",0x0C).replace("\\0",0x00));
 | 
			
		||||
            } // jshint ignore:line
 | 
			
		||||
            if (typeof this.splitc == "string") {
 | 
			
		||||
                if (this.splitc.substr(0,2) == "0x") {
 | 
			
		||||
                    this.splitc = parseInt(this.splitc);
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    this.splitc = this.splitc.charCodeAt(0);
 | 
			
		||||
                }
 | 
			
		||||
            } // jshint ignore:line
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var node = this;
 | 
			
		||||
 | 
			
		||||
        var clients = {};
 | 
			
		||||
 | 
			
		||||
        this.on("input", function(msg, nodeSend, nodeDone) {
 | 
			
		||||
            var i = 0;
 | 
			
		||||
            if ((!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) {
 | 
			
		||||
                msg.payload = msg.payload.toString();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var host = node.server || msg.host;
 | 
			
		||||
            var port = node.port || msg.port;
 | 
			
		||||
 | 
			
		||||
            // Store client information independently
 | 
			
		||||
            // the clients object will have:
 | 
			
		||||
            // clients[id].client, clients[id].msg, clients[id].timeout
 | 
			
		||||
            var connection_id = host + ":" + port;
 | 
			
		||||
            if (connection_id !== node.last_id) {
 | 
			
		||||
                node.status({});
 | 
			
		||||
                node.last_id = connection_id;
 | 
			
		||||
            }
 | 
			
		||||
            clients[connection_id] = clients[connection_id] || {
 | 
			
		||||
                msgQueue: new Denque(),
 | 
			
		||||
                connected: false,
 | 
			
		||||
                connecting: false
 | 
			
		||||
            };
 | 
			
		||||
            enqueue(clients[connection_id].msgQueue, {msg:msg, nodeSend:nodeSend, nodeDone:nodeDone});
 | 
			
		||||
            clients[connection_id].lastMsg = msg;
 | 
			
		||||
 | 
			
		||||
            if (!clients[connection_id].connecting && !clients[connection_id].connected) {
 | 
			
		||||
                var buf;
 | 
			
		||||
                if (this.out == "count") {
 | 
			
		||||
                    if (this.splitc === 0) { buf = Buffer.alloc(1); }
 | 
			
		||||
                    else { buf = Buffer.alloc(this.splitc); }
 | 
			
		||||
                }
 | 
			
		||||
                else { buf = Buffer.alloc(65536); } // set it to 64k... hopefully big enough for most TCP packets.... but only hopefully
 | 
			
		||||
 | 
			
		||||
                clients[connection_id].client = net.Socket();
 | 
			
		||||
                if (socketTimeout !== null) { clients[connection_id].client.setTimeout(socketTimeout);}
 | 
			
		||||
 | 
			
		||||
                if (host && port) {
 | 
			
		||||
                    clients[connection_id].connecting = true;
 | 
			
		||||
                    clients[connection_id].client.connect(port, host, function() {
 | 
			
		||||
                        //node.log(RED._("tcpin.errors.client-connected"));
 | 
			
		||||
                        node.status({fill:"green",shape:"dot",text:"common.status.connected"});
 | 
			
		||||
                        if (clients[connection_id] && clients[connection_id].client) {
 | 
			
		||||
                            clients[connection_id].connected = true;
 | 
			
		||||
                            clients[connection_id].connecting = false;
 | 
			
		||||
                            let event;
 | 
			
		||||
                            while (event = dequeue(clients[connection_id].msgQueue)) {
 | 
			
		||||
                                clients[connection_id].client.write(event.msg.payload);
 | 
			
		||||
                                event.nodeDone();
 | 
			
		||||
                            }
 | 
			
		||||
                            if (node.out === "time" && node.splitc < 0) {
 | 
			
		||||
                                clients[connection_id].connected = clients[connection_id].connecting = false;
 | 
			
		||||
                                clients[connection_id].client.end();
 | 
			
		||||
                                delete clients[connection_id];
 | 
			
		||||
                                node.status({});
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    node.warn(RED._("tcpin.errors.no-host"));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                clients[connection_id].client.on('data', function(data) {
 | 
			
		||||
                    if (node.out === "sit") { // if we are staying connected just send the buffer
 | 
			
		||||
                        if (clients[connection_id]) {
 | 
			
		||||
                            const msg = clients[connection_id].lastMsg || {};
 | 
			
		||||
                            msg.payload = RED.util.cloneMessage(data);
 | 
			
		||||
                            if (node.ret === "string") {
 | 
			
		||||
                                try { msg.payload = msg.payload.toString(); }
 | 
			
		||||
                                catch(e) { node.error("Failed to create string", msg); }
 | 
			
		||||
                            }
 | 
			
		||||
                            nodeSend(msg);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    // else if (node.splitc === 0) {
 | 
			
		||||
                    //     clients[connection_id].msg.payload = data;
 | 
			
		||||
                    //     node.send(clients[connection_id].msg);
 | 
			
		||||
                    // }
 | 
			
		||||
                    else {
 | 
			
		||||
                        for (var j = 0; j < data.length; j++ ) {
 | 
			
		||||
                            if (node.out === "time") {
 | 
			
		||||
                                if (clients[connection_id]) {
 | 
			
		||||
                                    // do the timer thing
 | 
			
		||||
                                    if (clients[connection_id].timeout) {
 | 
			
		||||
                                        i += 1;
 | 
			
		||||
                                        buf[i] = data[j];
 | 
			
		||||
                                    }
 | 
			
		||||
                                    else {
 | 
			
		||||
                                        clients[connection_id].timeout = setTimeout(function () {
 | 
			
		||||
                                            if (clients[connection_id]) {
 | 
			
		||||
                                                clients[connection_id].timeout = null;
 | 
			
		||||
                                                const msg = clients[connection_id].lastMsg || {};
 | 
			
		||||
                                                msg.payload = Buffer.alloc(i+1);
 | 
			
		||||
                                                buf.copy(msg.payload,0,0,i+1);
 | 
			
		||||
                                                if (node.ret === "string") {
 | 
			
		||||
                                                    try { msg.payload = msg.payload.toString(); }
 | 
			
		||||
                                                    catch(e) { node.error("Failed to create string", msg); }
 | 
			
		||||
                                                }
 | 
			
		||||
                                                nodeSend(msg);
 | 
			
		||||
                                                if (clients[connection_id].client) {
 | 
			
		||||
                                                    node.status({});
 | 
			
		||||
                                                    clients[connection_id].client.destroy();
 | 
			
		||||
                                                    delete clients[connection_id];
 | 
			
		||||
                                                }
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }, node.splitc);
 | 
			
		||||
                                        i = 0;
 | 
			
		||||
                                        buf[0] = data[j];
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            // count bytes into a buffer...
 | 
			
		||||
                            else if (node.out == "count") {
 | 
			
		||||
                                buf[i] = data[j];
 | 
			
		||||
                                i += 1;
 | 
			
		||||
                                if ( i >= node.splitc) {
 | 
			
		||||
                                    if (clients[connection_id]) {
 | 
			
		||||
                                        const msg = clients[connection_id].lastMsg || {};
 | 
			
		||||
                                        msg.payload = Buffer.alloc(i);
 | 
			
		||||
                                        buf.copy(msg.payload,0,0,i);
 | 
			
		||||
                                        if (node.ret === "string") {
 | 
			
		||||
                                            try { msg.payload = msg.payload.toString(); }
 | 
			
		||||
                                            catch(e) { node.error("Failed to create string", msg); }
 | 
			
		||||
                                        }
 | 
			
		||||
                                        nodeSend(msg);
 | 
			
		||||
                                        if (clients[connection_id].client) {
 | 
			
		||||
                                            node.status({});
 | 
			
		||||
                                            clients[connection_id].client.destroy();
 | 
			
		||||
                                            delete clients[connection_id];
 | 
			
		||||
                                        }
 | 
			
		||||
                                        i = 0;
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            // look for a char
 | 
			
		||||
                            else {
 | 
			
		||||
                                buf[i] = data[j];
 | 
			
		||||
                                i += 1;
 | 
			
		||||
                                if (data[j] == node.splitc) {
 | 
			
		||||
                                    if (clients[connection_id]) {
 | 
			
		||||
                                        const msg = clients[connection_id].lastMsg || {};
 | 
			
		||||
                                        msg.payload = Buffer.alloc(i);
 | 
			
		||||
                                        buf.copy(msg.payload,0,0,i);
 | 
			
		||||
                                        if (node.ret === "string") {
 | 
			
		||||
                                            try { msg.payload = msg.payload.toString(); }
 | 
			
		||||
                                            catch(e) { node.error("Failed to create string", msg); }
 | 
			
		||||
                                        }
 | 
			
		||||
                                        nodeSend(msg);
 | 
			
		||||
                                        if (clients[connection_id].client) {
 | 
			
		||||
                                            node.status({});
 | 
			
		||||
                                            clients[connection_id].client.destroy();
 | 
			
		||||
                                            delete clients[connection_id];
 | 
			
		||||
                                        }
 | 
			
		||||
                                        i = 0;
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                clients[connection_id].client.on('end', function() {
 | 
			
		||||
                    //console.log("END");
 | 
			
		||||
                    node.status({fill:"grey",shape:"ring",text:"common.status.disconnected"});
 | 
			
		||||
                    if (clients[connection_id] && clients[connection_id].client) {
 | 
			
		||||
                        clients[connection_id].connected = clients[connection_id].connecting = false;
 | 
			
		||||
                        clients[connection_id].client = null;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                clients[connection_id].client.on('close', function() {
 | 
			
		||||
                    //console.log("CLOSE");
 | 
			
		||||
                    if (clients[connection_id]) {
 | 
			
		||||
                        clients[connection_id].connected = clients[connection_id].connecting = false;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var anyConnected = false;
 | 
			
		||||
 | 
			
		||||
                    for (var client in clients) {
 | 
			
		||||
                        if (clients[client].connected) {
 | 
			
		||||
                            anyConnected = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (node.doneClose && !anyConnected) {
 | 
			
		||||
                        clients = {};
 | 
			
		||||
                        node.doneClose();
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                clients[connection_id].client.on('error', function() {
 | 
			
		||||
                    //console.log("ERROR");
 | 
			
		||||
                    node.status({fill:"red",shape:"ring",text:"common.status.error"});
 | 
			
		||||
                    node.error(RED._("tcpin.errors.connect-fail") + " " + connection_id, msg);
 | 
			
		||||
                    if (clients[connection_id] && clients[connection_id].client) {
 | 
			
		||||
                        clients[connection_id].client.destroy();
 | 
			
		||||
                        delete clients[connection_id];
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                clients[connection_id].client.on('timeout',function() {
 | 
			
		||||
                    //console.log("TIMEOUT");
 | 
			
		||||
                    if (clients[connection_id]) {
 | 
			
		||||
                        clients[connection_id].connected = clients[connection_id].connecting = false;
 | 
			
		||||
                        node.status({fill:"grey",shape:"dot",text:"tcpin.errors.connect-timeout"});
 | 
			
		||||
                        //node.warn(RED._("tcpin.errors.connect-timeout"));
 | 
			
		||||
                        if (clients[connection_id].client) {
 | 
			
		||||
                            clients[connection_id].connecting = true;
 | 
			
		||||
                            clients[connection_id].client.connect(port, host, function() {
 | 
			
		||||
                                clients[connection_id].connected = true;
 | 
			
		||||
                                clients[connection_id].connecting = false;
 | 
			
		||||
                                node.status({fill:"green",shape:"dot",text:"common.status.connected"});
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            else if (!clients[connection_id].connecting && clients[connection_id].connected) {
 | 
			
		||||
                if (clients[connection_id] && clients[connection_id].client) {
 | 
			
		||||
                    let event = dequeue(clients[connection_id].msgQueue)
 | 
			
		||||
                    clients[connection_id].client.write(event.msg.payload);
 | 
			
		||||
                    event.nodeDone();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.on("close", function(done) {
 | 
			
		||||
            node.doneClose = done;
 | 
			
		||||
            for (var cl in clients) {
 | 
			
		||||
                if (clients[cl].hasOwnProperty("client")) {
 | 
			
		||||
                    clients[cl].client.destroy();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            node.status({});
 | 
			
		||||
 | 
			
		||||
            // this is probably not necessary and may be removed
 | 
			
		||||
            var anyConnected = false;
 | 
			
		||||
            for (var c in clients) {
 | 
			
		||||
                if (clients[c].connected) {
 | 
			
		||||
                    anyConnected = true;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (!anyConnected) { clients = {}; }
 | 
			
		||||
            done();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("tcp request",TcpGet);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,231 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<!--  The Input Node  -->
 | 
			
		||||
<script type="text/html" data-template-name="udp in">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.listen"></span></label>
 | 
			
		||||
        <select id="node-input-multicast" style='width:70%'>
 | 
			
		||||
          <option value="false" data-i18n="udp.udpmsgs"></option>
 | 
			
		||||
          <option value="true" data-i18n="udp.mcmsgs"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-input-group">
 | 
			
		||||
        <label for="node-input-group"><i class="fa fa-list"></i> <span data-i18n="udp.label.group"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-group" placeholder="225.0.18.83">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-input-iface">
 | 
			
		||||
        <label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-iface" data-i18n="[placeholder]udp.placeholder.interfaceprompt">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.onport"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-port" style="width:80px">
 | 
			
		||||
          <span data-i18n="udp.label.using"></span> <select id="node-input-ipv" style="width:80px">
 | 
			
		||||
          <option value="udp4">ipv4</option>
 | 
			
		||||
          <option value="udp6">ipv6</option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-datatype"><i class="fa fa-sign-out"></i> <span data-i18n="udp.label.output"></span></label>
 | 
			
		||||
        <select id="node-input-datatype" style="width:70%;">
 | 
			
		||||
            <option value="buffer" data-i18n="udp.output.buffer"></option>
 | 
			
		||||
            <option value="utf8" data-i18n="udp.output.string"></option>
 | 
			
		||||
            <option value="base64" data-i18n="udp.output.base64"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-tips"><span data-i18n="udp.tip.in"></span></div>
 | 
			
		||||
    <div class="form-tips" id="udpporttip"><span data-i18n="[html]udp.tip.port"></span></div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('udp in',{
 | 
			
		||||
        category: 'network',
 | 
			
		||||
        color:"Silver",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            iface: {value:""},
 | 
			
		||||
            port: {value:"",required:true,validate:RED.validators.number()},
 | 
			
		||||
            ipv: {value:"udp4"},
 | 
			
		||||
            multicast: {value:"false"},
 | 
			
		||||
            group: {value:"",validate:function(v) { return (this.multicast !== "true")||v.length > 0;} },
 | 
			
		||||
            datatype: {value:"buffer",required:true}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:0,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "bridge-dash.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            if (this.multicast=="false") {
 | 
			
		||||
                return this.name||"udp "+this.port;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                return this.name||"udp "+(this.group+":"+this.port);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            $("#node-input-multicast").on("change", function() {
 | 
			
		||||
                var id = $("#node-input-multicast").val();
 | 
			
		||||
                if (id == "false") {
 | 
			
		||||
                    $(".node-input-group").hide();
 | 
			
		||||
                    $(".node-input-iface").hide();
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    $(".node-input-group").show();
 | 
			
		||||
                    $(".node-input-iface").show();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-multicast").change();
 | 
			
		||||
 | 
			
		||||
            var porttip = this._("udp.tip.port");
 | 
			
		||||
            var alreadyused = this._("udp.errors.alreadyused");
 | 
			
		||||
            var portsInUse = {};
 | 
			
		||||
            $.getJSON('udp-ports/'+this.id,function(data) {
 | 
			
		||||
                portsInUse = data || {};
 | 
			
		||||
                $('#udpporttip').html(porttip + data);
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-port").on("change", function() {
 | 
			
		||||
                var portnew = $("#node-input-port").val();
 | 
			
		||||
                if (portsInUse.hasOwnProperty($("#node-input-port").val())) {
 | 
			
		||||
                    RED.notify(alreadyused+" "+$("#node-input-port").val(),"warn");
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<!--  The Output Node  -->
 | 
			
		||||
<script type="text/html" data-template-name="udp out">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-port"><i class="fa fa-envelope"></i> <span data-i18n="udp.label.send"></span></label>
 | 
			
		||||
        <select id="node-input-multicast" style="width:40%">
 | 
			
		||||
          <option value="false" data-i18n="udp.udpmsg"></option>
 | 
			
		||||
          <option value="broad" data-i18n="udp.bcmsg"></option>
 | 
			
		||||
          <option value="multi" data-i18n="udp.mcmsg"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
        <span data-i18n="udp.label.toport"></span> <input type="text" id="node-input-port" style="width:70px">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-input-addr">
 | 
			
		||||
        <label for="node-input-addr" id="node-input-addr-label"><i class="fa fa-list"></i> <span data-i18n="udp.label.address"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-addr" data-i18n="[placeholder]udp.placeholder.address" style="width:50%;">
 | 
			
		||||
        <select id="node-input-ipv" style="width:70px">
 | 
			
		||||
          <option value="udp4">ipv4</option>
 | 
			
		||||
          <option value="udp6">ipv6</option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-input-iface">
 | 
			
		||||
        <label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-iface" data-i18n="[placeholder]udp.placeholder.interface">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-outport-type"> </label>
 | 
			
		||||
        <select id="node-input-outport-type">
 | 
			
		||||
          <option id="node-input-outport-type-random" value="random" data-i18n="udp.bind.random"></option>
 | 
			
		||||
          <option value="fixed" data-i18n="udp.bind.local"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
        <input type="text" id="node-input-outport" style="width:70px;">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input type="checkbox" id="node-input-base64" style="display:inline-block; width:auto; vertical-align:top;">
 | 
			
		||||
        <label for="node-input-base64" style="width:70%;"><span data-i18n="udp.label.decode-base64"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-tips"><span data-i18n="[html]udp.tip.out"></span></div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('udp out',{
 | 
			
		||||
        category: 'network',
 | 
			
		||||
        color:"Silver",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            addr: {value:""},
 | 
			
		||||
            iface: {value:""},
 | 
			
		||||
            port: {value:""},
 | 
			
		||||
            ipv: {value:"udp4"},
 | 
			
		||||
            outport: {value:""},
 | 
			
		||||
            base64: {value:false,required:true},
 | 
			
		||||
            multicast: {value:"false"}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:0,
 | 
			
		||||
        icon: "bridge-dash.svg",
 | 
			
		||||
        align: "right",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||"udp "+(this.addr+":"+this.port);
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var addresslabel = this._("udp.label.address");
 | 
			
		||||
            var addressph = this._("udp.placeholder.address");
 | 
			
		||||
            var grouplabel = this._("udp.label.group");
 | 
			
		||||
            var bindrandom = this._("udp.bind.random");
 | 
			
		||||
            var bindtarget = this._("udp.bind.target");
 | 
			
		||||
 | 
			
		||||
            var type = this.outport===""?"random":"fixed";
 | 
			
		||||
            $("#node-input-outport-type").val(type);
 | 
			
		||||
 | 
			
		||||
            $("#node-input-outport-type").on("change", function() {
 | 
			
		||||
                var type = $(this).val();
 | 
			
		||||
                if (type == "random") {
 | 
			
		||||
                    $("#node-input-outport").val("").hide();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-input-outport").show();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-outport-type").change();
 | 
			
		||||
 | 
			
		||||
            $("#node-input-multicast").on("change", function() {
 | 
			
		||||
                var id = $("#node-input-multicast").val();
 | 
			
		||||
                if (id === "multi") {
 | 
			
		||||
                    $(".node-input-iface").show();
 | 
			
		||||
                    $("#node-input-addr-label").html('<i class="fa fa-list"></i> ' + grouplabel);
 | 
			
		||||
                    $("#node-input-addr")[0].placeholder = '225.0.18.83';
 | 
			
		||||
                }
 | 
			
		||||
                else if (id === "broad") {
 | 
			
		||||
                    $(".node-input-iface").hide();
 | 
			
		||||
                    $("#node-input-addr-label").html('<i class="fa fa-list"></i> ' + addresslabel);
 | 
			
		||||
                    $("#node-input-addr")[0].placeholder = '255.255.255.255';
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    $(".node-input-iface").hide();
 | 
			
		||||
                    $("#node-input-addr-label").html('<i class="fa fa-list"></i> ' + addresslabel);
 | 
			
		||||
                    $("#node-input-addr")[0].placeholder = addressph;
 | 
			
		||||
                }
 | 
			
		||||
                var type = $(this).val();
 | 
			
		||||
                if (type == "false") {
 | 
			
		||||
                    $("#node-input-outport-type-random").html(bindrandom);
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-input-outport-type-random").html(bindtarget);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-multicast").change();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,280 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    var os = require('os');
 | 
			
		||||
    var dgram = require('dgram');
 | 
			
		||||
    var udpInputPortsInUse = {};
 | 
			
		||||
 | 
			
		||||
    // The Input Node
 | 
			
		||||
    function UDPin(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.group = n.group;
 | 
			
		||||
        this.port = n.port;
 | 
			
		||||
        this.datatype = n.datatype;
 | 
			
		||||
        this.iface = n.iface || null;
 | 
			
		||||
        this.multicast = n.multicast;
 | 
			
		||||
        this.ipv = n.ipv || "udp4";
 | 
			
		||||
        var node = this;
 | 
			
		||||
 | 
			
		||||
        if (node.iface && node.iface.indexOf(".") === -1) {
 | 
			
		||||
            try {
 | 
			
		||||
                if ((os.networkInterfaces())[node.iface][0].hasOwnProperty("scopeid")) {
 | 
			
		||||
                    if (node.ipv === "udp4") {
 | 
			
		||||
                        node.iface = (os.networkInterfaces())[node.iface][1].address;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        node.iface = (os.networkInterfaces())[node.iface][0].address;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    if (node.ipv === "udp4") {
 | 
			
		||||
                        node.iface = (os.networkInterfaces())[node.iface][0].address;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        node.iface = (os.networkInterfaces())[node.iface][1].address;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch(e) {
 | 
			
		||||
                node.warn(RED._("udp.errors.ifnotfound",{iface:node.iface}));
 | 
			
		||||
                node.iface = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var opts = {type:node.ipv, reuseAddr:true};
 | 
			
		||||
        if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
 | 
			
		||||
        var server;
 | 
			
		||||
 | 
			
		||||
        if (!udpInputPortsInUse.hasOwnProperty(node.port)) {
 | 
			
		||||
            server = dgram.createSocket(opts);  // default to udp4
 | 
			
		||||
            server.bind(node.port, function() {
 | 
			
		||||
                if (node.multicast == "true") {
 | 
			
		||||
                    server.setBroadcast(true);
 | 
			
		||||
                    server.setMulticastLoopback(false);
 | 
			
		||||
                    try {
 | 
			
		||||
                        server.setMulticastTTL(128);
 | 
			
		||||
                        server.addMembership(node.group,node.iface);
 | 
			
		||||
                        if (node.iface) { node.status({text:n.iface+" : "+node.iface}); }
 | 
			
		||||
                        node.log(RED._("udp.status.mc-group",{group:node.group}));
 | 
			
		||||
                    } catch (e) {
 | 
			
		||||
                        if (e.errno == "EINVAL") {
 | 
			
		||||
                            node.error(RED._("udp.errors.bad-mcaddress"));
 | 
			
		||||
                        } else if (e.errno == "ENODEV") {
 | 
			
		||||
                            node.error(RED._("udp.errors.interface"));
 | 
			
		||||
                        } else {
 | 
			
		||||
                            node.error(RED._("udp.errors.error",{error:e.errno}));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            udpInputPortsInUse[node.port] = server;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            node.log(RED._("udp.errors.alreadyused",{port:node.port}));
 | 
			
		||||
            server = udpInputPortsInUse[node.port];  // re-use existing
 | 
			
		||||
            if (node.iface) { node.status({text:n.iface+" : "+node.iface}); }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        server.on("error", function (err) {
 | 
			
		||||
            if ((err.code == "EACCES") && (node.port < 1024)) {
 | 
			
		||||
                node.error(RED._("udp.errors.access-error"));
 | 
			
		||||
            } else {
 | 
			
		||||
                node.error(RED._("udp.errors.error",{error:err.code}));
 | 
			
		||||
            }
 | 
			
		||||
            server.close();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        server.on('message', function (message, remote) {
 | 
			
		||||
            var msg;
 | 
			
		||||
            if (node.datatype =="base64") {
 | 
			
		||||
                msg = { payload:message.toString('base64'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
 | 
			
		||||
            } else if (node.datatype =="utf8") {
 | 
			
		||||
                msg = { payload:message.toString('utf8'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
 | 
			
		||||
            } else {
 | 
			
		||||
                msg = { payload:message, fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
 | 
			
		||||
            }
 | 
			
		||||
            node.send(msg);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        server.on('listening', function () {
 | 
			
		||||
            var address = server.address();
 | 
			
		||||
            node.log(RED._("udp.status.listener-at",{host:node.iface||address.address,port:address.port}));
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        node.on("close", function() {
 | 
			
		||||
            try {
 | 
			
		||||
                if (node.multicast == "true") { server.dropMembership(node.group); }
 | 
			
		||||
                server.close();
 | 
			
		||||
                node.log(RED._("udp.status.listener-stopped"));
 | 
			
		||||
            } catch (err) {
 | 
			
		||||
                //node.error(err);
 | 
			
		||||
            }
 | 
			
		||||
            if (udpInputPortsInUse.hasOwnProperty(node.port)) {
 | 
			
		||||
                delete udpInputPortsInUse[node.port];
 | 
			
		||||
            }
 | 
			
		||||
            node.status({});
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    RED.httpAdmin.get('/udp-ports/:id', RED.auth.needsPermission('udp-ports.read'), function(req,res) {
 | 
			
		||||
        res.json(Object.keys(udpInputPortsInUse));
 | 
			
		||||
    });
 | 
			
		||||
    RED.nodes.registerType("udp in",UDPin);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // The Output Node
 | 
			
		||||
    function UDPout(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        //this.group = n.group;
 | 
			
		||||
        this.port = n.port;
 | 
			
		||||
        this.outport = n.outport||"";
 | 
			
		||||
        this.base64 = n.base64;
 | 
			
		||||
        this.addr = n.addr;
 | 
			
		||||
        this.iface = n.iface || null;
 | 
			
		||||
        this.multicast = n.multicast;
 | 
			
		||||
        this.ipv = n.ipv || "udp4";
 | 
			
		||||
        var node = this;
 | 
			
		||||
 | 
			
		||||
        if (node.iface && node.iface.indexOf(".") === -1) {
 | 
			
		||||
            try {
 | 
			
		||||
                if ((os.networkInterfaces())[node.iface][0].hasOwnProperty("scopeid")) {
 | 
			
		||||
                    if (node.ipv === "udp4") {
 | 
			
		||||
                        node.iface = (os.networkInterfaces())[node.iface][1].address;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        node.iface = (os.networkInterfaces())[node.iface][0].address;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    if (node.ipv === "udp4") {
 | 
			
		||||
                        node.iface = (os.networkInterfaces())[node.iface][0].address;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        node.iface = (os.networkInterfaces())[node.iface][1].address;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch(e) {
 | 
			
		||||
                node.warn(RED._("udp.errors.ifnotfound",{iface:node.iface}));
 | 
			
		||||
                node.iface = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var opts = {type:node.ipv, reuseAddr:true};
 | 
			
		||||
 | 
			
		||||
        var sock;
 | 
			
		||||
        var p = this.outport || this.port || "0";
 | 
			
		||||
        node.tout = setTimeout(function() {
 | 
			
		||||
            if ((p != 0) && udpInputPortsInUse[p]) {
 | 
			
		||||
                sock = udpInputPortsInUse[p];
 | 
			
		||||
                if (node.multicast != "false") {
 | 
			
		||||
                    sock.setBroadcast(true);
 | 
			
		||||
                    sock.setMulticastLoopback(false);
 | 
			
		||||
                }
 | 
			
		||||
                node.log(RED._("udp.status.re-use",{outport:node.outport,host:node.addr,port:node.port}));
 | 
			
		||||
                if (node.iface) { node.status({text:n.iface+" : "+node.iface}); }
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                sock = dgram.createSocket(opts);  // default to udp4
 | 
			
		||||
                if (node.multicast != "false") {
 | 
			
		||||
                    sock.bind(node.outport, function() {    // have to bind before you can enable broadcast...
 | 
			
		||||
                        sock.setBroadcast(true);            // turn on broadcast
 | 
			
		||||
                        sock.setMulticastLoopback(false);   // turn off loopback
 | 
			
		||||
                        if (node.multicast == "multi") {
 | 
			
		||||
                            try {
 | 
			
		||||
                                sock.setMulticastTTL(128);
 | 
			
		||||
                                sock.addMembership(node.addr,node.iface);   // Add to the multicast group
 | 
			
		||||
                                if (node.iface) { node.status({text:n.iface+" : "+node.iface}); }
 | 
			
		||||
                                node.log(RED._("udp.status.mc-ready",{iface:node.iface,outport:node.outport,host:node.addr,port:node.port}));
 | 
			
		||||
                            } catch (e) {
 | 
			
		||||
                                if (e.errno == "EINVAL") {
 | 
			
		||||
                                    node.error(RED._("udp.errors.bad-mcaddress"));
 | 
			
		||||
                                } else if (e.errno == "ENODEV") {
 | 
			
		||||
                                    node.error(RED._("udp.errors.interface"));
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    node.error(RED._("udp.errors.error",{error:e.errno}));
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            node.log(RED._("udp.status.bc-ready",{outport:node.outport,host:node.addr,port:node.port}));
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                } else if ((node.outport !== "") && (!udpInputPortsInUse[node.outport])) {
 | 
			
		||||
                    sock.bind(node.outport);
 | 
			
		||||
                    node.log(RED._("udp.status.ready",{outport:node.outport,host:node.addr,port:node.port}));
 | 
			
		||||
                } else {
 | 
			
		||||
                    node.log(RED._("udp.status.ready-nolocal",{host:node.addr,port:node.port}));
 | 
			
		||||
                }
 | 
			
		||||
                sock.on("error", function(err) {
 | 
			
		||||
                    // Any async error will also get reported in the sock.send call.
 | 
			
		||||
                    // This handler is needed to ensure the error marked as handled to
 | 
			
		||||
                    // prevent it going to the global error handler and shutting node-red
 | 
			
		||||
                    // down.
 | 
			
		||||
                });
 | 
			
		||||
                udpInputPortsInUse[p] = sock;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            node.on("input", function(msg, nodeSend, nodeDone) {
 | 
			
		||||
                if (msg.hasOwnProperty("payload")) {
 | 
			
		||||
                    var add = node.addr || msg.ip || "";
 | 
			
		||||
                    var por = node.port || msg.port || 0;
 | 
			
		||||
                    if (add === "") {
 | 
			
		||||
                        node.warn(RED._("udp.errors.ip-notset"));
 | 
			
		||||
                        nodeDone();
 | 
			
		||||
                    } else if (por === 0) {
 | 
			
		||||
                        node.warn(RED._("udp.errors.port-notset"));
 | 
			
		||||
                        nodeDone();
 | 
			
		||||
                    } else if (isNaN(por) || (por < 1) || (por > 65535)) {
 | 
			
		||||
                        node.warn(RED._("udp.errors.port-invalid"));
 | 
			
		||||
                        nodeDone();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        var message;
 | 
			
		||||
                        if (node.base64) {
 | 
			
		||||
                            message = Buffer.from(msg.payload, 'base64');
 | 
			
		||||
                        } else if (msg.payload instanceof Buffer) {
 | 
			
		||||
                            message = msg.payload;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            message = Buffer.from(""+msg.payload);
 | 
			
		||||
                        }
 | 
			
		||||
                        sock.send(message, 0, message.length, por, add, function(err, bytes) {
 | 
			
		||||
                            if (err) {
 | 
			
		||||
                                node.error("udp : "+err,msg);
 | 
			
		||||
                            }
 | 
			
		||||
                            message = null;
 | 
			
		||||
                            nodeDone();
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }, 75);
 | 
			
		||||
 | 
			
		||||
        node.on("close", function() {
 | 
			
		||||
            if (node.tout) { clearTimeout(node.tout); }
 | 
			
		||||
            try {
 | 
			
		||||
                if (node.multicast == "multi") { sock.dropMembership(node.group); }
 | 
			
		||||
                sock.close();
 | 
			
		||||
                node.log(RED._("udp.status.output-stopped"));
 | 
			
		||||
            } catch (err) {
 | 
			
		||||
                //node.error(err);
 | 
			
		||||
            }
 | 
			
		||||
            if (udpInputPortsInUse.hasOwnProperty(p)) {
 | 
			
		||||
                delete udpInputPortsInUse[p];
 | 
			
		||||
            }
 | 
			
		||||
            node.status({});
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("udp out",UDPout);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,257 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
var util = require("util");
 | 
			
		||||
var mqtt = require("mqtt");
 | 
			
		||||
var events = require("events");
 | 
			
		||||
 | 
			
		||||
util.log("[warn] nodes/core/io/lib/mqtt.js is deprecated and will be removed in a future release of Node-RED. Please report this usage to the Node-RED mailing list.");
 | 
			
		||||
 | 
			
		||||
//var inspect = require("util").inspect;
 | 
			
		||||
 | 
			
		||||
//var Client = module.exports.Client = function(
 | 
			
		||||
 | 
			
		||||
var port = 1883;
 | 
			
		||||
var host = "localhost";
 | 
			
		||||
 | 
			
		||||
function MQTTClient(port,host) {
 | 
			
		||||
   this.port = port||1883;
 | 
			
		||||
   this.host = host||"localhost";
 | 
			
		||||
   this.messageId = 1;
 | 
			
		||||
   this.pendingSubscriptions = {};
 | 
			
		||||
   this.inboundMessages = {};
 | 
			
		||||
   this.lastOutbound = (new Date()).getTime();
 | 
			
		||||
   this.lastInbound = (new Date()).getTime();
 | 
			
		||||
   this.connected = false;
 | 
			
		||||
 | 
			
		||||
   this._nextMessageId = function() {
 | 
			
		||||
      this.messageId += 1;
 | 
			
		||||
      if (this.messageId > 0xFFFF) {
 | 
			
		||||
         this.messageId = 1;
 | 
			
		||||
      }
 | 
			
		||||
      return this.messageId;
 | 
			
		||||
   }
 | 
			
		||||
   events.EventEmitter.call(this);
 | 
			
		||||
}
 | 
			
		||||
util.inherits(MQTTClient, events.EventEmitter);
 | 
			
		||||
 | 
			
		||||
MQTTClient.prototype.connect = function(options) {
 | 
			
		||||
   if (!this.connected) {
 | 
			
		||||
       var self = this;
 | 
			
		||||
       options = options||{};
 | 
			
		||||
       self.options = options;
 | 
			
		||||
       self.options.keepalive = options.keepalive||15;
 | 
			
		||||
       self.options.clean = self.options.clean||true;
 | 
			
		||||
       self.options.protocolId = 'MQIsdp';
 | 
			
		||||
       self.options.protocolVersion = 3;
 | 
			
		||||
 | 
			
		||||
       self.client = mqtt.createConnection(this.port,this.host,function(err,client) {
 | 
			
		||||
             if (err) {
 | 
			
		||||
                self.connected = false;
 | 
			
		||||
                clearInterval(self.watchdog);
 | 
			
		||||
                self.connectionError = true;
 | 
			
		||||
                //util.log('[mqtt] ['+self.uid+'] connection error 1 : '+inspect(err));
 | 
			
		||||
                self.emit('connectionlost',err);
 | 
			
		||||
                return;
 | 
			
		||||
             }
 | 
			
		||||
             client.on('close',function(e) {
 | 
			
		||||
                   //util.log('[mqtt] ['+self.uid+'] on close');
 | 
			
		||||
                   clearInterval(self.watchdog);
 | 
			
		||||
                   if (!self.connectionError) {
 | 
			
		||||
                       if (self.connected) {
 | 
			
		||||
                          self.connected = false;
 | 
			
		||||
                          self.emit('connectionlost',e);
 | 
			
		||||
                       } else {
 | 
			
		||||
                          self.emit('disconnect');
 | 
			
		||||
                       }
 | 
			
		||||
                   }
 | 
			
		||||
             });
 | 
			
		||||
             client.on('error',function(e) {
 | 
			
		||||
                   //util.log('[mqtt] ['+self.uid+'] on error : '+inspect(e));
 | 
			
		||||
                   clearInterval(self.watchdog);
 | 
			
		||||
                   if (self.connected) {
 | 
			
		||||
                      self.connected = false;
 | 
			
		||||
                      self.emit('connectionlost',e);
 | 
			
		||||
                   }
 | 
			
		||||
             });
 | 
			
		||||
             client.on('connack',function(packet) {
 | 
			
		||||
                   if (packet.returnCode === 0) {
 | 
			
		||||
                      self.watchdog = setInterval(function(self) {
 | 
			
		||||
                            var now = (new Date()).getTime();
 | 
			
		||||
 | 
			
		||||
                            //util.log('[mqtt] ['+self.uid+'] watchdog '+inspect({connected:self.connected,connectionError:self.connectionError,pingOutstanding:self.pingOutstanding,now:now,lastOutbound:self.lastOutbound,lastInbound:self.lastInbound}));
 | 
			
		||||
 | 
			
		||||
                            if (now - self.lastOutbound > self.options.keepalive*500 || now - self.lastInbound > self.options.keepalive*500) {
 | 
			
		||||
                               if (self.pingOutstanding) {
 | 
			
		||||
                                  //util.log('[mqtt] ['+self.uid+'] watchdog pingOustanding - disconnect');
 | 
			
		||||
                                  try {
 | 
			
		||||
                                     self.client.disconnect();
 | 
			
		||||
                                  } catch (err) {
 | 
			
		||||
                                  }
 | 
			
		||||
                               } else {
 | 
			
		||||
                                  //util.log('[mqtt] ['+self.uid+'] watchdog pinging');
 | 
			
		||||
                                  self.lastOutbound = (new Date()).getTime();
 | 
			
		||||
                                  self.lastInbound = (new Date()).getTime();
 | 
			
		||||
                                  self.pingOutstanding = true;
 | 
			
		||||
                                  self.client.pingreq();
 | 
			
		||||
                               }
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                      },self.options.keepalive*500,self);
 | 
			
		||||
                      self.pingOutstanding = false;
 | 
			
		||||
                      self.lastInbound = (new Date()).getTime()
 | 
			
		||||
                      self.lastOutbound = (new Date()).getTime()
 | 
			
		||||
                      self.connected = true;
 | 
			
		||||
                      self.connectionError = false;
 | 
			
		||||
                      self.emit('connect');
 | 
			
		||||
                   } else {
 | 
			
		||||
                      self.connected = false;
 | 
			
		||||
                      self.emit('connectionlost');
 | 
			
		||||
                   }
 | 
			
		||||
             });
 | 
			
		||||
             client.on('suback',function(packet) {
 | 
			
		||||
                   self.lastInbound = (new Date()).getTime()
 | 
			
		||||
                   var topic = self.pendingSubscriptions[packet.messageId];
 | 
			
		||||
                   self.emit('subscribe',topic,packet.granted[0]);
 | 
			
		||||
                   delete self.pendingSubscriptions[packet.messageId];
 | 
			
		||||
             });
 | 
			
		||||
             client.on('unsuback',function(packet) {
 | 
			
		||||
                   self.lastInbound = (new Date()).getTime()
 | 
			
		||||
                   var topic = self.pendingSubscriptions[packet.messageId];
 | 
			
		||||
                   self.emit('unsubscribe',topic);
 | 
			
		||||
                   delete self.pendingSubscriptions[packet.messageId];
 | 
			
		||||
             });
 | 
			
		||||
             client.on('publish',function(packet) {
 | 
			
		||||
                   self.lastInbound = (new Date()).getTime();
 | 
			
		||||
                   if (packet.qos < 2) {
 | 
			
		||||
                      var p = packet;
 | 
			
		||||
                      self.emit('message',p.topic,p.payload,p.qos,p.retain);
 | 
			
		||||
                   } else {
 | 
			
		||||
                      self.inboundMessages[packet.messageId] = packet;
 | 
			
		||||
                      this.lastOutbound = (new Date()).getTime()
 | 
			
		||||
                      self.client.pubrec(packet);
 | 
			
		||||
                   }
 | 
			
		||||
                   if (packet.qos == 1) {
 | 
			
		||||
                      this.lastOutbound = (new Date()).getTime()
 | 
			
		||||
                      self.client.puback(packet);
 | 
			
		||||
                   }
 | 
			
		||||
             });
 | 
			
		||||
 | 
			
		||||
             client.on('pubrel',function(packet) {
 | 
			
		||||
                   self.lastInbound = (new Date()).getTime()
 | 
			
		||||
                   var p = self.inboundMessages[packet.messageId];
 | 
			
		||||
                   if (p) {
 | 
			
		||||
                       self.emit('message',p.topic,p.payload,p.qos,p.retain);
 | 
			
		||||
                       delete self.inboundMessages[packet.messageId];
 | 
			
		||||
                   }
 | 
			
		||||
                   self.lastOutbound = (new Date()).getTime()
 | 
			
		||||
                   self.client.pubcomp(packet);
 | 
			
		||||
             });
 | 
			
		||||
 | 
			
		||||
             client.on('puback',function(packet) {
 | 
			
		||||
                   self.lastInbound = (new Date()).getTime()
 | 
			
		||||
                   // outbound qos-1 complete
 | 
			
		||||
             });
 | 
			
		||||
 | 
			
		||||
             client.on('pubrec',function(packet) {
 | 
			
		||||
                   self.lastInbound = (new Date()).getTime()
 | 
			
		||||
                   self.lastOutbound = (new Date()).getTime()
 | 
			
		||||
                   self.client.pubrel(packet);
 | 
			
		||||
             });
 | 
			
		||||
             client.on('pubcomp',function(packet) {
 | 
			
		||||
                   self.lastInbound = (new Date()).getTime()
 | 
			
		||||
                   // outbound qos-2 complete
 | 
			
		||||
             });
 | 
			
		||||
             client.on('pingresp',function(packet) {
 | 
			
		||||
                   //util.log('[mqtt] ['+self.uid+'] received pingresp');
 | 
			
		||||
                   self.lastInbound = (new Date()).getTime()
 | 
			
		||||
                   self.pingOutstanding = false;
 | 
			
		||||
             });
 | 
			
		||||
 | 
			
		||||
             this.lastOutbound = (new Date()).getTime()
 | 
			
		||||
             this.connectionError = false;
 | 
			
		||||
             client.connect(self.options);
 | 
			
		||||
       });
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MQTTClient.prototype.subscribe = function(topic,qos) {
 | 
			
		||||
   var self = this;
 | 
			
		||||
   if (self.connected) {
 | 
			
		||||
      var options = {
 | 
			
		||||
         subscriptions:[{topic:topic,qos:qos}],
 | 
			
		||||
         messageId: self._nextMessageId()
 | 
			
		||||
      };
 | 
			
		||||
      this.pendingSubscriptions[options.messageId] = topic;
 | 
			
		||||
      this.lastOutbound = (new Date()).getTime();
 | 
			
		||||
      self.client.subscribe(options);
 | 
			
		||||
      self.client.setPacketEncoding('binary');
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
MQTTClient.prototype.unsubscribe = function(topic) {
 | 
			
		||||
   var self = this;
 | 
			
		||||
   if (self.connected) {
 | 
			
		||||
      var options = {
 | 
			
		||||
         unsubscriptions:[topic],
 | 
			
		||||
         messageId: self._nextMessageId()
 | 
			
		||||
      };
 | 
			
		||||
      this.pendingSubscriptions[options.messageId] = topic;
 | 
			
		||||
      this.lastOutbound = (new Date()).getTime()
 | 
			
		||||
      self.client.unsubscribe(options);
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MQTTClient.prototype.publish = function(topic,payload,qos,retain) {
 | 
			
		||||
   var self = this;
 | 
			
		||||
   if (self.connected) {
 | 
			
		||||
 | 
			
		||||
      if (!Buffer.isBuffer(payload)) {
 | 
			
		||||
         if (typeof payload === "object") {
 | 
			
		||||
            payload = JSON.stringify(payload);
 | 
			
		||||
         } else if (typeof payload !== "string") {
 | 
			
		||||
            payload = ""+payload;
 | 
			
		||||
         }
 | 
			
		||||
      }
 | 
			
		||||
      var options = {
 | 
			
		||||
         topic: topic,
 | 
			
		||||
         payload: payload,
 | 
			
		||||
         qos: qos||0,
 | 
			
		||||
         retain:retain||false
 | 
			
		||||
      };
 | 
			
		||||
      if (options.qos !== 0) {
 | 
			
		||||
         options.messageId = self._nextMessageId();
 | 
			
		||||
      }
 | 
			
		||||
      this.lastOutbound = (new Date()).getTime()
 | 
			
		||||
      self.client.publish(options);
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MQTTClient.prototype.disconnect = function() {
 | 
			
		||||
   var self = this;
 | 
			
		||||
   if (this.connected) {
 | 
			
		||||
       this.connected = false;
 | 
			
		||||
       try {
 | 
			
		||||
           this.client.disconnect();
 | 
			
		||||
       } catch(err) {
 | 
			
		||||
       }
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
MQTTClient.prototype.isConnected = function() {
 | 
			
		||||
    return this.connected;
 | 
			
		||||
}
 | 
			
		||||
module.exports.createClient = function(port,host) {
 | 
			
		||||
   var mqtt_client = new MQTTClient(port,host);
 | 
			
		||||
   return mqtt_client;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,125 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="csv">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-temp"><i class="fa fa-list"></i> <span data-i18n="csv.label.columns"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-temp" data-i18n="[placeholder]csv.placeholder.columns">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-select-sep"><i class="fa fa-text-width"></i> <span data-i18n="csv.label.separator"></span></label>
 | 
			
		||||
            <select style="width:150px" id="node-input-select-sep">
 | 
			
		||||
                <option value="," data-i18n="csv.separator.comma"></option>
 | 
			
		||||
                <option value="\t" data-i18n="csv.separator.tab"></option>
 | 
			
		||||
                <option value=" " data-i18n="csv.separator.space"></option>
 | 
			
		||||
                <option value=";" data-i18n="csv.separator.semicolon"></option>
 | 
			
		||||
                <option value=":" data-i18n="csv.separator.colon"></option>
 | 
			
		||||
                <option value="#" data-i18n="csv.separator.hashtag"></option>
 | 
			
		||||
                <option value="" data-i18n="csv.separator.other"></option>
 | 
			
		||||
           </select>
 | 
			
		||||
           <input style="width:40px;" type="text" id="node-input-sep" pattern=".">
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr align="middle"/>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label style="width:100%;"><span data-i18n="csv.label.c2o"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" style="padding-left:20px;">
 | 
			
		||||
        <label><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.input"></span></label>
 | 
			
		||||
        <span data-i18n="csv.label.skip-s"></span> <input type="text" id="node-input-skip" style="width:40px; height:25px;"/> <span data-i18n="csv.label.skip-e"></span><br/>
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-hdrin"><label style="width:auto; margin-top:7px;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span></label><br/>
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-strings"><label style="width:auto; margin-top:7px;" for="node-input-strings"><span data-i18n="csv.label.usestrings"></span></label><br/>
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_empty_strings"><label style="width:auto; margin-top:7px;" for="node-input-include_empty_strings"><span data-i18n="csv.label.include_empty_strings"></span></label><br/>
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_null_values"><label style="width:auto; margin-top:7px;" for="node-input-include_null_values"><span data-i18n="csv.label.include_null_values"></span></label><br/>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" style="padding-left:20px;">
 | 
			
		||||
        <label><i class="fa fa-sign-out"></i> <span data-i18n="csv.label.output"></span></label>
 | 
			
		||||
        <select type="text" id="node-input-multi" style="width:250px;">
 | 
			
		||||
            <option value="one" data-i18n="csv.output.row"></option>
 | 
			
		||||
            <option value="mult" data-i18n="csv.output.array"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" style="margin-top:20px">
 | 
			
		||||
        <label style="width:100%;"><span data-i18n="csv.label.o2c"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" style="padding-left:20px;">
 | 
			
		||||
        <label><i class="fa fa-sign-out"></i> <span data-i18n="csv.label.output"></span></label>
 | 
			
		||||
        <!-- <input style="width:20px; vertical-align:top; margin-right:5px;" type="checkbox" id="node-input-hdrout"><label style="width:auto;" for="node-input-hdrout"><span data-i18n="csv.label.includerow"></span></span> -->
 | 
			
		||||
        <select style="width:60%" id="node-input-hdrout">
 | 
			
		||||
            <option value="none" data-i18n="csv.hdrout.none"></option>
 | 
			
		||||
            <option value="all" data-i18n="csv.hdrout.all"></option>
 | 
			
		||||
            <option value="once" data-i18n="csv.hdrout.once"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" style="padding-left:20px;">
 | 
			
		||||
        <label></label>
 | 
			
		||||
        <label style="width:auto; margin-right:10px;" for="node-input-ret"><span data-i18n="csv.label.newline"></span></label>
 | 
			
		||||
        <select style="width:150px;" id="node-input-ret">
 | 
			
		||||
            <option value='\n' data-i18n="csv.newline.linux"></option>
 | 
			
		||||
            <option value='\r' data-i18n="csv.newline.mac"></option>
 | 
			
		||||
            <option value='\r\n' data-i18n="csv.newline.windows"></option>
 | 
			
		||||
       </select>
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('csv',{
 | 
			
		||||
        category: 'parser',
 | 
			
		||||
        color:"#DEBD5C",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)},
 | 
			
		||||
            //quo: {value:'"',required:true},
 | 
			
		||||
            hdrin: {value:""},
 | 
			
		||||
            hdrout: {value:"none"},
 | 
			
		||||
            multi: {value:"one",required:true},
 | 
			
		||||
            ret: {value:'\\n'},
 | 
			
		||||
            temp: {value:""},
 | 
			
		||||
            skip: {value:"0"},
 | 
			
		||||
            strings: {value:true},
 | 
			
		||||
            include_empty_strings: {value:""},
 | 
			
		||||
            include_null_values: {value:""}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "parser-csv.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||"csv";
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            if (this.hdrout === false) { this.hdrout = "none"; $("#node-input-hdrout").val("none"); }
 | 
			
		||||
            if (this.hdrout === true) { this.hdrout = "all"; $("#node-input-hdrout").val("all");}
 | 
			
		||||
            if (this.strings === undefined) { this.strings = true; $("#node-input-strings").prop('checked', true); }
 | 
			
		||||
            if (this.skip === undefined) { this.skip = 0; $("#node-input-skip").val("0");}
 | 
			
		||||
            $("#node-input-skip").spinner({ min:0 });
 | 
			
		||||
            if (this.sep == "," || this.sep == "\\t" || this.sep == ";" || this.sep == ":" || this.sep == " " || this.sep == "#") {
 | 
			
		||||
                $("#node-input-select-sep").val(this.sep);
 | 
			
		||||
                $("#node-input-sep").hide();
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-input-select-sep").val("");
 | 
			
		||||
                $("#node-input-sep").val(this.sep);
 | 
			
		||||
                $("#node-input-sep").show();
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-select-sep").on("change", function() {
 | 
			
		||||
                var v = $("#node-input-select-sep").val();
 | 
			
		||||
                $("#node-input-sep").val(v);
 | 
			
		||||
                if (v == "") {
 | 
			
		||||
                    $("#node-input-sep").val("");
 | 
			
		||||
                    $("#node-input-sep").show().focus();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-input-sep").hide();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,319 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    function CSVNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.template = (n.temp || "");
 | 
			
		||||
        this.sep = (n.sep || ',').replace("\\t","\t").replace("\\n","\n").replace("\\r","\r");
 | 
			
		||||
        this.quo = '"';
 | 
			
		||||
        this.ret = (n.ret || "\n").replace("\\n","\n").replace("\\r","\r");
 | 
			
		||||
        this.winflag = (this.ret === "\r\n");
 | 
			
		||||
        this.lineend = "\n";
 | 
			
		||||
        this.multi = n.multi || "one";
 | 
			
		||||
        this.hdrin = n.hdrin || false;
 | 
			
		||||
        this.hdrout = n.hdrout || "none";
 | 
			
		||||
        this.goodtmpl = true;
 | 
			
		||||
        this.skip = parseInt(n.skip || 0);
 | 
			
		||||
        this.store = [];
 | 
			
		||||
        this.parsestrings = n.strings;
 | 
			
		||||
        this.include_empty_strings = n.include_empty_strings || false;
 | 
			
		||||
        this.include_null_values = n.include_null_values || false;
 | 
			
		||||
        if (this.parsestrings === undefined) { this.parsestrings = true; }
 | 
			
		||||
        if (this.hdrout === false) { this.hdrout = "none"; }
 | 
			
		||||
        if (this.hdrout === true) { this.hdrout = "all"; }
 | 
			
		||||
        var tmpwarn = true;
 | 
			
		||||
        var node = this;
 | 
			
		||||
        var re = new RegExp(node.sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') + '(?=(?:(?:[^"]*"){2})*[^"]*$)','g');
 | 
			
		||||
 | 
			
		||||
        // pass in an array of column names to be trimmed, de-quoted and retrimmed
 | 
			
		||||
        var clean = function(col,sep) {
 | 
			
		||||
            if (sep) { re = new RegExp(sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') +'(?=(?:(?:[^"]*"){2})*[^"]*$)','g'); }
 | 
			
		||||
            col = col.trim().split(re) || [""];
 | 
			
		||||
            col = col.map(x => x.replace(/"/g,'').trim());
 | 
			
		||||
            if ((col.length === 1) && (col[0] === "")) { node.goodtmpl = false; }
 | 
			
		||||
            else { node.goodtmpl = true; }
 | 
			
		||||
            return col;
 | 
			
		||||
        }
 | 
			
		||||
        var template = clean(node.template,',');
 | 
			
		||||
        var notemplate = template.length === 1 && template[0] === '';
 | 
			
		||||
        node.hdrSent = false;
 | 
			
		||||
 | 
			
		||||
        this.on("input", function(msg, send, done) {
 | 
			
		||||
            if (msg.hasOwnProperty("reset")) {
 | 
			
		||||
                node.hdrSent = false;
 | 
			
		||||
            }
 | 
			
		||||
            if (msg.hasOwnProperty("payload")) {
 | 
			
		||||
                if (typeof msg.payload == "object") { // convert object to CSV string
 | 
			
		||||
                    try {
 | 
			
		||||
                        if (!(notemplate && (msg.hasOwnProperty("parts") && msg.parts.hasOwnProperty("index") && msg.parts.index > 0))) {
 | 
			
		||||
                            template = clean(node.template);
 | 
			
		||||
                        }
 | 
			
		||||
                        var ou = "";
 | 
			
		||||
                        if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; }
 | 
			
		||||
                        if (node.hdrout !== "none" && node.hdrSent === false) {
 | 
			
		||||
                            if ((template.length === 1) && (template[0] === '')) {
 | 
			
		||||
                                if (msg.hasOwnProperty("columns")) {
 | 
			
		||||
                                    template = clean(msg.columns || "",",");
 | 
			
		||||
                                }
 | 
			
		||||
                                else {
 | 
			
		||||
                                    template = Object.keys(msg.payload[0]);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            ou += template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).join(node.sep) + node.ret;
 | 
			
		||||
                            if (node.hdrout === "once") { node.hdrSent = true; }
 | 
			
		||||
                        }
 | 
			
		||||
                        for (var s = 0; s < msg.payload.length; s++) {
 | 
			
		||||
                            if ((Array.isArray(msg.payload[s])) || (typeof msg.payload[s] !== "object")) {
 | 
			
		||||
                                if (typeof msg.payload[s] !== "object") { msg.payload = [ msg.payload ]; }
 | 
			
		||||
                                for (var t = 0; t < msg.payload[s].length; t++) {
 | 
			
		||||
                                    if (msg.payload[s][t] === undefined) { msg.payload[s][t] = ""; }
 | 
			
		||||
                                    if (msg.payload[s][t].toString().indexOf(node.quo) !== -1) { // add double quotes if any quotes
 | 
			
		||||
                                        msg.payload[s][t] = msg.payload[s][t].toString().replace(/"/g, '""');
 | 
			
		||||
                                        msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    else if (msg.payload[s][t].toString().indexOf(node.sep) !== -1) { // add quotes if any "commas"
 | 
			
		||||
                                        msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                ou += msg.payload[s].join(node.sep) + node.ret;
 | 
			
		||||
                            }
 | 
			
		||||
                            else {
 | 
			
		||||
                                if ((template.length === 1) && (template[0] === '') && (msg.hasOwnProperty("columns"))) {
 | 
			
		||||
                                    template = clean(msg.columns || "",",");
 | 
			
		||||
                                }
 | 
			
		||||
                                if ((template.length === 1) && (template[0] === '')) {
 | 
			
		||||
                                    /* istanbul ignore else */
 | 
			
		||||
                                    if (tmpwarn === true) { // just warn about missing template once
 | 
			
		||||
                                        node.warn(RED._("csv.errors.obj_csv"));
 | 
			
		||||
                                        tmpwarn = false;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    for (var p in msg.payload[0]) {
 | 
			
		||||
                                        /* istanbul ignore else */
 | 
			
		||||
                                        if (msg.payload[s].hasOwnProperty(p)) {
 | 
			
		||||
                                            /* istanbul ignore else */
 | 
			
		||||
                                            if (typeof msg.payload[s][p] !== "object") {
 | 
			
		||||
                                                var q = "" + msg.payload[s][p];
 | 
			
		||||
                                                if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes
 | 
			
		||||
                                                    q = q.replace(/"/g, '""');
 | 
			
		||||
                                                    ou += node.quo + q + node.quo + node.sep;
 | 
			
		||||
                                                }
 | 
			
		||||
                                                else if (q.indexOf(node.sep) !== -1) { // add quotes if any "commas"
 | 
			
		||||
                                                    ou += node.quo + q + node.quo + node.sep;
 | 
			
		||||
                                                }
 | 
			
		||||
                                                else { ou += q + node.sep; } // otherwise just add
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                    ou = ou.slice(0,-1) + node.ret;
 | 
			
		||||
                                }
 | 
			
		||||
                                else {
 | 
			
		||||
                                    for (var t=0; t < template.length; t++) {
 | 
			
		||||
                                        if (template[t] === '') {
 | 
			
		||||
                                            ou += node.sep;
 | 
			
		||||
                                        }
 | 
			
		||||
                                        else {
 | 
			
		||||
                                            var p = RED.util.ensureString(RED.util.getMessageProperty(msg,"payload["+s+"]['"+template[t]+"']"));
 | 
			
		||||
                                            /* istanbul ignore else */
 | 
			
		||||
                                            if (p === "undefined") { p = ""; }
 | 
			
		||||
                                            if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes
 | 
			
		||||
                                                p = p.replace(/"/g, '""');
 | 
			
		||||
                                                ou += node.quo + p + node.quo + node.sep;
 | 
			
		||||
                                            }
 | 
			
		||||
                                            else if (p.indexOf(node.sep) !== -1) { // add quotes if any "commas"
 | 
			
		||||
                                                ou += node.quo + p + node.quo + node.sep;
 | 
			
		||||
                                            }
 | 
			
		||||
                                            else { ou += p + node.sep; } // otherwise just add
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                    ou = ou.slice(0,-1) + node.ret; // remove final "comma" and add "newline"
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        msg.payload = ou;
 | 
			
		||||
                        msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).join(',');
 | 
			
		||||
                        if (msg.payload !== '') { send(msg); }
 | 
			
		||||
                        done();
 | 
			
		||||
                    }
 | 
			
		||||
                    catch(e) { done(e); }
 | 
			
		||||
                }
 | 
			
		||||
                else if (typeof msg.payload == "string") { // convert CSV string to object
 | 
			
		||||
                    try {
 | 
			
		||||
                        var f = true; // flag to indicate if inside or outside a pair of quotes true = outside.
 | 
			
		||||
                        var j = 0; // pointer into array of template items
 | 
			
		||||
                        var k = [""]; // array of data for each of the template items
 | 
			
		||||
                        var o = {}; // output object to build up
 | 
			
		||||
                        var a = []; // output array is needed for multiline option
 | 
			
		||||
                        var first = true; // is this the first line
 | 
			
		||||
                        var last = false;
 | 
			
		||||
                        var line = msg.payload;
 | 
			
		||||
                        var linecount = 0;
 | 
			
		||||
                        var tmp = "";
 | 
			
		||||
                        var has_parts = msg.hasOwnProperty("parts");
 | 
			
		||||
                        var reg = /^[-]?(?!E)(?!0\d)\d*\.?\d*(E-?\+?)?\d+$/i;
 | 
			
		||||
                        if (msg.hasOwnProperty("parts")) {
 | 
			
		||||
                            linecount = msg.parts.index;
 | 
			
		||||
                            if (msg.parts.index > node.skip) { first = false; }
 | 
			
		||||
                            if (msg.parts.hasOwnProperty("count") && (msg.parts.index+1 >= msg.parts.count)) { last = true; }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // For now we are just going to assume that any \r or \n means an end of line...
 | 
			
		||||
                        //   got to be a weird csv that has singleton \r \n in it for another reason...
 | 
			
		||||
 | 
			
		||||
                        // Now process the whole file/line
 | 
			
		||||
                        var nocr = (line.match(/[\r\n]/g)||[]).length;
 | 
			
		||||
                        if (has_parts && node.multi === "mult" && nocr > 1) { tmp = ""; first = true; }
 | 
			
		||||
                        for (var i = 0; i < line.length; i++) {
 | 
			
		||||
                            if (first && (linecount < node.skip)) {
 | 
			
		||||
                                if (line[i] === "\n") { linecount += 1; }
 | 
			
		||||
                                continue;
 | 
			
		||||
                            }
 | 
			
		||||
                            if ((node.hdrin === true) && first) { // if the template is in the first line
 | 
			
		||||
                                if ((line[i] === "\n")||(line[i] === "\r")||(line.length - i === 1)) { // look for first line break
 | 
			
		||||
                                    if (line.length - i === 1) { tmp += line[i]; }
 | 
			
		||||
                                    template = clean(tmp,node.sep);
 | 
			
		||||
                                    first = false;
 | 
			
		||||
                                }
 | 
			
		||||
                                else { tmp += line[i]; }
 | 
			
		||||
                            }
 | 
			
		||||
                            else {
 | 
			
		||||
                                if (line[i] === node.quo) { // if it's a quote toggle inside or outside
 | 
			
		||||
                                    f = !f;
 | 
			
		||||
                                    if (line[i-1] === node.quo) {
 | 
			
		||||
                                        if (f === false) { k[j] += '\"'; }
 | 
			
		||||
                                    } // if it's a quotequote then it's actually a quote
 | 
			
		||||
                                    //if ((line[i-1] !== node.sep) && (line[i+1] !== node.sep)) { k[j] += line[i]; }
 | 
			
		||||
                                }
 | 
			
		||||
                                else if ((line[i] === node.sep) && f) { // if it is the end of the line then finish
 | 
			
		||||
                                    if (!node.goodtmpl) { template[j] = "col"+(j+1); }
 | 
			
		||||
                                    if ( template[j] && (template[j] !== "") ) {
 | 
			
		||||
                                        // if no value between separators ('1,,"3"...') or if the line beings with separator (',1,"2"...') treat value as null
 | 
			
		||||
                                        if (line[i-1] === node.sep || line[i-1].includes('\n','\r')) k[j] = null;
 | 
			
		||||
                                        if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j].trim()) ) { k[j] = parseFloat(k[j].trim()); }
 | 
			
		||||
                                        if (node.include_null_values && k[j] === null) o[template[j]] = k[j];
 | 
			
		||||
                                        if (node.include_empty_strings && k[j] === "") o[template[j]] = k[j];
 | 
			
		||||
                                        if (k[j] !== null && k[j] !== "") o[template[j]] = k[j];
 | 
			
		||||
                                    }
 | 
			
		||||
                                    j += 1;
 | 
			
		||||
                                    // if separator is last char in processing string line (without end of line), add null value at the end - example: '1,2,3\n3,"3",'
 | 
			
		||||
                                    k[j] = line.length - 1 === i ? null : "";
 | 
			
		||||
                                }
 | 
			
		||||
                                else if (((line[i] === "\n") || (line[i] === "\r")) && f) { // handle multiple lines
 | 
			
		||||
                                    //console.log(j,k,o,k[j]);
 | 
			
		||||
                                    if (!node.goodtmpl) { template[j] = "col"+(j+1); }
 | 
			
		||||
                                    if ( template[j] && (template[j] !== "") ) {
 | 
			
		||||
                                        // if separator before end of line, set null value ie. '1,2,"3"\n1,2,\n1,2,3'
 | 
			
		||||
                                        if (line[i-1] === node.sep) k[j] = null;
 | 
			
		||||
                                        if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j].trim()) ) { k[j] = parseFloat(k[j].trim()); }
 | 
			
		||||
                                        else { if (k[j] !== null) k[j].replace(/\r$/,''); }
 | 
			
		||||
                                        if (node.include_null_values && k[j] === null) o[template[j]] = k[j];
 | 
			
		||||
                                        if (node.include_empty_strings && k[j] === "") o[template[j]] = k[j];
 | 
			
		||||
                                        if (k[j] !== null && k[j] !== "") o[template[j]] = k[j];
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (JSON.stringify(o) !== "{}") { // don't send empty objects
 | 
			
		||||
                                        a.push(o); // add to the array
 | 
			
		||||
                                    }
 | 
			
		||||
                                    j = 0;
 | 
			
		||||
                                    k = [""];
 | 
			
		||||
                                    o = {};
 | 
			
		||||
                                    f = true; // reset in/out flag ready for next line.
 | 
			
		||||
                                }
 | 
			
		||||
                                else { // just add to the part of the message
 | 
			
		||||
                                    k[j] += line[i];
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        // Finished so finalize and send anything left
 | 
			
		||||
                        if (f === false) { node.warn(RED._("csv.errors.bad_csv")); }
 | 
			
		||||
                        if (!node.goodtmpl) { template[j] = "col"+(j+1); }
 | 
			
		||||
 | 
			
		||||
                        if ( template[j] && (template[j] !== "") ) {
 | 
			
		||||
                            if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j].trim()) ) { k[j] = parseFloat(k[j].trim()); }
 | 
			
		||||
                            else { if (k[j] !== null) k[j].replace(/\r$/,''); }
 | 
			
		||||
                            if (node.include_null_values && k[j] === null) o[template[j]] = k[j];
 | 
			
		||||
                            if (node.include_empty_strings && k[j] === "") o[template[j]] = k[j];
 | 
			
		||||
                            if (k[j] !== null && k[j] !== "") o[template[j]] = k[j];
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (JSON.stringify(o) !== "{}") { // don't send empty objects
 | 
			
		||||
                            a.push(o); // add to the array
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (node.multi !== "one") {
 | 
			
		||||
                            msg.payload = a;
 | 
			
		||||
                            if (has_parts && nocr <= 1) {
 | 
			
		||||
                                if (JSON.stringify(o) !== "{}") {
 | 
			
		||||
                                    node.store.push(o);
 | 
			
		||||
                                }
 | 
			
		||||
                                if (msg.parts.index + 1 === msg.parts.count) {
 | 
			
		||||
                                    msg.payload = node.store;
 | 
			
		||||
                                    msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
 | 
			
		||||
                                    delete msg.parts;
 | 
			
		||||
                                    send(msg);
 | 
			
		||||
                                    node.store = [];
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            else {
 | 
			
		||||
                                msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
 | 
			
		||||
                                send(msg); // finally send the array
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            var len = a.length;
 | 
			
		||||
                            for (var i = 0; i < len; i++) {
 | 
			
		||||
                                var newMessage = RED.util.cloneMessage(msg);
 | 
			
		||||
                                newMessage.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
 | 
			
		||||
                                newMessage.payload = a[i];
 | 
			
		||||
                                if (!has_parts) {
 | 
			
		||||
                                    newMessage.parts = {
 | 
			
		||||
                                        id: msg._msgid,
 | 
			
		||||
                                        index: i,
 | 
			
		||||
                                        count: len
 | 
			
		||||
                                    };
 | 
			
		||||
                                }
 | 
			
		||||
                                else {
 | 
			
		||||
                                    newMessage.parts.index -= node.skip;
 | 
			
		||||
                                    newMessage.parts.count -= node.skip;
 | 
			
		||||
                                    if (node.hdrin) { // if we removed the header line then shift the counts by 1
 | 
			
		||||
                                        newMessage.parts.index -= 1;
 | 
			
		||||
                                        newMessage.parts.count -= 1;
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                if (last) { newMessage.complete = true; }
 | 
			
		||||
                                send(newMessage);
 | 
			
		||||
                            }
 | 
			
		||||
                            if (has_parts && last && len === 0) {
 | 
			
		||||
                                send({complete:true});
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        node.linecount = 0;
 | 
			
		||||
                        done();
 | 
			
		||||
                    }
 | 
			
		||||
                    catch(e) { done(e); }
 | 
			
		||||
                }
 | 
			
		||||
                else { node.warn(RED._("csv.errors.csv_js")); done(); }
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                if (!msg.hasOwnProperty("reset")) {
 | 
			
		||||
                    node.send(msg); // If no payload and not reset - just pass it on.
 | 
			
		||||
                }
 | 
			
		||||
                done();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("csv",CSVNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,64 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="html">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-property" style="width:70%">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-tag"><i class="fa fa-filter"></i> <span data-i18n="html.label.select"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-tag" placeholder="h1">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-ret"><i class="fa fa-sign-out"></i> <span data-i18n="html.label.output"></span></label>
 | 
			
		||||
        <select id="node-input-ret" style="width:70%">
 | 
			
		||||
            <option value="html" data-i18n="html.output.html"></option>
 | 
			
		||||
            <option value="text" data-i18n="html.output.text"></option>
 | 
			
		||||
            <option value="attr" data-i18n="html.output.attr"></option>
 | 
			
		||||
            <!-- <option value="val">return the value from a form element</option> -->
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-as"> </label>
 | 
			
		||||
        <select id="node-input-as" style="width:70%">
 | 
			
		||||
            <option value="single" data-i18n="html.format.single"></option>
 | 
			
		||||
            <option value="multi" data-i18n="html.format.multi"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-outproperty"> </label>
 | 
			
		||||
        <span data-i18n="html.label.in" style="padding-left:8px; padding-right:2px; vertical-align:-1px;"></span> <input type="text" id="node-input-outproperty" style="width:64%">
 | 
			
		||||
    </div>
 | 
			
		||||
    <br/>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" style="width:70%" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('html',{
 | 
			
		||||
        category: 'parser',
 | 
			
		||||
        color:"#DEBD5C",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            property: {value:"payload"},
 | 
			
		||||
            outproperty: {value:"payload"},
 | 
			
		||||
            tag: {value:""},
 | 
			
		||||
            ret: {value:"html"},
 | 
			
		||||
            as: {value:"single"}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "parser-html.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||this.tag||"html";
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            $("#node-input-property").typedInput({default:'msg',types:['msg']});
 | 
			
		||||
            $("#node-input-outproperty").typedInput({default:'msg',types:['msg']});
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,90 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    var cheerio = require('cheerio');
 | 
			
		||||
 | 
			
		||||
    function CheerioNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.property = n.property||"payload";
 | 
			
		||||
        this.outproperty = n.outproperty||this.property||"payload";
 | 
			
		||||
        this.tag = n.tag;
 | 
			
		||||
        this.ret = n.ret || "html";
 | 
			
		||||
        this.as = n.as || "single";
 | 
			
		||||
        var node = this;
 | 
			
		||||
        this.on("input", function(msg,send,done) {
 | 
			
		||||
            var value = RED.util.getMessageProperty(msg,node.property);
 | 
			
		||||
            if (value !== undefined) {
 | 
			
		||||
                var tag = node.tag;
 | 
			
		||||
                if (msg.hasOwnProperty("select")) { tag = node.tag || msg.select; }
 | 
			
		||||
                try {
 | 
			
		||||
                    var $ = cheerio.load(value);
 | 
			
		||||
                    var pay = [];
 | 
			
		||||
                    var count = 0;
 | 
			
		||||
                    $(tag).each(function() {
 | 
			
		||||
                        count++;
 | 
			
		||||
                    });
 | 
			
		||||
                    var index = 0;
 | 
			
		||||
                    $(tag).each(function() {
 | 
			
		||||
                        if (node.as === "multi") {
 | 
			
		||||
                            var pay2 = null;
 | 
			
		||||
                            if (node.ret === "html") { pay2 = cheerio.load($(this).html().trim(),null,false).xml(); }
 | 
			
		||||
                            if (node.ret === "text") { pay2 = $(this).text(); }
 | 
			
		||||
                            if (node.ret === "attr") {
 | 
			
		||||
                                pay2 = Object.assign({},this.attribs);
 | 
			
		||||
                            }
 | 
			
		||||
                            //if (node.ret === "val")  { pay2 = $(this).val(); }
 | 
			
		||||
                            /* istanbul ignore else */
 | 
			
		||||
                            if (pay2) {
 | 
			
		||||
                                var new_msg = RED.util.cloneMessage(msg);
 | 
			
		||||
                                RED.util.setMessageProperty(new_msg,node.outproperty,pay2);
 | 
			
		||||
                                new_msg.parts = {
 | 
			
		||||
                                    id: msg._msgid,
 | 
			
		||||
                                    index: index,
 | 
			
		||||
                                    count: count,
 | 
			
		||||
                                    type: "string",
 | 
			
		||||
                                    ch: ""
 | 
			
		||||
                                };
 | 
			
		||||
                                send(new_msg);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        if (node.as === "single") {
 | 
			
		||||
                            if (node.ret === "html") { pay.push( cheerio.load($(this).html().trim(),null,false).xml() ); }
 | 
			
		||||
                            if (node.ret === "text") { pay.push( $(this).text() ); }
 | 
			
		||||
                            if (node.ret === "attr") {
 | 
			
		||||
                                var attribs = Object.assign({},this.attribs);
 | 
			
		||||
                                pay.push( attribs );
 | 
			
		||||
                            }
 | 
			
		||||
                            //if (node.ret === "val")  { pay.push( $(this).val() ); }
 | 
			
		||||
                        }
 | 
			
		||||
                        index++;
 | 
			
		||||
                    });
 | 
			
		||||
                    if (node.as === "single") {  // Always return an array - even if blank
 | 
			
		||||
                        RED.util.setMessageProperty(msg,node.outproperty,pay);
 | 
			
		||||
                        send(msg);
 | 
			
		||||
                    }
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
                catch (error) {
 | 
			
		||||
                    done(error.message);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else { send(msg); done(); } // If no payload - just pass it on.
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("html",CheerioNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,62 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="json">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-action"><i class="fa fa-dot-circle-o"></i> <span data-i18n="json.label.action"></span></label>
 | 
			
		||||
        <select style="width:70%" id="node-input-action">
 | 
			
		||||
            <option value=""    data-i18n="json.label.actions.toggle"></option>
 | 
			
		||||
            <option value="str" data-i18n="json.label.actions.str"></option>
 | 
			
		||||
            <option value="obj" data-i18n="json.label.actions.obj"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="json.label.property"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-property" style="width:70%;"/>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr align="middle"/>
 | 
			
		||||
    <div class="form-row node-json-to-json-options">
 | 
			
		||||
        <label style="width:100%;"><span data-i18n="json.label.o2j"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-json-to-json-options" style="padding-left: 20px;">
 | 
			
		||||
        <input style="width:20px; vertical-align:top; margin-right: 5px;" type="checkbox" id="node-input-pretty"><label style="width: auto;" for="node-input-pretty" data-i18n="json.label.pretty"></span>
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('json',{
 | 
			
		||||
        category: 'parser',
 | 
			
		||||
        color:"#DEBD5C",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            property: {value:"payload",required:true},
 | 
			
		||||
            action: {value:""},
 | 
			
		||||
            pretty: {value:false}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "parser-json.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||"json";
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            if (this.property === undefined) {
 | 
			
		||||
                $("#node-input-property").val("payload");
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-property").typedInput({default:'msg',types:['msg']});
 | 
			
		||||
            $("#node-input-action").on("change", function() {
 | 
			
		||||
                if (this.value === "" || this.value === "str") {
 | 
			
		||||
                    $(".node-json-to-json-options").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $(".node-json-to-json-options").hide();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-action").change();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,134 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    const Ajv = require('ajv');
 | 
			
		||||
    const ajv = new Ajv({allErrors: true});
 | 
			
		||||
    ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
 | 
			
		||||
 | 
			
		||||
    function JSONNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.indent = n.pretty ? 4 : 0;
 | 
			
		||||
        this.action = n.action||"";
 | 
			
		||||
        this.property = n.property||"payload";
 | 
			
		||||
        this.schema = null;
 | 
			
		||||
        this.compiledSchema = null;
 | 
			
		||||
 | 
			
		||||
        var node = this;
 | 
			
		||||
 | 
			
		||||
        this.on("input", function(msg,send,done) {
 | 
			
		||||
            var validate = false;
 | 
			
		||||
            if (msg.schema) {
 | 
			
		||||
                // If input schema is different, re-compile it
 | 
			
		||||
                if (JSON.stringify(this.schema) != JSON.stringify(msg.schema)) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        this.compiledSchema = ajv.compile(msg.schema);
 | 
			
		||||
                        this.schema = msg.schema;
 | 
			
		||||
                    } catch(e) {
 | 
			
		||||
                        this.schema = null;
 | 
			
		||||
                        this.compiledSchema = null;
 | 
			
		||||
                        done(RED._("json.errors.schema-error-compile"));
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                validate = true;
 | 
			
		||||
            }
 | 
			
		||||
            var value = RED.util.getMessageProperty(msg,node.property);
 | 
			
		||||
            if (value !== undefined) {
 | 
			
		||||
                if (typeof value === "string") {
 | 
			
		||||
                    if (node.action === "" || node.action === "obj") {
 | 
			
		||||
                        try {
 | 
			
		||||
                            RED.util.setMessageProperty(msg,node.property,JSON.parse(value));
 | 
			
		||||
                            if (validate) {
 | 
			
		||||
                                if (this.compiledSchema(msg[node.property])) {
 | 
			
		||||
                                    delete msg.schema;
 | 
			
		||||
                                    send(msg);
 | 
			
		||||
                                    done();
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    msg.schemaError = this.compiledSchema.errors;
 | 
			
		||||
                                    done(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`);
 | 
			
		||||
                                }
 | 
			
		||||
                            } else  {
 | 
			
		||||
                                send(msg);
 | 
			
		||||
                                done();
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        catch(e) { done(e.message); }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // If node.action is str and value is str
 | 
			
		||||
                        if (validate) {
 | 
			
		||||
                            if (this.compiledSchema(JSON.parse(msg[node.property]))) {
 | 
			
		||||
                                delete msg.schema;
 | 
			
		||||
                                send(msg);
 | 
			
		||||
                                done();
 | 
			
		||||
                            } else {
 | 
			
		||||
                                msg.schemaError = this.compiledSchema.errors;
 | 
			
		||||
                                done(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`);
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            send(msg);
 | 
			
		||||
                            done();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else if ((typeof value === "object") || (typeof value === "boolean") || (typeof value === "number")) {
 | 
			
		||||
                    if (node.action === "" || node.action === "str") {
 | 
			
		||||
                        if (!Buffer.isBuffer(value)) {
 | 
			
		||||
                            try {
 | 
			
		||||
                                if (validate) {
 | 
			
		||||
                                    if (this.compiledSchema(value)) {
 | 
			
		||||
                                        RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent));
 | 
			
		||||
                                        delete msg.schema;
 | 
			
		||||
                                        send(msg);
 | 
			
		||||
                                        done();
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        msg.schemaError = this.compiledSchema.errors;
 | 
			
		||||
                                        done(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`);
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent));
 | 
			
		||||
                                    send(msg);
 | 
			
		||||
                                    done();
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            catch(e) { done(RED._("json.errors.dropped-error")); }
 | 
			
		||||
                        }
 | 
			
		||||
                        else { node.warn(RED._("json.errors.dropped-object")); done(); }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // If node.action is obj and value is object
 | 
			
		||||
                        if (validate) {
 | 
			
		||||
                            if (this.compiledSchema(value)) {
 | 
			
		||||
                                delete msg.schema;
 | 
			
		||||
                                send(msg);
 | 
			
		||||
                                done();
 | 
			
		||||
                            } else {
 | 
			
		||||
                                msg.schemaError = this.compiledSchema.errors;
 | 
			
		||||
                                done(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`);
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            send(msg);
 | 
			
		||||
                            done();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else { node.warn(RED._("json.errors.dropped")); done(); }
 | 
			
		||||
            }
 | 
			
		||||
            else { send(msg); done(); } // If no property - just pass it on.
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("json",JSONNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,49 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="xml">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-property" style="width:70%;"/>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr align="middle"/>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label style="width:100%;"><span data-i18n="xml.label.x2o"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" style="padding-left: 20px;">
 | 
			
		||||
        <label style="width:250px;" for="node-input-attr" data-i18n="xml.label.represent"></label> <input type="text" id="node-input-attr" style="text-align:center; width:40px" placeholder="$">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" style="padding-left: 20px;">
 | 
			
		||||
        <label style="width:250px;" for="node-input-chr" data-i18n="xml.label.prefix"></label> <input type="text" id="node-input-chr" style="text-align:center; width:40px" placeholder="_">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('xml',{
 | 
			
		||||
        category: 'parser',
 | 
			
		||||
        color:"#DEBD5C",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            property: {value:"payload",required:true},
 | 
			
		||||
            attr: {value:""},
 | 
			
		||||
            chr: {value:""}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "parser-xml.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||"xml";
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            if (this.property === undefined) {
 | 
			
		||||
                $("#node-input-property").val("payload");
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-property").typedInput({default:'msg',types:['msg']});
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,49 +0,0 @@
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    var xml2js = require('xml2js');
 | 
			
		||||
    var parseString = xml2js.parseString;
 | 
			
		||||
 | 
			
		||||
    function XMLNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.attrkey = n.attr;
 | 
			
		||||
        this.charkey = n.chr;
 | 
			
		||||
        this.property = n.property||"payload";
 | 
			
		||||
        var node = this;
 | 
			
		||||
        this.on("input", function(msg,send,done) {
 | 
			
		||||
            var value = RED.util.getMessageProperty(msg,node.property);
 | 
			
		||||
            if (value !== undefined) {
 | 
			
		||||
                var options;
 | 
			
		||||
                if (typeof value === "object") {
 | 
			
		||||
                    options = {renderOpts:{pretty:false}};
 | 
			
		||||
                    if (msg.hasOwnProperty("options") && typeof msg.options === "object") { options = msg.options; }
 | 
			
		||||
                    options.async = false;
 | 
			
		||||
                    var builder = new xml2js.Builder(options);
 | 
			
		||||
                    value = builder.buildObject(value, options);
 | 
			
		||||
                    RED.util.setMessageProperty(msg,node.property,value);
 | 
			
		||||
                    send(msg);
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
                else if (typeof value == "string") {
 | 
			
		||||
                    options = {};
 | 
			
		||||
                    if (msg.hasOwnProperty("options") && typeof msg.options === "object") { options = msg.options; }
 | 
			
		||||
                    options.async = true;
 | 
			
		||||
                    options.attrkey = node.attrkey || options.attrkey || '$';
 | 
			
		||||
                    options.charkey = node.charkey || options.charkey || '_';
 | 
			
		||||
                    parseString(value, options, function (err, result) {
 | 
			
		||||
                        if (err) { done(err); }
 | 
			
		||||
                        else {
 | 
			
		||||
                            value = result;
 | 
			
		||||
                            RED.util.setMessageProperty(msg,node.property,value);
 | 
			
		||||
                            send(msg);
 | 
			
		||||
                            done();
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                else { node.warn(RED._("xml.errors.xml_js")); done(); }
 | 
			
		||||
            }
 | 
			
		||||
            else { send(msg); done(); } // If no property - just pass it on.
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("xml",XMLNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,37 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="yaml">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-property" style="width:70%;"/>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('yaml',{
 | 
			
		||||
        category: 'parser',
 | 
			
		||||
        color:"#DEBD5C",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            property: {value:"payload",required:true},
 | 
			
		||||
            name: {value:""}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "parser-yaml.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||"yaml";
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            if (this.property === undefined) {
 | 
			
		||||
                $("#node-input-property").val("payload");
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-property").typedInput({default:'msg',types:['msg']});
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,41 +0,0 @@
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    var yaml = require('js-yaml');
 | 
			
		||||
    function YAMLNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.property = n.property||"payload";
 | 
			
		||||
        var node = this;
 | 
			
		||||
        this.on("input", function(msg,send,done) {
 | 
			
		||||
            var value = RED.util.getMessageProperty(msg,node.property);
 | 
			
		||||
            if (value !== undefined) {
 | 
			
		||||
                if (typeof value === "string") {
 | 
			
		||||
                    try {
 | 
			
		||||
                        value = yaml.load(value);
 | 
			
		||||
                        RED.util.setMessageProperty(msg,node.property,value);
 | 
			
		||||
                        send(msg);
 | 
			
		||||
                        done();
 | 
			
		||||
                    }
 | 
			
		||||
                    catch(e) { done(e.message); }
 | 
			
		||||
                }
 | 
			
		||||
                else if (typeof value === "object") {
 | 
			
		||||
                    if (!Buffer.isBuffer(value)) {
 | 
			
		||||
                        try {
 | 
			
		||||
                            value = yaml.dump(value);
 | 
			
		||||
                            RED.util.setMessageProperty(msg,node.property,value);
 | 
			
		||||
                            send(msg);
 | 
			
		||||
                            done();
 | 
			
		||||
                        }
 | 
			
		||||
                        catch(e) {
 | 
			
		||||
                            done(RED._("yaml.errors.dropped-error"));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else { node.warn(RED._("yaml.errors.dropped-object")); done(); }
 | 
			
		||||
                }
 | 
			
		||||
                else { node.warn(RED._("yaml.errors.dropped")); done(); }
 | 
			
		||||
            }
 | 
			
		||||
            else { send(msg); done(); } // If no payload - just pass it on.
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("yaml",YAMLNode);
 | 
			
		||||
};
 | 
			
		||||
@@ -1,325 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="split">
 | 
			
		||||
    <div class="form-row"><span data-i18n="[html]split.intro"></span></div>
 | 
			
		||||
    <div class="form-row"><span data-i18n="[html]split.strBuff"></span></div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-splt" style="padding-left:10px; margin-right:-10px;" data-i18n="split.splitUsing"></label>
 | 
			
		||||
        <input type="text" id="node-input-splt" style="width:70%">
 | 
			
		||||
        <input type="hidden" id="node-input-spltType">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <input type="checkbox" id="node-input-stream" style="margin-left:10px; vertical-align:top; width:auto;">
 | 
			
		||||
        <label for="node-input-stream" style="width:auto;" data-i18n="split.stream"></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row"><span data-i18n="[html]split.array"></span></div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-arraySplt" style="padding-left:10px; margin-right:-10px;" data-i18n="split.splitUsing"></label>
 | 
			
		||||
        <input type="text" id="node-input-arraySplt" style="width:70%">
 | 
			
		||||
        <input type="hidden" id="node-input-arraySpltType">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row"><span data-i18n="[html]split.object"></span></div>
 | 
			
		||||
    <div class="form-row" style="padding-left: 10px"><span data-i18n="[html]split.objectSend"></span></div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <input type="checkbox" id="node-input-addname-cb" style="margin-left:10px; vertical-align:baseline; width:auto;">
 | 
			
		||||
        <label for="node-input-addname-cb" style="width:auto;" data-i18n="split.addname"></label>
 | 
			
		||||
        <input type="text" id="node-input-addname" style="width:70%">
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr/>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('split',{
 | 
			
		||||
        category: 'sequence',
 | 
			
		||||
        color:"#E2D96E",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            splt: {value:"\\n"},
 | 
			
		||||
            spltType: {value:"str"},
 | 
			
		||||
            arraySplt: {value:1},
 | 
			
		||||
            arraySpltType: {value:"len"},
 | 
			
		||||
            stream: {value:false},
 | 
			
		||||
            addname: {value:""}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "split.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||this._("split.split");
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            $("#node-input-splt").typedInput({
 | 
			
		||||
                default: 'str',
 | 
			
		||||
                typeField: $("#node-input-spltType"),
 | 
			
		||||
                types:[
 | 
			
		||||
                    'str',
 | 
			
		||||
                    'bin',
 | 
			
		||||
                    {value:"len", label:RED._("node-red:split.splitLength"),validate:/^\d+$/}
 | 
			
		||||
                ]
 | 
			
		||||
            });
 | 
			
		||||
            if (this.arraySplt === undefined) {
 | 
			
		||||
                $("#node-input-arraySplt").val(1);
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-arraySplt").typedInput({
 | 
			
		||||
                default: 'len',
 | 
			
		||||
                typeField: $("#node-input-arraySpltType"),
 | 
			
		||||
                types:[
 | 
			
		||||
                    {value:"len", label:RED._("node-red:split.splitLength"),validate:/^\d+$/}
 | 
			
		||||
                ]
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-addname").typedInput({
 | 
			
		||||
                typeField: $("#node-input-fnameType"),
 | 
			
		||||
                types:['msg']
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $("#node-input-addname-cb").on("change", function() {
 | 
			
		||||
                $("#node-input-addname").prop('disabled',!this.checked);
 | 
			
		||||
            })
 | 
			
		||||
            if (this.addname === "") {
 | 
			
		||||
                $("#node-input-addname-cb").prop('checked',false);
 | 
			
		||||
                $("#node-input-addname").val('topic');
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#node-input-addname-cb").prop('checked',true);
 | 
			
		||||
            }
 | 
			
		||||
            $("#node-input-addname-cb").change();
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            if (!$("#node-input-addname-cb").prop('checked')) {
 | 
			
		||||
                $("#node-input-addname").val('');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="join">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label data-i18n="join.mode.mode"></label>
 | 
			
		||||
        <select id="node-input-mode" style="width:200px;">
 | 
			
		||||
            <option value="auto" data-i18n="join.mode.auto"></option>
 | 
			
		||||
            <option value="custom" data-i18n="join.mode.custom"></option>
 | 
			
		||||
            <option value="reduce" data-i18n="join.mode.reduce"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="node-row-custom">
 | 
			
		||||
        <div class="form-row node-row-property">
 | 
			
		||||
            <label data-i18n="join.combine"> </label>
 | 
			
		||||
            <input type="text" id="node-input-property" style="width:70%;">
 | 
			
		||||
            <input type="hidden" id="node-input-propertyType">
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="form-row">
 | 
			
		||||
            <label data-i18n="join.create"></label>
 | 
			
		||||
            <select id="node-input-build" style="width:70%;">
 | 
			
		||||
                <option value="string" data-i18n="join.type.string"></option>
 | 
			
		||||
                <option value="buffer" data-i18n="join.type.buffer"></option>
 | 
			
		||||
                <option value="array" data-i18n="join.type.array"></option>
 | 
			
		||||
                <option value="object" data-i18n="join.type.object"></option>
 | 
			
		||||
                <option value="merged" data-i18n="join.type.merged"></option>
 | 
			
		||||
            </select>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="form-row node-row-key">
 | 
			
		||||
            <label style="vertical-align:top; margin-top:7px; width:auto; margin-right: 5px;" data-i18n="join.using"></label>
 | 
			
		||||
            <div style="display:inline-block">
 | 
			
		||||
                <input type="text" id="node-input-key" style="width:220px;"> <span data-i18n="join.key"></span>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="form-row node-row-joiner">
 | 
			
		||||
            <label for="node-input-joiner" data-i18n="join.joinedUsing"></label>
 | 
			
		||||
            <input type="text" id="node-input-joiner" style="width:70%">
 | 
			
		||||
            <input type="hidden" id="node-input-joinerType">
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="form-row node-row-trigger" id="trigger-row">
 | 
			
		||||
            <label style="width:auto;" data-i18n="join.send"></label>
 | 
			
		||||
            <ul>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <label style="width:280px;" for="node-input-count" data-i18n="join.afterCount"></label> <input id="node-input-count" data-i18n="[placeholder]join.count" type="text" style="width:75px;">
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="node-row-accumulate" style="list-style-type:none;">
 | 
			
		||||
                    <input type="checkbox" id="node-input-accumulate" style="display:inline-block; width:20px; margin-left:20px; vertical-align:top;">  <label style="width: auto" for="node-input-accumulate" data-i18n="join.subsequent"></label>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <label style="width:280px;" for="node-input-timeout" data-i18n="join.afterTimeout"></label> <input id="node-input-timeout" data-i18n="[placeholder]join.seconds" type="text" style="width:75px;">
 | 
			
		||||
                </li>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <label style="width:auto; padding-top:6px;" data-i18n="[html]join.complete"></label>
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="node-row-reduce">
 | 
			
		||||
        <div class="form-row">
 | 
			
		||||
            <label for="node-input-reduceExp" data-i18n="join.reduce.exp" style="margin-left:10px;"></label>
 | 
			
		||||
            <input type="text" id="node-input-reduceExp" data-i18n="[placeholder]join.reduce.exp-value" style="width:65%">
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="form-row">
 | 
			
		||||
            <label for="node-input-reduceInit" data-i18n="join.reduce.init" style="margin-left:10px;"></label>
 | 
			
		||||
            <input type="text" id="node-input-reduceInit" data-i18n="[placeholder]join.reduce.init" style="width:65%">
 | 
			
		||||
            <input type="hidden" id="node-input-reduceInitType">
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="form-row">
 | 
			
		||||
            <label for="node-input-reduceFixup" data-i18n="join.reduce.fixup" style="margin-left:10px;"></label>
 | 
			
		||||
            <input type="text" id="node-input-reduceFixup" data-i18n="[placeholder]join.reduce.exp-value" style="width:65%">
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="form-row">
 | 
			
		||||
            <label> </label>
 | 
			
		||||
            <input type="checkbox" id="node-input-reduceRight" style="display:inline-block; width:auto; vertical-align:top; margin-left:10px;">
 | 
			
		||||
            <label for="node-input-reduceRight" style="width:70%;" data-i18n="join.reduce.right" style="margin-left:10px;"/>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-tips form-tips-auto hide" data-i18n="[html]join.tip"></div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('join',{
 | 
			
		||||
        category: 'sequence',
 | 
			
		||||
        color:"#E2D96E",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            mode: {value:"auto"},
 | 
			
		||||
            build: { value:"object"},
 | 
			
		||||
            property: { value:"payload", validate:RED.validators.typedInput("propertyType")},
 | 
			
		||||
            propertyType: { value:"msg"},
 | 
			
		||||
            key: {value:"topic"},
 | 
			
		||||
            joiner: { value:"\\n"},
 | 
			
		||||
            joinerType: { value:"str"},
 | 
			
		||||
            accumulate: { value:"false" },
 | 
			
		||||
            timeout: {value:""},
 | 
			
		||||
            count: {value:""},
 | 
			
		||||
            reduceRight: {value:false},
 | 
			
		||||
            reduceExp: {value:undefined},
 | 
			
		||||
            reduceInit: {value:undefined},
 | 
			
		||||
            reduceInitType: {value:undefined},
 | 
			
		||||
            reduceFixup: {value:undefined}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "join.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||this._("join.join");
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var node = this;
 | 
			
		||||
 | 
			
		||||
            $("#node-input-mode").on("change", function(e) {
 | 
			
		||||
                var val = $(this).val();
 | 
			
		||||
                $(".node-row-custom").toggle(val==='custom');
 | 
			
		||||
                $(".node-row-reduce").toggle(val==='reduce');
 | 
			
		||||
                $(".form-tips-auto").toggle((val==='auto') || (val==='reduce'));
 | 
			
		||||
                if (val === "auto") {
 | 
			
		||||
                    $("#node-input-accumulate").attr('checked', false);
 | 
			
		||||
                }
 | 
			
		||||
                else if (val === "custom") {
 | 
			
		||||
                    $("#node-input-build").change();
 | 
			
		||||
                }
 | 
			
		||||
                else if (val === "reduce") {
 | 
			
		||||
                    var jsonata_or_empty = {
 | 
			
		||||
                        value: "jsonata",
 | 
			
		||||
                        label: "expression",
 | 
			
		||||
                        icon: "red/images/typedInput/expr.png",
 | 
			
		||||
                        validate: function(v) {
 | 
			
		||||
                            try{
 | 
			
		||||
                                if(v !== "") {
 | 
			
		||||
                                    jsonata(v);
 | 
			
		||||
                                }
 | 
			
		||||
                                return true;
 | 
			
		||||
                            }
 | 
			
		||||
                            catch(e){
 | 
			
		||||
                                return false;
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        expand:function() {
 | 
			
		||||
                            var that = this;
 | 
			
		||||
                            RED.editor.editExpression({
 | 
			
		||||
                                value: this.value().replace(/\t/g,"\n"),
 | 
			
		||||
                                complete: function(v) {
 | 
			
		||||
                                    that.value(v.replace(/\n/g,"\t"));
 | 
			
		||||
                                }
 | 
			
		||||
                            })
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                    $("#node-input-reduceExp").typedInput({types:[jsonata_or_empty]});
 | 
			
		||||
                    $("#node-input-reduceInit").typedInput({
 | 
			
		||||
                        default: 'num',
 | 
			
		||||
                        types:['flow','global','str','num','bool','json','bin','date','jsonata','env'],
 | 
			
		||||
                        typeField: $("#node-input-reduceInitType")
 | 
			
		||||
                    });
 | 
			
		||||
                    $("#node-input-reduceFixup").typedInput({types:[jsonata_or_empty]});
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $("#node-input-build").on("change", function(e) {
 | 
			
		||||
                var val = $(this).val();
 | 
			
		||||
                $(".node-row-key").toggle(val==='object');
 | 
			
		||||
                $(".node-row-accumulate").toggle(val==='object' || val==='merged');
 | 
			
		||||
                $(".node-row-joiner").toggle(val==='string' || val==='buffer');
 | 
			
		||||
                $(".node-row-trigger").toggle(val!=='auto');
 | 
			
		||||
                if (val === 'string' || val==='buffer') {
 | 
			
		||||
                    $("#node-input-property").typedInput('types',['msg']);
 | 
			
		||||
                    $("#node-input-joiner").typedInput("show");
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#node-input-property").typedInput('types', ['msg', {
 | 
			
		||||
                        value: "full",
 | 
			
		||||
                        label: RED._("node-red:join.completeMessage"),
 | 
			
		||||
                        hasValue: false
 | 
			
		||||
                    }]);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $("#node-input-joiner").typedInput({
 | 
			
		||||
                default: 'str',
 | 
			
		||||
                typeField: $("#node-input-joinerType"),
 | 
			
		||||
                types:['str', 'bin']
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $("#node-input-property").typedInput({
 | 
			
		||||
                typeField: $("#node-input-propertyType"),
 | 
			
		||||
                types: ['msg', {
 | 
			
		||||
                    value: "full",
 | 
			
		||||
                    label: RED._("node-red:join.completeMessage"),
 | 
			
		||||
                    hasValue: false
 | 
			
		||||
                }]
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $("#node-input-key").typedInput({
 | 
			
		||||
                types:['msg']
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $("#node-input-build").change();
 | 
			
		||||
            $("#node-input-mode").change();
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            var build = $("#node-input-build").val();
 | 
			
		||||
            if (build !== 'object' && build !== 'merged') {
 | 
			
		||||
                $("#node-input-accumulate").prop("checked",false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,785 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    function sendArray(node,msg,array,send) {
 | 
			
		||||
        for (var i = 0; i < array.length-1; i++) {
 | 
			
		||||
            msg.payload = array[i];
 | 
			
		||||
            msg.parts.index = node.c++;
 | 
			
		||||
            if (node.stream !== true) { msg.parts.count = array.length; }
 | 
			
		||||
            send(RED.util.cloneMessage(msg));
 | 
			
		||||
        }
 | 
			
		||||
        if (node.stream !== true) {
 | 
			
		||||
            msg.payload = array[i];
 | 
			
		||||
            msg.parts.index = node.c++;
 | 
			
		||||
            msg.parts.count = array.length;
 | 
			
		||||
            send(RED.util.cloneMessage(msg));
 | 
			
		||||
            node.c = 0;
 | 
			
		||||
        }
 | 
			
		||||
        else { node.remainder = array[i]; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function SplitNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        var node = this;
 | 
			
		||||
        node.stream = n.stream;
 | 
			
		||||
        node.spltType = n.spltType || "str";
 | 
			
		||||
        node.addname = n.addname || "";
 | 
			
		||||
        try {
 | 
			
		||||
            if (node.spltType === "str") {
 | 
			
		||||
                this.splt = (n.splt || "\\n").replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t").replace(/\\e/g,"\e").replace(/\\f/g,"\f").replace(/\\0/g,"\0");
 | 
			
		||||
            } else if (node.spltType === "bin") {
 | 
			
		||||
                var spltArray = JSON.parse(n.splt);
 | 
			
		||||
                if (Array.isArray(spltArray)) {
 | 
			
		||||
                    this.splt = Buffer.from(spltArray);
 | 
			
		||||
                } else {
 | 
			
		||||
                    throw new Error("not an array");
 | 
			
		||||
                }
 | 
			
		||||
                this.spltBuffer = spltArray;
 | 
			
		||||
            } else if (node.spltType === "len") {
 | 
			
		||||
                this.splt = parseInt(n.splt);
 | 
			
		||||
                if (isNaN(this.splt) || this.splt < 1) {
 | 
			
		||||
                    throw new Error("invalid split length: "+n.splt);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            this.arraySplt = (n.arraySplt === undefined)?1:parseInt(n.arraySplt);
 | 
			
		||||
            if (isNaN(this.arraySplt) || this.arraySplt < 1) {
 | 
			
		||||
                throw new Error("invalid array split length: "+n.arraySplt);
 | 
			
		||||
            }
 | 
			
		||||
        } catch(err) {
 | 
			
		||||
            this.error("Invalid split property: "+err.toString());
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        node.c = 0;
 | 
			
		||||
        node.buffer = Buffer.from([]);
 | 
			
		||||
        node.pendingDones = [];
 | 
			
		||||
        this.on("input", function(msg, send, done) {
 | 
			
		||||
            if (msg.hasOwnProperty("payload")) {
 | 
			
		||||
                if (msg.hasOwnProperty("parts")) { msg.parts = { parts:msg.parts }; } // push existing parts to a stack
 | 
			
		||||
                else { msg.parts = {}; }
 | 
			
		||||
                msg.parts.id = RED.util.generateId();  // generate a random id
 | 
			
		||||
                delete msg._msgid;
 | 
			
		||||
                if (typeof msg.payload === "string") { // Split String into array
 | 
			
		||||
                    msg.payload = (node.remainder || "") + msg.payload;
 | 
			
		||||
                    msg.parts.type = "string";
 | 
			
		||||
                    if (node.spltType === "len") {
 | 
			
		||||
                        msg.parts.ch = "";
 | 
			
		||||
                        msg.parts.len = node.splt;
 | 
			
		||||
                        var count = msg.payload.length/node.splt;
 | 
			
		||||
                        if (Math.floor(count) !== count) {
 | 
			
		||||
                            count = Math.ceil(count);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (node.stream !== true) {
 | 
			
		||||
                            msg.parts.count = count;
 | 
			
		||||
                            node.c = 0;
 | 
			
		||||
                        }
 | 
			
		||||
                        var pos = 0;
 | 
			
		||||
                        var data = msg.payload;
 | 
			
		||||
                        for (var i=0; i<count-1; i++) {
 | 
			
		||||
                            msg.payload = data.substring(pos,pos+node.splt);
 | 
			
		||||
                            msg.parts.index = node.c++;
 | 
			
		||||
                            pos += node.splt;
 | 
			
		||||
                            send(RED.util.cloneMessage(msg));
 | 
			
		||||
                        }
 | 
			
		||||
                        if (count > 1) {
 | 
			
		||||
                            node.pendingDones.forEach(d => d());
 | 
			
		||||
                            node.pendingDones = [];
 | 
			
		||||
                        }
 | 
			
		||||
                        node.remainder = data.substring(pos);
 | 
			
		||||
                        if ((node.stream !== true) || (node.remainder.length === node.splt)) {
 | 
			
		||||
                            msg.payload = node.remainder;
 | 
			
		||||
                            msg.parts.index = node.c++;
 | 
			
		||||
                            send(RED.util.cloneMessage(msg));
 | 
			
		||||
                            node.pendingDones.forEach(d => d());
 | 
			
		||||
                            node.pendingDones = [];
 | 
			
		||||
                            done();
 | 
			
		||||
                            node.remainder = "";
 | 
			
		||||
                        } else {
 | 
			
		||||
                            node.pendingDones.push(done);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        var a = [];
 | 
			
		||||
                        if (node.spltType === "bin") {
 | 
			
		||||
                            if (!node.spltBufferString) {
 | 
			
		||||
                                node.spltBufferString = node.splt.toString();
 | 
			
		||||
                            }
 | 
			
		||||
                            a = msg.payload.split(node.spltBufferString);
 | 
			
		||||
                            msg.parts.ch = node.spltBuffer; // pass the split char to other end for rejoin
 | 
			
		||||
                        } else if (node.spltType === "str") {
 | 
			
		||||
                            a = msg.payload.split(node.splt);
 | 
			
		||||
                            msg.parts.ch = node.splt; // pass the split char to other end for rejoin
 | 
			
		||||
                        }
 | 
			
		||||
                        sendArray(node,msg,a,send);
 | 
			
		||||
                        done();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else if (Array.isArray(msg.payload)) { // then split array into messages
 | 
			
		||||
                    msg.parts.type = "array";
 | 
			
		||||
                    var count = msg.payload.length/node.arraySplt;
 | 
			
		||||
                    if (Math.floor(count) !== count) {
 | 
			
		||||
                        count = Math.ceil(count);
 | 
			
		||||
                    }
 | 
			
		||||
                    msg.parts.count = count;
 | 
			
		||||
                    var pos = 0;
 | 
			
		||||
                    var data = msg.payload;
 | 
			
		||||
                    msg.parts.len = node.arraySplt;
 | 
			
		||||
                    for (var i=0; i<count; i++) {
 | 
			
		||||
                        msg.payload = data.slice(pos,pos+node.arraySplt);
 | 
			
		||||
                        if (node.arraySplt === 1) {
 | 
			
		||||
                            msg.payload = msg.payload[0];
 | 
			
		||||
                        }
 | 
			
		||||
                        msg.parts.index = i;
 | 
			
		||||
                        pos += node.arraySplt;
 | 
			
		||||
                        send(RED.util.cloneMessage(msg));
 | 
			
		||||
                    }
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
                else if ((typeof msg.payload === "object") && !Buffer.isBuffer(msg.payload)) {
 | 
			
		||||
                    var j = 0;
 | 
			
		||||
                    var l = Object.keys(msg.payload).length;
 | 
			
		||||
                    var pay = msg.payload;
 | 
			
		||||
                    msg.parts.type = "object";
 | 
			
		||||
                    for (var p in pay) {
 | 
			
		||||
                        if (pay.hasOwnProperty(p)) {
 | 
			
		||||
                            msg.payload = pay[p];
 | 
			
		||||
                            if (node.addname !== "") {
 | 
			
		||||
                                msg[node.addname] = p;
 | 
			
		||||
                            }
 | 
			
		||||
                            msg.parts.key = p;
 | 
			
		||||
                            msg.parts.index = j;
 | 
			
		||||
                            msg.parts.count = l;
 | 
			
		||||
                            send(RED.util.cloneMessage(msg));
 | 
			
		||||
                            j += 1;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
                else if (Buffer.isBuffer(msg.payload)) {
 | 
			
		||||
                    var len = node.buffer.length + msg.payload.length;
 | 
			
		||||
                    var buff = Buffer.concat([node.buffer, msg.payload], len);
 | 
			
		||||
                    msg.parts.type = "buffer";
 | 
			
		||||
                    if (node.spltType === "len") {
 | 
			
		||||
                        var count = buff.length/node.splt;
 | 
			
		||||
                        if (Math.floor(count) !== count) {
 | 
			
		||||
                            count = Math.ceil(count);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (node.stream !== true) {
 | 
			
		||||
                            msg.parts.count = count;
 | 
			
		||||
                            node.c = 0;
 | 
			
		||||
                        }
 | 
			
		||||
                        var pos = 0;
 | 
			
		||||
                        msg.parts.len = node.splt;
 | 
			
		||||
                        for (var i=0; i<count-1; i++) {
 | 
			
		||||
                            msg.payload = buff.slice(pos,pos+node.splt);
 | 
			
		||||
                            msg.parts.index = node.c++;
 | 
			
		||||
                            pos += node.splt;
 | 
			
		||||
                            send(RED.util.cloneMessage(msg));
 | 
			
		||||
                        }
 | 
			
		||||
                        if (count > 1) {
 | 
			
		||||
                            node.pendingDones.forEach(d => d());
 | 
			
		||||
                            node.pendingDones = [];
 | 
			
		||||
                        }
 | 
			
		||||
                        node.buffer = buff.slice(pos);
 | 
			
		||||
                        if ((node.stream !== true) || (node.buffer.length === node.splt)) {
 | 
			
		||||
                            msg.payload = node.buffer;
 | 
			
		||||
                            msg.parts.index = node.c++;
 | 
			
		||||
                            send(RED.util.cloneMessage(msg));
 | 
			
		||||
                            node.pendingDones.forEach(d => d());
 | 
			
		||||
                            node.pendingDones = [];
 | 
			
		||||
                            done();
 | 
			
		||||
                            node.buffer = Buffer.from([]);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            node.pendingDones.push(done);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        var count = 0;
 | 
			
		||||
                        if (node.spltType === "bin") {
 | 
			
		||||
                            msg.parts.ch = node.spltBuffer;
 | 
			
		||||
                        } else if (node.spltType === "str") {
 | 
			
		||||
                            msg.parts.ch = node.splt;
 | 
			
		||||
                        }
 | 
			
		||||
                        var pos = buff.indexOf(node.splt);
 | 
			
		||||
                        var end;
 | 
			
		||||
                        while (pos > -1) {
 | 
			
		||||
                            count++;
 | 
			
		||||
                            end = pos+node.splt.length;
 | 
			
		||||
                            pos = buff.indexOf(node.splt,end);
 | 
			
		||||
                        }
 | 
			
		||||
                        count++;
 | 
			
		||||
                        if (node.stream !== true) {
 | 
			
		||||
                            msg.parts.count = count;
 | 
			
		||||
                            node.c = 0;
 | 
			
		||||
                        }
 | 
			
		||||
                        var i = 0, p = 0;
 | 
			
		||||
                        pos = buff.indexOf(node.splt);
 | 
			
		||||
                        while (pos > -1) {
 | 
			
		||||
                            msg.payload = buff.slice(p,pos);
 | 
			
		||||
                            msg.parts.index = node.c++;
 | 
			
		||||
                            send(RED.util.cloneMessage(msg));
 | 
			
		||||
                            i++;
 | 
			
		||||
                            p = pos+node.splt.length;
 | 
			
		||||
                            pos = buff.indexOf(node.splt,p);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (count > 1) {
 | 
			
		||||
                            node.pendingDones.forEach(d => d());
 | 
			
		||||
                            node.pendingDones = [];
 | 
			
		||||
                        }
 | 
			
		||||
                        if ((node.stream !== true) && (p < buff.length)) {
 | 
			
		||||
                            msg.payload = buff.slice(p,buff.length);
 | 
			
		||||
                            msg.parts.index = node.c++;
 | 
			
		||||
                            msg.parts.count = node.c++;
 | 
			
		||||
                            send(RED.util.cloneMessage(msg));
 | 
			
		||||
                            node.pendingDones.forEach(d => d());
 | 
			
		||||
                            node.pendingDones = [];
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            node.buffer = buff.slice(p,buff.length);
 | 
			
		||||
                            node.pendingDones.push(done);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (node.buffer.length == 0) {
 | 
			
		||||
                            done();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else { // otherwise drop the message.
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("split",SplitNode);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    var _maxKeptMsgsCount;
 | 
			
		||||
 | 
			
		||||
    function maxKeptMsgsCount(node) {
 | 
			
		||||
        if (_maxKeptMsgsCount === undefined) {
 | 
			
		||||
            var name = "nodeMessageBufferMaxLength";
 | 
			
		||||
            if (RED.settings.hasOwnProperty(name)) {
 | 
			
		||||
                _maxKeptMsgsCount = RED.settings[name];
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                _maxKeptMsgsCount = 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return _maxKeptMsgsCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function applyReduce(exp, accum, msg, index, count, done) {
 | 
			
		||||
        exp.assign("I", index);
 | 
			
		||||
        exp.assign("N", count);
 | 
			
		||||
        exp.assign("A", accum);
 | 
			
		||||
        RED.util.evaluateJSONataExpression(exp, msg, done);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function exp_or_undefined(exp) {
 | 
			
		||||
        if((exp === "") ||
 | 
			
		||||
           (exp === null)) {
 | 
			
		||||
            return undefined;
 | 
			
		||||
        }
 | 
			
		||||
        return exp
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function reduceMessageGroup(node,msgInfos,exp,fixup,count,accumulator,done) {
 | 
			
		||||
        var msgInfo = msgInfos.shift();
 | 
			
		||||
        exp.assign("I", msgInfo.msg.parts.index);
 | 
			
		||||
        exp.assign("N", count);
 | 
			
		||||
        exp.assign("A", accumulator);
 | 
			
		||||
        RED.util.evaluateJSONataExpression(exp, msgInfo.msg, (err,result) => {
 | 
			
		||||
            if (err) {
 | 
			
		||||
                return done(err);
 | 
			
		||||
            }
 | 
			
		||||
            if (msgInfos.length === 0) {
 | 
			
		||||
                if (fixup) {
 | 
			
		||||
                    fixup.assign("N", count);
 | 
			
		||||
                    fixup.assign("A", result);
 | 
			
		||||
                    RED.util.evaluateJSONataExpression(fixup, {}, (err, result) => {
 | 
			
		||||
                        if (err) {
 | 
			
		||||
                            return done(err);
 | 
			
		||||
                        }
 | 
			
		||||
                        msgInfo.send({payload: result});
 | 
			
		||||
                        done();
 | 
			
		||||
                    });
 | 
			
		||||
                } else {
 | 
			
		||||
                    msgInfo.send({payload: result});
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                reduceMessageGroup(node,msgInfos,exp,fixup,count,result,done);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    function reduceAndSendGroup(node, group, done) {
 | 
			
		||||
        var is_right = node.reduce_right;
 | 
			
		||||
        var flag = is_right ? -1 : 1;
 | 
			
		||||
        var msgInfos = group.msgs;
 | 
			
		||||
        const preservedMsgInfos = [...msgInfos];
 | 
			
		||||
        try {
 | 
			
		||||
            RED.util.evaluateNodeProperty(node.exp_init, node.exp_init_type, node, {}, (err,accum) => {
 | 
			
		||||
                var reduceExpression = node.reduceExpression;
 | 
			
		||||
                var fixupExpression = node.fixupExpression;
 | 
			
		||||
                var count = group.count;
 | 
			
		||||
                msgInfos.sort(function(x,y) {
 | 
			
		||||
                    var ix = x.msg.parts.index;
 | 
			
		||||
                    var iy = y.msg.parts.index;
 | 
			
		||||
                    if (ix < iy) {return -flag;}
 | 
			
		||||
                    if (ix > iy) {return flag;}
 | 
			
		||||
                    return 0;
 | 
			
		||||
                });
 | 
			
		||||
                reduceMessageGroup(node, msgInfos,reduceExpression,fixupExpression,count,accum,(err,result) => {
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        preservedMsgInfos.pop(); // omit last message to emit error message
 | 
			
		||||
                        preservedMsgInfos.forEach(mInfo => mInfo.done());
 | 
			
		||||
                        done(err);
 | 
			
		||||
                        return;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        preservedMsgInfos.forEach(mInfo => mInfo.done());
 | 
			
		||||
                        done();
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            });
 | 
			
		||||
        } catch(err) {
 | 
			
		||||
            done(new Error(RED._("join.errors.invalid-expr",{error:err.message})));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function reduceMessage(node, msgInfo, done) {
 | 
			
		||||
        let msg = msgInfo.msg;
 | 
			
		||||
        if (msg.hasOwnProperty('parts')) {
 | 
			
		||||
            var parts = msg.parts;
 | 
			
		||||
            var pending = node.pending;
 | 
			
		||||
            var pending_count = node.pending_count;
 | 
			
		||||
            var gid = msg.parts.id;
 | 
			
		||||
            var count;
 | 
			
		||||
            if (!pending.hasOwnProperty(gid)) {
 | 
			
		||||
                if(parts.hasOwnProperty('count')) {
 | 
			
		||||
                    count = msg.parts.count;
 | 
			
		||||
                }
 | 
			
		||||
                pending[gid] = {
 | 
			
		||||
                    count: count,
 | 
			
		||||
                    msgs: []
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
            var group = pending[gid];
 | 
			
		||||
            var msgs = group.msgs;
 | 
			
		||||
            if (parts.hasOwnProperty('count') && (group.count === undefined)) {
 | 
			
		||||
                group.count = parts.count;
 | 
			
		||||
            }
 | 
			
		||||
            msgs.push(msgInfo);
 | 
			
		||||
            pending_count++;
 | 
			
		||||
            var completeProcess = function(err) {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    return done(err);
 | 
			
		||||
                }
 | 
			
		||||
                node.pending_count = pending_count;
 | 
			
		||||
                var max_msgs = maxKeptMsgsCount(node);
 | 
			
		||||
                if ((max_msgs > 0) && (pending_count > max_msgs)) {
 | 
			
		||||
                    Object.values(node.pending).forEach(group => {
 | 
			
		||||
                        group.msgs.forEach(mInfo => {
 | 
			
		||||
                            if (mInfo.msg._msgid !== msgInfo.msg._msgid) {
 | 
			
		||||
                                mInfo.done();
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                    node.pending = {};
 | 
			
		||||
                    node.pending_count = 0;
 | 
			
		||||
                    done(RED._("join.too-many"));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                return done();
 | 
			
		||||
            }
 | 
			
		||||
            if (msgs.length === group.count) {
 | 
			
		||||
                delete pending[gid];
 | 
			
		||||
                pending_count -= msgs.length;
 | 
			
		||||
                reduceAndSendGroup(node, group, completeProcess)
 | 
			
		||||
            } else {
 | 
			
		||||
                completeProcess();
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            msgInfo.send(msg);
 | 
			
		||||
            msgInfo.done();
 | 
			
		||||
            done();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function JoinNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.mode = n.mode||"auto";
 | 
			
		||||
        this.property = n.property||"payload";
 | 
			
		||||
        this.propertyType = n.propertyType||"msg";
 | 
			
		||||
        if (this.propertyType === 'full') {
 | 
			
		||||
            this.property = "payload";
 | 
			
		||||
        }
 | 
			
		||||
        this.key = n.key||"topic";
 | 
			
		||||
        this.timer = (this.mode === "auto") ? 0 : Number(n.timeout || 0)*1000;
 | 
			
		||||
        this.count = Number(n.count || 0);
 | 
			
		||||
        this.joiner = n.joiner||"";
 | 
			
		||||
        this.joinerType = n.joinerType||"str";
 | 
			
		||||
 | 
			
		||||
        this.reduce = (this.mode === "reduce");
 | 
			
		||||
        if (this.reduce) {
 | 
			
		||||
            this.exp_init = n.reduceInit;
 | 
			
		||||
            this.exp_init_type = n.reduceInitType;
 | 
			
		||||
            var exp_reduce = n.reduceExp;
 | 
			
		||||
            var exp_fixup = exp_or_undefined(n.reduceFixup);
 | 
			
		||||
            this.reduce_right = n.reduceRight;
 | 
			
		||||
            try {
 | 
			
		||||
                this.reduceExpression = RED.util.prepareJSONataExpression(exp_reduce, this);
 | 
			
		||||
                this.fixupExpression = (exp_fixup !== undefined) ? RED.util.prepareJSONataExpression(exp_fixup, this) : undefined;
 | 
			
		||||
            } catch(e) {
 | 
			
		||||
                this.error(RED._("join.errors.invalid-expr",{error:e.message}));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.joinerType === "str") {
 | 
			
		||||
            this.joiner = this.joiner.replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t").replace(/\\e/g,"\e").replace(/\\f/g,"\f").replace(/\\0/g,"\0");
 | 
			
		||||
        } else if (this.joinerType === "bin") {
 | 
			
		||||
            var joinArray = JSON.parse(n.joiner || "[]");
 | 
			
		||||
            if (Array.isArray(joinArray)) {
 | 
			
		||||
                this.joiner = Buffer.from(joinArray);
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new Error("not an array");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.build = n.build || "array";
 | 
			
		||||
        this.accumulate = n.accumulate || "false";
 | 
			
		||||
 | 
			
		||||
        this.output = n.output || "stream";
 | 
			
		||||
        this.pending = {};
 | 
			
		||||
        this.pending_count = 0;
 | 
			
		||||
 | 
			
		||||
        //this.topic = n.topic;
 | 
			
		||||
        var node = this;
 | 
			
		||||
        var inflight = {};
 | 
			
		||||
 | 
			
		||||
        var completeSend = function(partId) {
 | 
			
		||||
            var group = inflight[partId];
 | 
			
		||||
            if (group.timeout) { clearTimeout(group.timeout); }
 | 
			
		||||
            if ((node.accumulate !== true) || group.msg.hasOwnProperty("complete")) { delete inflight[partId]; }
 | 
			
		||||
            if (group.type === 'array' && group.arrayLen > 1) {
 | 
			
		||||
                var newArray = [];
 | 
			
		||||
                group.payload.forEach(function(n) {
 | 
			
		||||
                    newArray = newArray.concat(n);
 | 
			
		||||
                })
 | 
			
		||||
                group.payload = newArray;
 | 
			
		||||
            }
 | 
			
		||||
            else if (group.type === 'buffer') {
 | 
			
		||||
                var buffers = [];
 | 
			
		||||
                var bufferLen = 0;
 | 
			
		||||
                if (group.joinChar !== undefined) {
 | 
			
		||||
                    var joinBuffer = Buffer.from(group.joinChar);
 | 
			
		||||
                    for (var i=0; i<group.payload.length; i++) {
 | 
			
		||||
                        if (i > 0) {
 | 
			
		||||
                            buffers.push(joinBuffer);
 | 
			
		||||
                            bufferLen += joinBuffer.length;
 | 
			
		||||
                        }
 | 
			
		||||
                        if (!Buffer.isBuffer(group.payload[i])) {
 | 
			
		||||
                            group.payload[i] = Buffer.from(group.payload[i]);
 | 
			
		||||
                        }
 | 
			
		||||
                        buffers.push(group.payload[i]);
 | 
			
		||||
                        bufferLen += group.payload[i].length;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    bufferLen = group.bufferLen;
 | 
			
		||||
                    buffers = group.payload;
 | 
			
		||||
                }
 | 
			
		||||
                group.payload = Buffer.concat(buffers,bufferLen);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (group.type === 'string') {
 | 
			
		||||
                var groupJoinChar = group.joinChar;
 | 
			
		||||
                if (typeof group.joinChar !== 'string') {
 | 
			
		||||
                    groupJoinChar = group.joinChar.toString();
 | 
			
		||||
                }
 | 
			
		||||
                RED.util.setMessageProperty(group.msg,node.property,group.payload.join(groupJoinChar));
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                if (node.propertyType === 'full') {
 | 
			
		||||
                    group.msg = RED.util.cloneMessage(group.msg);
 | 
			
		||||
                }
 | 
			
		||||
                RED.util.setMessageProperty(group.msg,node.property,group.payload);
 | 
			
		||||
            }
 | 
			
		||||
            if (group.msg.hasOwnProperty('parts') && group.msg.parts.hasOwnProperty('parts')) {
 | 
			
		||||
                group.msg.parts = group.msg.parts.parts;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                delete group.msg.parts;
 | 
			
		||||
            }
 | 
			
		||||
            delete group.msg.complete;
 | 
			
		||||
            group.send(RED.util.cloneMessage(group.msg));
 | 
			
		||||
            group.dones.forEach(f => f());
 | 
			
		||||
            group.dones = [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var pendingMessages = [];
 | 
			
		||||
        var activeMessage = null;
 | 
			
		||||
        // In reduce mode, we must process messages fully in order otherwise
 | 
			
		||||
        // groups may overlap and cause unexpected results. The use of JSONata
 | 
			
		||||
        // means some async processing *might* occur if flow/global context is
 | 
			
		||||
        // accessed.
 | 
			
		||||
        var processReduceMessageQueue = function(msgInfo) {
 | 
			
		||||
            if (msgInfo) {
 | 
			
		||||
                // A new message has arrived - add it to the message queue
 | 
			
		||||
                pendingMessages.push(msgInfo);
 | 
			
		||||
                if (activeMessage !== null) {
 | 
			
		||||
                    // The node is currently processing a message, so do nothing
 | 
			
		||||
                    // more with this message
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (pendingMessages.length === 0) {
 | 
			
		||||
                // There are no more messages to process, clear the active flag
 | 
			
		||||
                // and return
 | 
			
		||||
                activeMessage = null;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // There are more messages to process. Get the next message and
 | 
			
		||||
            // start processing it. Recurse back in to check for any more
 | 
			
		||||
            var nextMsgInfo = pendingMessages.shift();
 | 
			
		||||
            activeMessage = true;
 | 
			
		||||
            reduceMessage(node, nextMsgInfo, err => {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    nextMsgInfo.done(err);//.error(err,nextMsg);
 | 
			
		||||
                }
 | 
			
		||||
                activeMessage = null;
 | 
			
		||||
                processReduceMessageQueue();
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.on("input", function(msg, send, done) {
 | 
			
		||||
            try {
 | 
			
		||||
                var property;
 | 
			
		||||
                var partId = "_";
 | 
			
		||||
                if (node.propertyType == "full") {
 | 
			
		||||
                    property = msg;
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    try {
 | 
			
		||||
                        property = RED.util.getMessageProperty(msg,node.property);
 | 
			
		||||
                    } catch(err) {
 | 
			
		||||
                        node.warn("Message property "+node.property+" not found");
 | 
			
		||||
                        done();
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (node.mode === 'auto' && (!msg.hasOwnProperty("parts")||!msg.parts.hasOwnProperty("id"))) {
 | 
			
		||||
                    // if a blank reset messag erest it all.
 | 
			
		||||
                    if (msg.hasOwnProperty("reset")) {
 | 
			
		||||
                        if (inflight && inflight.hasOwnProperty("partId") && inflight[partId].timeout) {
 | 
			
		||||
                            clearTimeout(inflight[partId].timeout);
 | 
			
		||||
                        }
 | 
			
		||||
                        inflight = {};
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        node.warn("Message missing msg.parts property - cannot join in 'auto' mode")
 | 
			
		||||
                    }
 | 
			
		||||
                    done();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var payloadType;
 | 
			
		||||
                var propertyKey;
 | 
			
		||||
                var targetCount;
 | 
			
		||||
                var joinChar;
 | 
			
		||||
                var arrayLen;
 | 
			
		||||
                var propertyIndex;
 | 
			
		||||
                if (node.mode === "auto") {
 | 
			
		||||
                    // Use msg.parts to identify all of the group information
 | 
			
		||||
                    partId = msg.parts.id;
 | 
			
		||||
                    payloadType = msg.parts.type;
 | 
			
		||||
                    targetCount = msg.parts.count;
 | 
			
		||||
                    joinChar = msg.parts.ch;
 | 
			
		||||
                    propertyKey = msg.parts.key;
 | 
			
		||||
                    arrayLen = msg.parts.len;
 | 
			
		||||
                    propertyIndex = msg.parts.index;
 | 
			
		||||
                }
 | 
			
		||||
                else if (node.mode === 'reduce') {
 | 
			
		||||
                    return processReduceMessageQueue({msg, send, done});
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    // Use the node configuration to identify all of the group information
 | 
			
		||||
                    payloadType = node.build;
 | 
			
		||||
                    targetCount = node.count;
 | 
			
		||||
                    joinChar = node.joiner;
 | 
			
		||||
                    if (n.count === "" && msg.hasOwnProperty('parts')) {
 | 
			
		||||
                        targetCount = msg.parts.count || 0;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (node.build === 'object') {
 | 
			
		||||
                        propertyKey = RED.util.getMessageProperty(msg,node.key);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (msg.hasOwnProperty("restartTimeout")) {
 | 
			
		||||
                    if (inflight[partId]) {
 | 
			
		||||
                        if (inflight[partId].timeout) {
 | 
			
		||||
                            clearTimeout(inflight[partId].timeout);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (node.timer > 0) {
 | 
			
		||||
                            inflight[partId].timeout = setTimeout(function() {
 | 
			
		||||
                                completeSend(partId)
 | 
			
		||||
                            }, node.timer)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (msg.hasOwnProperty("reset")) {
 | 
			
		||||
                    if (inflight[partId]) {
 | 
			
		||||
                        if (inflight[partId].timeout) {
 | 
			
		||||
                            clearTimeout(inflight[partId].timeout);
 | 
			
		||||
                        }
 | 
			
		||||
                        inflight[partId].dones.forEach(f => f());
 | 
			
		||||
                        delete inflight[partId]
 | 
			
		||||
                    }
 | 
			
		||||
                    done();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ((payloadType === 'object') && (propertyKey === null || propertyKey === undefined || propertyKey === "")) {
 | 
			
		||||
                    if (node.mode === "auto") {
 | 
			
		||||
                        node.warn("Message missing 'msg.parts.key' property - cannot add to object");
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        if (msg.hasOwnProperty('complete')) {
 | 
			
		||||
                            if (inflight[partId]) {
 | 
			
		||||
                                inflight[partId].msg.complete = msg.complete;
 | 
			
		||||
                                inflight[partId].send = send;
 | 
			
		||||
                                completeSend(partId);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            node.warn("Message missing key property 'msg."+node.key+"' - cannot add to object")
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    done();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!inflight.hasOwnProperty(partId)) {
 | 
			
		||||
                    if (payloadType === 'object' || payloadType === 'merged') {
 | 
			
		||||
                        inflight[partId] = {
 | 
			
		||||
                            currentCount:0,
 | 
			
		||||
                            payload:{},
 | 
			
		||||
                            targetCount:targetCount,
 | 
			
		||||
                            type:"object",
 | 
			
		||||
                            msg:RED.util.cloneMessage(msg),
 | 
			
		||||
                            send: send,
 | 
			
		||||
                            dones: []
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        inflight[partId] = {
 | 
			
		||||
                            currentCount:0,
 | 
			
		||||
                            payload:[],
 | 
			
		||||
                            targetCount:targetCount,
 | 
			
		||||
                            type:payloadType,
 | 
			
		||||
                            msg:RED.util.cloneMessage(msg),
 | 
			
		||||
                            send: send,
 | 
			
		||||
                            dones: []
 | 
			
		||||
                        };
 | 
			
		||||
                        if (payloadType === 'string') {
 | 
			
		||||
                            inflight[partId].joinChar = joinChar;
 | 
			
		||||
                        } else if (payloadType === 'array') {
 | 
			
		||||
                            inflight[partId].arrayLen = arrayLen;
 | 
			
		||||
                        } else if (payloadType === 'buffer') {
 | 
			
		||||
                            inflight[partId].bufferLen = 0;
 | 
			
		||||
                            inflight[partId].joinChar = joinChar;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (node.timer > 0) {
 | 
			
		||||
                        inflight[partId].timeout = setTimeout(function() {
 | 
			
		||||
                            completeSend(partId)
 | 
			
		||||
                        }, node.timer)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                inflight[partId].dones.push(done);
 | 
			
		||||
 | 
			
		||||
                var group = inflight[partId];
 | 
			
		||||
                if (payloadType === 'buffer') {
 | 
			
		||||
                    if (property !== undefined) {
 | 
			
		||||
                        if (Buffer.isBuffer(property) || (typeof property === "string") || Array.isArray(property)) {
 | 
			
		||||
                            inflight[partId].bufferLen += property.length;
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            done(RED._("join.errors.invalid-type",{error:(typeof property)}));
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (payloadType === 'object') {
 | 
			
		||||
                    group.payload[propertyKey] = property;
 | 
			
		||||
                    group.currentCount = Object.keys(group.payload).length;
 | 
			
		||||
                } else if (payloadType === 'merged') {
 | 
			
		||||
                    if (Array.isArray(property) || typeof property !== 'object') {
 | 
			
		||||
                        if (!msg.hasOwnProperty("complete")) {
 | 
			
		||||
                            node.warn("Cannot merge non-object types");
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        for (propertyKey in property) {
 | 
			
		||||
                            if (property.hasOwnProperty(propertyKey) && propertyKey !== '_msgid') {
 | 
			
		||||
                                group.payload[propertyKey] = property[propertyKey];
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        group.currentCount = Object.keys(group.payload).length;
 | 
			
		||||
                        //group.currentCount++;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (!isNaN(propertyIndex)) {
 | 
			
		||||
                        if (group.payload[propertyIndex] == undefined) { group.currentCount++; }
 | 
			
		||||
                        group.payload[propertyIndex] = property;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (property !== undefined) {
 | 
			
		||||
                            group.payload.push(property);
 | 
			
		||||
                            group.currentCount++;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                group.msg = Object.assign(group.msg, msg);
 | 
			
		||||
                group.send = send;
 | 
			
		||||
                var tcnt = group.targetCount;
 | 
			
		||||
                if (msg.hasOwnProperty("parts")) {
 | 
			
		||||
                    tcnt = group.targetCount || msg.parts.count;
 | 
			
		||||
                    group.targetCount = tcnt;
 | 
			
		||||
                }
 | 
			
		||||
                if ((tcnt > 0 && group.currentCount >= tcnt) || msg.hasOwnProperty('complete')) {
 | 
			
		||||
                    completeSend(partId);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch(err) {
 | 
			
		||||
                done(err);
 | 
			
		||||
                console.log(err.stack);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.on("close", function() {
 | 
			
		||||
            for (var i in inflight) {
 | 
			
		||||
                if (inflight.hasOwnProperty(i)) {
 | 
			
		||||
                    clearTimeout(inflight[i].timeout);
 | 
			
		||||
                    inflight[i].dones.forEach(d => d());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("join",JoinNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,119 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="sort">
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-target"><i class="fa fa-dot-circle-o"></i> <span data-i18n="sort.target"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-target" style="width:70%;">
 | 
			
		||||
        <input type="hidden" id="node-input-targetType">
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="node-row-sort-msg-key">
 | 
			
		||||
        <div class="form-row">
 | 
			
		||||
            <label for="node-input-msgKey"><i class="fa fa-filter"></i> <span data-i18n="sort.key"></span></label>
 | 
			
		||||
            <input type="text" id="node-input-msgKey" style="width:70%;">
 | 
			
		||||
            <input type="hidden" id="node-input-msgKeyType">
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="node-row-sort-seq-key">
 | 
			
		||||
        <div class="form-row">
 | 
			
		||||
            <label for="node-input-seqKey"><i class="fa fa-filter"></i> <span data-i18n="sort.key"></span></label>
 | 
			
		||||
            <input type="text" id="node-input-seqKey" style="width:70%;">
 | 
			
		||||
            <input type="hidden" id="node-input-seqKeyType">
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label><i class="fa fa-random"></i>  <span data-i18n="sort.order"></span></label>
 | 
			
		||||
        <select id="node-input-order" style="width:200px;">
 | 
			
		||||
            <option value="ascending" data-i18n="sort.ascending"></option>
 | 
			
		||||
            <option value="descending" data-i18n="sort.descending"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row" id="node-as_num">
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input type="checkbox" id="node-input-as_num" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-input-as_num" style="width: 70%;" data-i18n="sort.as-number"></label>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('sort',{
 | 
			
		||||
        category: 'sequence',
 | 
			
		||||
        color:"#E2D96E",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: { value:"" },
 | 
			
		||||
            order: { value:"ascending" },
 | 
			
		||||
            as_num: { value:false },
 | 
			
		||||
            target: { value:"payload" },
 | 
			
		||||
            targetType: { value:"msg" },
 | 
			
		||||
            msgKey: { value:"payload" },
 | 
			
		||||
            msgKeyType: { value:"elem" },
 | 
			
		||||
            seqKey: { value:"payload" },
 | 
			
		||||
            seqKeyType: { value:"msg" }
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "sort.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||this._("sort.sort");
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name ? "node_label_italic" : "";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var seq = {
 | 
			
		||||
                value: "seq",
 | 
			
		||||
                label: RED._("node-red:sort.seq"),
 | 
			
		||||
                hasValue: false
 | 
			
		||||
            };
 | 
			
		||||
            var elem = {
 | 
			
		||||
                value: "elem",
 | 
			
		||||
                label: RED._("node-red:sort.elem"),
 | 
			
		||||
                hasValue: false
 | 
			
		||||
            };
 | 
			
		||||
            $("#node-input-target").typedInput({
 | 
			
		||||
                default:'msg',
 | 
			
		||||
                typeField: $("#node-input-targetType"),
 | 
			
		||||
                types:['msg', seq]
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-msgKey").typedInput({
 | 
			
		||||
                default:'elem',
 | 
			
		||||
                typeField: $("#node-input-msgKeyType"),
 | 
			
		||||
                types:[elem, 'jsonata']
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-seqKey").typedInput({
 | 
			
		||||
                default:'msg',
 | 
			
		||||
                typeField: $("#node-input-seqKeyType"),
 | 
			
		||||
                types:['msg', 'jsonata']
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-target").on("change", function(e) {
 | 
			
		||||
                var val = $("#node-input-target").typedInput('type');
 | 
			
		||||
                $(".node-row-sort-msg-key").toggle(val === "msg");
 | 
			
		||||
                $(".node-row-sort-seq-key").toggle(val === "seq");
 | 
			
		||||
            });
 | 
			
		||||
            $("#node-input-target").change();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,266 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    var _max_kept_msgs_count;
 | 
			
		||||
 | 
			
		||||
    function max_kept_msgs_count(node) {
 | 
			
		||||
        if (_max_kept_msgs_count === undefined) {
 | 
			
		||||
            var name = "nodeMessageBufferMaxLength";
 | 
			
		||||
            if (RED.settings.hasOwnProperty(name)) {
 | 
			
		||||
                _max_kept_msgs_count = RED.settings[name];
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                _max_kept_msgs_count = 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return _max_kept_msgs_count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // function get_context_val(node, name, dval) {
 | 
			
		||||
    //     var context = node.context();
 | 
			
		||||
    //     var val = context.get(name);
 | 
			
		||||
    //     if (val === undefined) {
 | 
			
		||||
    //         context.set(name, dval);
 | 
			
		||||
    //         return dval;
 | 
			
		||||
    //     }
 | 
			
		||||
    //     return val;
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    function SortNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this, n);
 | 
			
		||||
        var node = this;
 | 
			
		||||
        var pending = {};//get_context_val(node, 'pending', {})
 | 
			
		||||
        var pending_count = 0;
 | 
			
		||||
        var pending_id = 0;
 | 
			
		||||
        var order = n.order || "ascending";
 | 
			
		||||
        var as_num = n.as_num || false;
 | 
			
		||||
        var target_prop = n.target || "payload";
 | 
			
		||||
        var target_is_prop = (n.targetType === 'msg');
 | 
			
		||||
        var key_is_exp = target_is_prop ? (n.msgKeyType === "jsonata") : (n.seqKeyType === "jsonata");
 | 
			
		||||
        var key_prop = n.seqKey || "payload";
 | 
			
		||||
        var key_exp = target_is_prop ? n.msgKey : n.seqKey;
 | 
			
		||||
 | 
			
		||||
        if (key_is_exp) {
 | 
			
		||||
            try {
 | 
			
		||||
                key_exp = RED.util.prepareJSONataExpression(key_exp, this);
 | 
			
		||||
            }
 | 
			
		||||
            catch (e) {
 | 
			
		||||
                node.error(RED._("sort.invalid-exp",{message:e.toString()}));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        var dir = (order === "ascending") ? 1 : -1;
 | 
			
		||||
        var conv = as_num ? function(x) { return Number(x); }
 | 
			
		||||
                          : function(x) { return x; };
 | 
			
		||||
 | 
			
		||||
        function generateComparisonFunction(key) {
 | 
			
		||||
            return function(x, y) {
 | 
			
		||||
                var xp = conv(key(x));
 | 
			
		||||
                var yp = conv(key(y));
 | 
			
		||||
                if (xp === yp) { return 0; }
 | 
			
		||||
                if (xp > yp) { return dir; }
 | 
			
		||||
                return -dir;
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function sortMessageGroup(group) {
 | 
			
		||||
            var promise;
 | 
			
		||||
            var msgInfos = group.msgInfos;
 | 
			
		||||
            if (key_is_exp) {
 | 
			
		||||
                var evaluatedDataPromises = msgInfos.map(mInfo => {
 | 
			
		||||
                    return new Promise((resolve,reject) => {
 | 
			
		||||
                        RED.util.evaluateJSONataExpression(key_exp, mInfo.msg, (err, result) => {
 | 
			
		||||
                            if (err) {
 | 
			
		||||
                                reject(RED._("sort.invalid-exp",{message:err.toString()}));
 | 
			
		||||
                            } else {
 | 
			
		||||
                                resolve({
 | 
			
		||||
                                    item: mInfo,
 | 
			
		||||
                                    sortValue: result
 | 
			
		||||
                                })
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    })
 | 
			
		||||
                });
 | 
			
		||||
                promise = Promise.all(evaluatedDataPromises).then(evaluatedElements => {
 | 
			
		||||
                    // Once all of the sort keys are evaluated, sort by them
 | 
			
		||||
                    var comp = generateComparisonFunction(elem=>elem.sortValue);
 | 
			
		||||
                    return evaluatedElements.sort(comp).map(elem=>elem.item);
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                var key = function(msg) {
 | 
			
		||||
                    return ;
 | 
			
		||||
                }
 | 
			
		||||
                var comp = generateComparisonFunction(mInfo => RED.util.getMessageProperty(mInfo.msg, key_prop));
 | 
			
		||||
                try {
 | 
			
		||||
                    msgInfos.sort(comp);
 | 
			
		||||
                }
 | 
			
		||||
                catch (e) {
 | 
			
		||||
                    return; // not send when error
 | 
			
		||||
                }
 | 
			
		||||
                promise = Promise.resolve(msgInfos);
 | 
			
		||||
            }
 | 
			
		||||
            return promise.then(msgInfos => {
 | 
			
		||||
                for (let i = 0; i < msgInfos.length; i++) {
 | 
			
		||||
                    const msg = msgInfos[i].msg;
 | 
			
		||||
                    msg.parts.index = i;
 | 
			
		||||
                    msgInfos[i].send(msg);
 | 
			
		||||
                    msgInfos[i].done();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function sortMessageProperty(msg) {
 | 
			
		||||
            var data = RED.util.getMessageProperty(msg, target_prop);
 | 
			
		||||
            if (Array.isArray(data)) {
 | 
			
		||||
                if (key_is_exp) {
 | 
			
		||||
                    // key is an expression. Evaluated the expression for each item
 | 
			
		||||
                    // to get its sort value. As this could be async, need to do
 | 
			
		||||
                    // it first.
 | 
			
		||||
                    var evaluatedDataPromises = data.map(elem => {
 | 
			
		||||
                        return new Promise((resolve,reject) => {
 | 
			
		||||
                            RED.util.evaluateJSONataExpression(key_exp, elem, (err, result) => {
 | 
			
		||||
                                if (err) {
 | 
			
		||||
                                    reject(RED._("sort.invalid-exp",{message:err.toString()}));
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    resolve({
 | 
			
		||||
                                        item: elem,
 | 
			
		||||
                                        sortValue: result
 | 
			
		||||
                                    })
 | 
			
		||||
                                }
 | 
			
		||||
                            });
 | 
			
		||||
                        })
 | 
			
		||||
                    })
 | 
			
		||||
                    return Promise.all(evaluatedDataPromises).then(evaluatedElements => {
 | 
			
		||||
                        // Once all of the sort keys are evaluated, sort by them
 | 
			
		||||
                        // and reconstruct the original message item with the newly
 | 
			
		||||
                        // sorted values.
 | 
			
		||||
                        var comp = generateComparisonFunction(elem=>elem.sortValue);
 | 
			
		||||
                        data = evaluatedElements.sort(comp).map(elem=>elem.item);
 | 
			
		||||
                        RED.util.setMessageProperty(msg, target_prop,data);
 | 
			
		||||
                        return true;
 | 
			
		||||
                    })
 | 
			
		||||
                } else {
 | 
			
		||||
                    var comp = generateComparisonFunction(elem=>elem);
 | 
			
		||||
                    try {
 | 
			
		||||
                        data.sort(comp);
 | 
			
		||||
                    } catch (e) {
 | 
			
		||||
                        return Promise.resolve(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    return Promise.resolve(true);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return Promise.resolve(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function removeOldestPending() {
 | 
			
		||||
            var oldest;
 | 
			
		||||
            var oldest_key;
 | 
			
		||||
            for(var key in pending) {
 | 
			
		||||
                if (pending.hasOwnProperty(key)) {
 | 
			
		||||
                    var item = pending[key];
 | 
			
		||||
                    if((oldest === undefined) ||
 | 
			
		||||
                       (oldest.seq_no > item.seq_no)) {
 | 
			
		||||
                        oldest = item;
 | 
			
		||||
                        oldest_key = key;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if(oldest !== undefined) {
 | 
			
		||||
                oldest.msgInfos[oldest.msgInfos.length - 1].done(RED._("sort.too-many"));
 | 
			
		||||
                for (let i = 0; i < oldest.msgInfos.length - 1; i++) {
 | 
			
		||||
                    oldest.msgInfos[i].done();
 | 
			
		||||
                }
 | 
			
		||||
                delete pending[oldest_key];
 | 
			
		||||
                return oldest.msgInfos.length;
 | 
			
		||||
            }
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function processMessage(msgInfo) {
 | 
			
		||||
            const msg = msgInfo.msg;
 | 
			
		||||
            if (target_is_prop) {
 | 
			
		||||
                sortMessageProperty(msg).then(send => {
 | 
			
		||||
                    if (send) {
 | 
			
		||||
                        msgInfo.send(msg);
 | 
			
		||||
                    }
 | 
			
		||||
                    msgInfo.done();
 | 
			
		||||
                }).catch(err => {
 | 
			
		||||
                    msgInfo.done(err);
 | 
			
		||||
                });
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var parts = msg.parts;
 | 
			
		||||
            if (!parts || !parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) {
 | 
			
		||||
                msgInfo.done();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var gid = parts.id;
 | 
			
		||||
            if (!pending.hasOwnProperty(gid)) {
 | 
			
		||||
                pending[gid] = {
 | 
			
		||||
                    count: undefined,
 | 
			
		||||
                    msgInfos: [],
 | 
			
		||||
                    seq_no: pending_id++
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
            var group = pending[gid];
 | 
			
		||||
            var msgInfos = group.msgInfos;
 | 
			
		||||
            msgInfos.push(msgInfo);
 | 
			
		||||
            if (parts.hasOwnProperty("count")) {
 | 
			
		||||
                group.count = parts.count;
 | 
			
		||||
            }
 | 
			
		||||
            pending_count++;
 | 
			
		||||
            if (group.count === msgInfos.length) {
 | 
			
		||||
                delete pending[gid]
 | 
			
		||||
                sortMessageGroup(group).catch(err => {
 | 
			
		||||
                    // throw an error for last message, and just call done() for remaining messages
 | 
			
		||||
                    msgInfos[msgInfos.length-1].done(err);
 | 
			
		||||
                    for (let i = 0; i < msgInfos.length - 1; i++) {
 | 
			
		||||
                        msgInfos[i].done()
 | 
			
		||||
                    };
 | 
			
		||||
                });
 | 
			
		||||
                pending_count -= msgInfos.length;
 | 
			
		||||
            } else {
 | 
			
		||||
                var max_msgs = max_kept_msgs_count(node);
 | 
			
		||||
                if ((max_msgs > 0) && (pending_count > max_msgs)) {
 | 
			
		||||
                    pending_count -= removeOldestPending();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.on("input", function(msg, send, done) {
 | 
			
		||||
            processMessage({msg, send, done});
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.on("close", function() {
 | 
			
		||||
            for(var key in pending) {
 | 
			
		||||
                if (pending.hasOwnProperty(key)) {
 | 
			
		||||
                    node.log(RED._("sort.clear"), pending[key].msgInfos[0]);
 | 
			
		||||
                    const group = pending[key];
 | 
			
		||||
                    group.msgInfos.forEach(mInfo => {
 | 
			
		||||
                        mInfo.done();
 | 
			
		||||
                    });
 | 
			
		||||
                    delete pending[key];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            pending_count = 0;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType("sort", SortNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,170 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="batch">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-mode"><span data-i18n="batch.mode.label"></span></label>
 | 
			
		||||
        <select type="text" id="node-input-mode" style="width: 300px;">
 | 
			
		||||
            <option value="count" data-i18n="batch.mode.num-msgs"></option>
 | 
			
		||||
            <option value="interval" data-i18n="batch.mode.interval"></option>
 | 
			
		||||
            <option value="concat" data-i18n="batch.mode.concat"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="node-row-msg-count">
 | 
			
		||||
        <div class="form-row node-row-count">
 | 
			
		||||
            <label style="margin-left: 10px; width: 175px;" for="node-input-count" data-i18n="batch.count.label"></label>
 | 
			
		||||
            <input type="text" id="node-input-count" style="width: 50px;">
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="node-row-msg-overlap">
 | 
			
		||||
        <div class="form-row node-row-overlap">
 | 
			
		||||
            <label style="margin-left: 10px; width: 175px;" for="node-input-overlap" data-i18n="batch.count.overlap"></label>
 | 
			
		||||
            <input type="text" id="node-input-overlap" style="width: 50px;">
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="node-row-msg-interval">
 | 
			
		||||
        <div class="form-row node-row-interval">
 | 
			
		||||
            <label style="margin-left: 10px; width: 175px;" for="node-input-interval"> <span data-i18n="batch.interval.label"></span></label>
 | 
			
		||||
            <input type="text" id="node-input-interval" style="width: 50px;">
 | 
			
		||||
            <span data-i18n="batch.interval.seconds"></span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="form-row">
 | 
			
		||||
            <input type="checkbox" id="node-input-allowEmptySequence" style="margin-left:20px; margin-right: 10px; vertical-align:top; width:auto;">
 | 
			
		||||
            <label for="node-input-allowEmptySequence" style="width:auto;" data-i18n="batch.interval.empty"></label>
 | 
			
		||||
	</div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="node-row-msg-concat">
 | 
			
		||||
        <div class="form-row">
 | 
			
		||||
            <label data-i18n="batch.concat.topics-label"></label>
 | 
			
		||||
            <div class="form-row node-input-topics-container-row">
 | 
			
		||||
                <ol id="node-input-topics-container"></ol>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType("batch",{
 | 
			
		||||
        category: "sequence",
 | 
			
		||||
        color:"#E2D96E",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            mode: {value:"count"},
 | 
			
		||||
            count: {value:10,validate:function(v) { return RED.validators.number(v) && (v >= 1); }},
 | 
			
		||||
            overlap: {value:0,validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
 | 
			
		||||
            interval: {value:10,validate:function(v) { return RED.validators.number(v) && (v >= 1); }},
 | 
			
		||||
            allowEmptySequence: {value:false},
 | 
			
		||||
            topics: {value:[{topic:""}]}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "batch.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||this._("batch.batch");;
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name ? "node_label_italic" : "";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var node = this;
 | 
			
		||||
            var topic_str = node._("batch.concat.topic");
 | 
			
		||||
 | 
			
		||||
            function resizeTopics(topic) {
 | 
			
		||||
                var newWidth = topic.width();
 | 
			
		||||
                topic.find('.red-ui-typedInput')
 | 
			
		||||
                     .typedInput("width",newWidth-15);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $("#node-input-topics-container")
 | 
			
		||||
                .css('min-height','150px').css('min-width','430px')
 | 
			
		||||
                .editableList({
 | 
			
		||||
                    addItem: function(container, i, opt) {
 | 
			
		||||
                        if (!opt.hasOwnProperty('topic')) {
 | 
			
		||||
                            opt.topic = "";
 | 
			
		||||
                        }
 | 
			
		||||
                        var row = $('<div/>').appendTo(container);
 | 
			
		||||
                        var valueField = $('<input/>',{
 | 
			
		||||
                            class:"node-input-topic-value",
 | 
			
		||||
                            type:"text",
 | 
			
		||||
                            style:"margin-left: 5px;"
 | 
			
		||||
                        }).appendTo(row)
 | 
			
		||||
                        .typedInput({default:'str', types:['str']});
 | 
			
		||||
                        valueField.typedInput('value', opt.topic);
 | 
			
		||||
                        valueField.typedInput('type', 'str');
 | 
			
		||||
                        valueField.attr('placeholder', topic_str);
 | 
			
		||||
                        resizeTopics(container);
 | 
			
		||||
                    },
 | 
			
		||||
                    resizeItem: resizeTopics,
 | 
			
		||||
                    sortable: true,
 | 
			
		||||
                    removable: true
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            $("#node-input-count").spinner({min:1});
 | 
			
		||||
            $("#node-input-overlap").spinner({min:0});
 | 
			
		||||
            $("#node-input-interval").spinner({min:1});
 | 
			
		||||
            $("#node-input-mode").on("change", function(e) {
 | 
			
		||||
                var val = $(this).val();
 | 
			
		||||
                $(".node-row-msg-count").toggle(val==="count");
 | 
			
		||||
                $(".node-row-msg-overlap").toggle(val==="count");
 | 
			
		||||
                $(".node-row-msg-interval").toggle(val==="interval");
 | 
			
		||||
                $(".node-row-msg-concat").toggle(val==="concat");
 | 
			
		||||
                if (val==="concat") {
 | 
			
		||||
                    var topics = node.topics;
 | 
			
		||||
                    var container = $("#node-input-topics-container");
 | 
			
		||||
                    container.editableList('empty');
 | 
			
		||||
                    for (var i = 0; i < topics.length; i++) {
 | 
			
		||||
                        var topic = topics[i];
 | 
			
		||||
                        container.editableList('addItem', topic);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        oneditsave: function() {
 | 
			
		||||
            var topics = $("#node-input-topics-container").editableList('items');
 | 
			
		||||
            var node = this;
 | 
			
		||||
            node.topics = [];
 | 
			
		||||
            topics.each(function(i) {
 | 
			
		||||
                var topicData = $(this).data('data');
 | 
			
		||||
                var topic = $(this);
 | 
			
		||||
                var vf = topic.find(".node-input-topic-value");
 | 
			
		||||
                var value = vf.typedInput('value');
 | 
			
		||||
                var type = vf.typedInput('type');
 | 
			
		||||
                var r = {topic:value};
 | 
			
		||||
                node.topics.push(r);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        oneditresize: function(size) {
 | 
			
		||||
            var rows = $("#dialog-form>div:not(.node-input-topics-container-row)");
 | 
			
		||||
            var height = size.height;
 | 
			
		||||
            for (var i = 0; i < rows.length; i++) {
 | 
			
		||||
                height -= $(rows[i]).outerHeight(true);
 | 
			
		||||
            }
 | 
			
		||||
            var editorRow = $("#dialog-form>div.node-input-topics-container-row");
 | 
			
		||||
            height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
 | 
			
		||||
            $("#node-input-topics-container").editableList('height',height);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,311 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    var _max_kept_msgs_count = undefined;
 | 
			
		||||
 | 
			
		||||
    function max_kept_msgs_count(node) {
 | 
			
		||||
        if (_max_kept_msgs_count === undefined) {
 | 
			
		||||
            var name = "nodeMessageBufferMaxLength";
 | 
			
		||||
            if (RED.settings.hasOwnProperty(name)) {
 | 
			
		||||
                _max_kept_msgs_count = RED.settings[name];
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                _max_kept_msgs_count = 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return _max_kept_msgs_count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function send_msgs(node, msgInfos, clone_msg) {
 | 
			
		||||
        var count = msgInfos.length;
 | 
			
		||||
        var msg_id = msgInfos[0].msg._msgid;
 | 
			
		||||
        for (var i = 0; i < count; i++) {
 | 
			
		||||
            var msg = clone_msg ? RED.util.cloneMessage(msgInfos[i].msg) : msgInfos[i].msg;
 | 
			
		||||
            if (!msg.hasOwnProperty("parts")) {
 | 
			
		||||
                msg.parts = {};
 | 
			
		||||
            }
 | 
			
		||||
            var parts = msg.parts;
 | 
			
		||||
            parts.id = msg_id;
 | 
			
		||||
            parts.index = i;
 | 
			
		||||
            parts.count = count;
 | 
			
		||||
            msgInfos[i].send(msg);
 | 
			
		||||
            //msgInfos[i].done();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function send_interval(node, allow_empty_seq) {
 | 
			
		||||
        let msgInfos = node.pending;
 | 
			
		||||
        if (msgInfos.length > 0) {
 | 
			
		||||
            send_msgs(node, msgInfos, false);
 | 
			
		||||
            msgInfos.forEach(e => e.done());
 | 
			
		||||
            node.pending = [];
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            if (allow_empty_seq) {
 | 
			
		||||
                let mid = RED.util.generateId();
 | 
			
		||||
                let msg = {
 | 
			
		||||
                    payload: null,
 | 
			
		||||
                    parts: {
 | 
			
		||||
                        id: mid,
 | 
			
		||||
                        index: 0,
 | 
			
		||||
                        count: 1
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
                node.send(msg);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function is_complete(pending, topic) {
 | 
			
		||||
        if (pending.hasOwnProperty(topic)) {
 | 
			
		||||
            var p_topic = pending[topic];
 | 
			
		||||
            var gids = p_topic.gids;
 | 
			
		||||
            if (gids.length > 0) {
 | 
			
		||||
                var gid = gids[0];
 | 
			
		||||
                var groups = p_topic.groups;
 | 
			
		||||
                var group = groups[gid];
 | 
			
		||||
                return (group.count === group.msgs.length);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function get_msgs_of_topic(pending, topic) {
 | 
			
		||||
        var p_topic = pending[topic];
 | 
			
		||||
        var groups = p_topic.groups;
 | 
			
		||||
        var gids = p_topic.gids;
 | 
			
		||||
        var gid = gids[0];
 | 
			
		||||
        var group = groups[gid];
 | 
			
		||||
        return group.msgs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function remove_topic(pending, topic) {
 | 
			
		||||
        var p_topic = pending[topic];
 | 
			
		||||
        var groups = p_topic.groups;
 | 
			
		||||
        var gids = p_topic.gids;
 | 
			
		||||
        var gid = gids.shift();
 | 
			
		||||
        delete groups[gid];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function try_concat(node, pending) {
 | 
			
		||||
        var topics = node.topics;
 | 
			
		||||
        for (var topic of topics) {
 | 
			
		||||
            if (!is_complete(pending, topic)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        var msgInfos = [];
 | 
			
		||||
        for (var topic of topics) {
 | 
			
		||||
            var t_msgInfos = get_msgs_of_topic(pending, topic);
 | 
			
		||||
            msgInfos = msgInfos.concat(t_msgInfos);
 | 
			
		||||
        }
 | 
			
		||||
        for (var topic of topics) {
 | 
			
		||||
            remove_topic(pending, topic);
 | 
			
		||||
        }
 | 
			
		||||
        send_msgs(node, msgInfos, true);
 | 
			
		||||
        msgInfos.forEach(e => e.done() );
 | 
			
		||||
        node.pending_count -= msgInfos.length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function add_to_topic_group(pending, topic, gid, msgInfo) {
 | 
			
		||||
        if (!pending.hasOwnProperty(topic)) {
 | 
			
		||||
            pending[topic] = { groups: {}, gids: [] };
 | 
			
		||||
        }
 | 
			
		||||
        var p_topic = pending[topic];
 | 
			
		||||
        var groups = p_topic.groups;
 | 
			
		||||
        var gids = p_topic.gids;
 | 
			
		||||
        if (!groups.hasOwnProperty(gid)) {
 | 
			
		||||
            groups[gid] = { msgs: [], count: undefined };
 | 
			
		||||
            gids.push(gid);
 | 
			
		||||
        }
 | 
			
		||||
        var group = groups[gid];
 | 
			
		||||
        group.msgs.push(msgInfo);
 | 
			
		||||
        if ((group.count === undefined) &&
 | 
			
		||||
            msgInfo.msg.parts.hasOwnProperty('count')) {
 | 
			
		||||
            group.count = msgInfo.msg.parts.count;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function concat_msg(node, msg, send, done) {
 | 
			
		||||
        var topic = msg.topic;
 | 
			
		||||
        if(node.topics.indexOf(topic) >= 0) {
 | 
			
		||||
            if (!msg.hasOwnProperty("parts") ||
 | 
			
		||||
                !msg.parts.hasOwnProperty("id") ||
 | 
			
		||||
                !msg.parts.hasOwnProperty("index") ||
 | 
			
		||||
                !msg.parts.hasOwnProperty("count")) {
 | 
			
		||||
                done(RED._("batch.no-parts"));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var gid = msg.parts.id;
 | 
			
		||||
            var pending = node.pending;
 | 
			
		||||
            add_to_topic_group(pending, topic, gid, {msg, send, done});
 | 
			
		||||
            node.pending_count++;
 | 
			
		||||
            var max_msgs = max_kept_msgs_count(node);
 | 
			
		||||
            if ((max_msgs > 0) && (node.pending_count > max_msgs)) {
 | 
			
		||||
                Object.values(node.pending).forEach(p_topic => {
 | 
			
		||||
                    Object.values(p_topic.groups).forEach(group => {
 | 
			
		||||
                        group.msgs.forEach(msgInfo => {
 | 
			
		||||
                            if (msgInfo.msg.id === msg.id) {
 | 
			
		||||
                                // the message that caused the overflow
 | 
			
		||||
                                msgInfo.done(RED._("batch.too-many"));
 | 
			
		||||
                            } else {
 | 
			
		||||
                                msgInfo.done();
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                    })
 | 
			
		||||
                });
 | 
			
		||||
                node.pending = {};
 | 
			
		||||
                node.pending_count = 0;
 | 
			
		||||
            }
 | 
			
		||||
            try_concat(node, pending);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function BatchNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        var node = this;
 | 
			
		||||
        var mode = n.mode || "count";
 | 
			
		||||
 | 
			
		||||
        node.pending_count = 0;
 | 
			
		||||
        if (mode === "count") {
 | 
			
		||||
            var count = Number(n.count || 1);
 | 
			
		||||
            var overlap = Number(n.overlap || 0);
 | 
			
		||||
            var is_overlap = (overlap > 0);
 | 
			
		||||
            if (count <= overlap) {
 | 
			
		||||
                node.error(RED._("batch.count.invalid"));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            node.pending = [];
 | 
			
		||||
            this.on("input", function(msg, send, done) {
 | 
			
		||||
                if (msg.hasOwnProperty("reset")) {
 | 
			
		||||
                    node.pending.forEach(e => e.done());
 | 
			
		||||
                    node.pending = [];
 | 
			
		||||
                    node.pending_count = 0;
 | 
			
		||||
                    done();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var queue = node.pending;
 | 
			
		||||
                queue.push({msg, send, done});
 | 
			
		||||
                node.pending_count++;
 | 
			
		||||
                if (queue.length === count) {
 | 
			
		||||
                    send_msgs(node, queue, is_overlap);
 | 
			
		||||
                    for (let i = 0; i < queue.length-overlap; i++) {
 | 
			
		||||
                        queue[i].done();
 | 
			
		||||
                    }
 | 
			
		||||
                    node.pending =
 | 
			
		||||
                        (overlap === 0) ? [] : queue.slice(-overlap);
 | 
			
		||||
                    node.pending_count = 0;
 | 
			
		||||
                }
 | 
			
		||||
                var max_msgs = max_kept_msgs_count(node);
 | 
			
		||||
                if ((max_msgs > 0) && (node.pending_count > max_msgs)) {
 | 
			
		||||
                    let lastMInfo = node.pending.pop();
 | 
			
		||||
                    lastMInfo.done(RED._("batch.too-many"));
 | 
			
		||||
                    node.pending.forEach(e => e.done());
 | 
			
		||||
                    node.pending = [];
 | 
			
		||||
                    node.pending_count = 0;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            this.on("close", function() {
 | 
			
		||||
                node.pending.forEach(e=> e.done());
 | 
			
		||||
                node.pending_count = 0;
 | 
			
		||||
                node.pending = [];
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        else if (mode === "interval") {
 | 
			
		||||
            var interval = Number(n.interval || "0") *1000;
 | 
			
		||||
            var allow_empty_seq = n.allowEmptySequence;
 | 
			
		||||
            node.pending = []
 | 
			
		||||
            function msgHandler() {
 | 
			
		||||
                send_interval(node, allow_empty_seq);
 | 
			
		||||
                node.pending_count = 0;
 | 
			
		||||
            }
 | 
			
		||||
            var timer = undefined;
 | 
			
		||||
            if (interval > 0) {
 | 
			
		||||
                timer = setInterval(msgHandler, interval);
 | 
			
		||||
            }
 | 
			
		||||
            this.on("input", function(msg, send, done) {
 | 
			
		||||
                if (msg.hasOwnProperty("reset")) {
 | 
			
		||||
                    if (timer !== undefined) {
 | 
			
		||||
                        clearInterval(timer);
 | 
			
		||||
                    }
 | 
			
		||||
                    node.pending.forEach(e => e.done());
 | 
			
		||||
                    node.pending = [];
 | 
			
		||||
                    node.pending_count = 0;
 | 
			
		||||
                    done();
 | 
			
		||||
                    if (interval > 0) {
 | 
			
		||||
                        timer = setInterval(msgHandler, interval);
 | 
			
		||||
                    }
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                node.pending.push({msg, send, done});
 | 
			
		||||
                node.pending_count++;
 | 
			
		||||
                var max_msgs = max_kept_msgs_count(node);
 | 
			
		||||
                if ((max_msgs > 0) && (node.pending_count > max_msgs)) {
 | 
			
		||||
                    let lastMInfo = node.pending.pop();
 | 
			
		||||
                    lastMInfo.done(RED._("batch.too-many"));
 | 
			
		||||
                    node.pending.forEach(e => e.done());
 | 
			
		||||
                    node.pending = [];
 | 
			
		||||
                    node.pending_count = 0;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            this.on("close", function() {
 | 
			
		||||
                if (timer !== undefined) {
 | 
			
		||||
                    clearInterval(timer);
 | 
			
		||||
                }
 | 
			
		||||
                node.pending.forEach(e => e.done());
 | 
			
		||||
                node.pending = [];
 | 
			
		||||
                node.pending_count = 0;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        else if(mode === "concat") {
 | 
			
		||||
            node.topics = (n.topics || []).map(function(x) {
 | 
			
		||||
                return x.topic;
 | 
			
		||||
            });
 | 
			
		||||
            node.pending = {};
 | 
			
		||||
            this.on("input", function(msg, send, done) {
 | 
			
		||||
                if (msg.hasOwnProperty("reset")) {
 | 
			
		||||
                    Object.values(node.pending).forEach(p_topic => {
 | 
			
		||||
                        Object.values(p_topic.groups).forEach(group => {
 | 
			
		||||
                            group.msgs.forEach(e => e.done());
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                    node.pending = {};
 | 
			
		||||
                    node.pending_count = 0;
 | 
			
		||||
                    done();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                concat_msg(node, msg, send, done);
 | 
			
		||||
            });
 | 
			
		||||
            this.on("close", function() {
 | 
			
		||||
                Object.values(node.pending).forEach(p_topic => {
 | 
			
		||||
                    Object.values(p_topic.groups).forEach(group => {
 | 
			
		||||
                        group.msgs.forEach(e => e.done());
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
                node.pending = {};
 | 
			
		||||
                node.pending_count = 0;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            node.error(RED._("batch.unexpected"));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType("batch", BatchNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,347 +0,0 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="file">
 | 
			
		||||
    <div class="form-row node-input-filename">
 | 
			
		||||
         <label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
 | 
			
		||||
         <input id="node-input-filename" type="text">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-overwriteFile"><i class="fa fa-random"></i> <span data-i18n="file.label.action"></span></label>
 | 
			
		||||
        <select type="text" id="node-input-overwriteFile" style="width: 250px;">
 | 
			
		||||
            <option value="false" data-i18n="file.action.append"></option>
 | 
			
		||||
            <option value="true" data-i18n="file.action.overwrite"></option>
 | 
			
		||||
            <option value="delete" data-i18n="file.action.delete"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row form-row-file-write-options">
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input type="checkbox" id="node-input-appendNewline" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-input-appendNewline" style="width: 70%;"><span data-i18n="file.label.addnewline"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row form-row-file-write-options">
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input type="checkbox" id="node-input-createDir" style="display: inline-block; width: auto; vertical-align: top;">
 | 
			
		||||
        <label for="node-input-createDir" style="width: 70%;"><span data-i18n="file.label.createdir"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row form-row-file-encoding">
 | 
			
		||||
        <label for="node-input-encoding"><i class="fa fa-flag"></i> <span data-i18n="file.label.encoding"></span></label>
 | 
			
		||||
        <select type="text" id="node-input-encoding" style="width: 250px;">
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-tips"><span data-i18n="file.tip"></span></div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="file in">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
         <label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
 | 
			
		||||
         <input id="node-input-filename" type="text" data-i18n="[placeholder]file.label.filename">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-format"><i class="fa fa-sign-out"></i> <span data-i18n="file.label.outputas"></span></label>
 | 
			
		||||
        <select id="node-input-format" style="width: 250px;">
 | 
			
		||||
            <option value="utf8" data-i18n="file.output.utf8"></option>
 | 
			
		||||
            <option value="lines" data-i18n="file.output.lines"></option>
 | 
			
		||||
            <option value="" data-i18n="file.output.buffer"></option>
 | 
			
		||||
            <option value="stream" data-i18n="file.output.stream"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" id="file-allprops">
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input type="checkbox" id="node-input-allProps" style="display:inline-block; width:auto; vertical-align:top;">
 | 
			
		||||
        <label for="node-input-allProps" style="width: 70%;"><span data-i18n="file.label.allProps"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row" id="encoding-spec">
 | 
			
		||||
        <label for="node-input-encoding"><i class="fa fa-flag"></i> <span data-i18n="file.label.encoding"></span></label>
 | 
			
		||||
        <select type="text" id="node-input-encoding" style="width:250px;">
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-tips"><span data-i18n="file.tip"></span></div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
(function(){
 | 
			
		||||
    var encodings = [
 | 
			
		||||
        [ "file.encoding.native",
 | 
			
		||||
          "utf8",
 | 
			
		||||
          "ucs2",
 | 
			
		||||
          "utf-16le",
 | 
			
		||||
          "ascii",
 | 
			
		||||
          "binary",
 | 
			
		||||
          "base64",
 | 
			
		||||
          "hex"
 | 
			
		||||
        ],
 | 
			
		||||
        [ "file.encoding.unicode",
 | 
			
		||||
          "utf-16be",
 | 
			
		||||
        ],
 | 
			
		||||
        [ "file.encoding.japanese",
 | 
			
		||||
          "Shift_JIS",
 | 
			
		||||
          "Windows-31j",
 | 
			
		||||
          "Windows932",
 | 
			
		||||
          "EUC-JP"
 | 
			
		||||
        ],
 | 
			
		||||
        [ "file.encoding.chinese",
 | 
			
		||||
          "GB2312",
 | 
			
		||||
          "GBK",
 | 
			
		||||
          "GB18030",
 | 
			
		||||
          "Windows936",
 | 
			
		||||
          "EUC-CN"
 | 
			
		||||
        ],
 | 
			
		||||
        [ "file.encoding.korean",
 | 
			
		||||
          "KS_C_5601",
 | 
			
		||||
          "Windows949",
 | 
			
		||||
          "EUC-KR"
 | 
			
		||||
        ],
 | 
			
		||||
        [ "file.encoding.taiwan",
 | 
			
		||||
          "Big5",
 | 
			
		||||
          "Big5-HKSCS",
 | 
			
		||||
          "Windows950"
 | 
			
		||||
        ],
 | 
			
		||||
        [ "file.encoding.windows",
 | 
			
		||||
          "cp874",
 | 
			
		||||
          "cp1250",
 | 
			
		||||
          "cp1251",
 | 
			
		||||
          "cp1252",
 | 
			
		||||
          "cp1253",
 | 
			
		||||
          "cp1254",
 | 
			
		||||
          "cp1255",
 | 
			
		||||
          "cp1256",
 | 
			
		||||
          "cp1257",
 | 
			
		||||
          "cp1258"
 | 
			
		||||
        ],
 | 
			
		||||
        [ "file.encoding.iso",
 | 
			
		||||
          "ISO-8859-1",
 | 
			
		||||
          "ISO-8859-2",
 | 
			
		||||
          "ISO-8859-3",
 | 
			
		||||
          "ISO-8859-4",
 | 
			
		||||
          "ISO-8859-5",
 | 
			
		||||
          "ISO-8859-6",
 | 
			
		||||
          "ISO-8859-7",
 | 
			
		||||
          "ISO-8859-8",
 | 
			
		||||
          "ISO-8859-9",
 | 
			
		||||
          "ISO-8859-10",
 | 
			
		||||
          "ISO-8859-11",
 | 
			
		||||
          "ISO-8859-12",
 | 
			
		||||
          "ISO-8859-13",
 | 
			
		||||
          "ISO-8859-14",
 | 
			
		||||
          "ISO-8859-15",
 | 
			
		||||
          "ISO-8859-16"
 | 
			
		||||
        ],
 | 
			
		||||
        [ "file.encoding.ibm",
 | 
			
		||||
          "cp437",
 | 
			
		||||
          "cp737",
 | 
			
		||||
          "cp775",
 | 
			
		||||
          "cp808",
 | 
			
		||||
          "cp850",
 | 
			
		||||
          "cp852",
 | 
			
		||||
          "cp855",
 | 
			
		||||
          "cp856",
 | 
			
		||||
          "cp857",
 | 
			
		||||
          "cp858",
 | 
			
		||||
          "cp860",
 | 
			
		||||
          "cp861",
 | 
			
		||||
          "cp866",
 | 
			
		||||
          "cp869",
 | 
			
		||||
          "cp922",
 | 
			
		||||
          "cp1046",
 | 
			
		||||
          "cp1124",
 | 
			
		||||
          "cp1125",
 | 
			
		||||
          "cp1129",
 | 
			
		||||
          "cp1133",
 | 
			
		||||
          "cp1161",
 | 
			
		||||
          "cp1162",
 | 
			
		||||
          "cp1163"
 | 
			
		||||
        ],
 | 
			
		||||
        [ "file.encoding.mac",
 | 
			
		||||
          "maccroatian",
 | 
			
		||||
          "maccyrillic",
 | 
			
		||||
          "macgreek",
 | 
			
		||||
          "maciceland",
 | 
			
		||||
          "macroman",
 | 
			
		||||
          "macromania",
 | 
			
		||||
          "macthai",
 | 
			
		||||
          "macturkish",
 | 
			
		||||
          "macukraine",
 | 
			
		||||
          "maccenteuro",
 | 
			
		||||
          "macintosh"
 | 
			
		||||
        ],
 | 
			
		||||
        [ "file.encoding.koi8",
 | 
			
		||||
          "koi8-r",
 | 
			
		||||
          "koi8-u",
 | 
			
		||||
          "koi8-ru",
 | 
			
		||||
          "koi8-t"
 | 
			
		||||
        ],
 | 
			
		||||
        [ "file.encoding.misc",
 | 
			
		||||
          "armscii8",
 | 
			
		||||
          "rk1048",
 | 
			
		||||
          "tcvn",
 | 
			
		||||
          "georgianacademy",
 | 
			
		||||
          "georgianps",
 | 
			
		||||
          "pt154",
 | 
			
		||||
          "viscii",
 | 
			
		||||
          "iso646cn",
 | 
			
		||||
          "iso646jp",
 | 
			
		||||
          "hproman8",
 | 
			
		||||
          "tis620"
 | 
			
		||||
        ]
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('file',{
 | 
			
		||||
        category: 'storage',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            filename: {value:""},
 | 
			
		||||
            appendNewline: {value:true},
 | 
			
		||||
            createDir: {value:false},
 | 
			
		||||
            overwriteFile: {value:"false"},
 | 
			
		||||
            encoding: {value:"none"}
 | 
			
		||||
        },
 | 
			
		||||
        color:"BurlyWood",
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "file-out.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            if (this.overwriteFile === "delete") {
 | 
			
		||||
                return this.name||this._("file.label.deletelabel",{file:this.filename});
 | 
			
		||||
            } else {
 | 
			
		||||
                return this.name||this.filename||this._("file.label.write");
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        paletteLabel: RED._("node-red:file.label.write"),
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var node = this;
 | 
			
		||||
            var encSel = $("#node-input-encoding");
 | 
			
		||||
            var label = node._("file.encoding.none");
 | 
			
		||||
            $("<option/>", {
 | 
			
		||||
                value: "none",
 | 
			
		||||
                label: label
 | 
			
		||||
            }).text(label).appendTo(encSel);
 | 
			
		||||
            $("<option/>", {
 | 
			
		||||
                value: "setbymsg",
 | 
			
		||||
                label: node._("file.encoding.setbymsg")
 | 
			
		||||
            }).text(label).appendTo(encSel);
 | 
			
		||||
            encodings.forEach(function(item) {
 | 
			
		||||
                if(Array.isArray(item)) {
 | 
			
		||||
                    var group = $("<optgroup/>", {
 | 
			
		||||
                        label: node._(item[0])
 | 
			
		||||
                    }).appendTo(encSel);
 | 
			
		||||
                    for (var i = 1; i < item.length; i++) {
 | 
			
		||||
                        var enc = item[i];
 | 
			
		||||
                        $("<option/>", {
 | 
			
		||||
                            value: enc,
 | 
			
		||||
                            label: enc
 | 
			
		||||
                        }).text(enc).appendTo(group);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    $("<option/>", {
 | 
			
		||||
                        value: item,
 | 
			
		||||
                        label: item
 | 
			
		||||
                    }).text(item).appendTo(encSel);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            encSel.val(node.encoding);
 | 
			
		||||
            $("#node-input-overwriteFile").on("change",function() {
 | 
			
		||||
                if (this.value === "delete") {
 | 
			
		||||
                    $(".form-row-file-write-options").hide();
 | 
			
		||||
                    $(".form-row-file-encoding").hide();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $(".form-row-file-write-options").show();
 | 
			
		||||
                    $(".form-row-file-encoding").show();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    RED.nodes.registerType('file in',{
 | 
			
		||||
        category: 'storage',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            filename: {value:""},
 | 
			
		||||
            format: {value:"utf8"},
 | 
			
		||||
            chunk: {value:false},
 | 
			
		||||
            sendError: {value: false},
 | 
			
		||||
            encoding: {value: "none"},
 | 
			
		||||
            allProps: {value: false}
 | 
			
		||||
        },
 | 
			
		||||
        color:"BurlyWood",
 | 
			
		||||
        inputs:1,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        outputLabels: function(i) {
 | 
			
		||||
            var l;
 | 
			
		||||
            if (this.format === "utf8") {
 | 
			
		||||
                l = "file.label.utf8String";
 | 
			
		||||
            } else if (this.format === "lines") {
 | 
			
		||||
                l = "file.label.utf8String_plural";
 | 
			
		||||
            } else if (this.format === "stream") {
 | 
			
		||||
                l = "file.label.binaryBuffer_plural";
 | 
			
		||||
            } else {
 | 
			
		||||
                l = "file.label.binaryBuffer";
 | 
			
		||||
            }
 | 
			
		||||
            return this._(l);
 | 
			
		||||
        },
 | 
			
		||||
        icon: "file-in.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||this.filename||this._("file.label.read");
 | 
			
		||||
        },
 | 
			
		||||
        paletteLabel: RED._("node-red:file.label.read"),
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var node = this;
 | 
			
		||||
            var encSel = $("#node-input-encoding");
 | 
			
		||||
            var label = node._("file.encoding.none");
 | 
			
		||||
            $("<option/>", {
 | 
			
		||||
                value: "none",
 | 
			
		||||
                label: label
 | 
			
		||||
            }).text(label).appendTo(encSel);
 | 
			
		||||
            encodings.forEach(function(item) {
 | 
			
		||||
                if(Array.isArray(item)) {
 | 
			
		||||
                    var group = $("<optgroup/>", {
 | 
			
		||||
                        label: node._(item[0])
 | 
			
		||||
                    }).appendTo(encSel);
 | 
			
		||||
                    for (var i = 1; i < item.length; i++) {
 | 
			
		||||
                        var enc = item[i];
 | 
			
		||||
                        $("<option/>", {
 | 
			
		||||
                            value: enc,
 | 
			
		||||
                            label: enc
 | 
			
		||||
                        }).text(enc).appendTo(group);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    $("<option/>", {
 | 
			
		||||
                        value: item,
 | 
			
		||||
                        label: item
 | 
			
		||||
                    }).text(item).appendTo(encSel);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            encSel.val(node.encoding);
 | 
			
		||||
            $("#node-input-format").on("change",function() {
 | 
			
		||||
                var format = $("#node-input-format").val();
 | 
			
		||||
                if ((format === "utf8") || (format === "lines")) {
 | 
			
		||||
                    $("#encoding-spec").show();
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    $("#encoding-spec").hide();
 | 
			
		||||
                }
 | 
			
		||||
                if ((format === "lines") || (format === "stream")) {
 | 
			
		||||
                    $("#file-allprops").show();
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    $("#file-allprops").hide();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
})();
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,401 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    var fs = require("fs-extra");
 | 
			
		||||
    var os = require("os");
 | 
			
		||||
    var path = require("path");
 | 
			
		||||
    var iconv = require("iconv-lite")
 | 
			
		||||
 | 
			
		||||
    function encode(data, enc) {
 | 
			
		||||
        if (enc !== "none") {
 | 
			
		||||
            return iconv.encode(data, enc);
 | 
			
		||||
        }
 | 
			
		||||
        return Buffer.from(data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function decode(data, enc) {
 | 
			
		||||
        if (enc !== "none") {
 | 
			
		||||
            return iconv.decode(data, enc);
 | 
			
		||||
        }
 | 
			
		||||
        return data.toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function FileNode(n) {
 | 
			
		||||
        // Write/delete a file
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.filename = n.filename;
 | 
			
		||||
        this.appendNewline = n.appendNewline;
 | 
			
		||||
        this.overwriteFile = n.overwriteFile.toString();
 | 
			
		||||
        this.createDir = n.createDir || false;
 | 
			
		||||
        this.encoding = n.encoding || "none";
 | 
			
		||||
        var node = this;
 | 
			
		||||
        node.wstream = null;
 | 
			
		||||
        node.msgQueue = [];
 | 
			
		||||
        node.closing = false;
 | 
			
		||||
        node.closeCallback = null;
 | 
			
		||||
 | 
			
		||||
        function processMsg(msg,nodeSend, done) {
 | 
			
		||||
            var filename = node.filename || msg.filename || "";
 | 
			
		||||
            var fullFilename = filename;
 | 
			
		||||
            if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
 | 
			
		||||
                fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
 | 
			
		||||
            }
 | 
			
		||||
            if ((!node.filename) && (!node.tout)) {
 | 
			
		||||
                node.tout = setTimeout(function() {
 | 
			
		||||
                    node.status({fill:"grey",shape:"dot",text:filename});
 | 
			
		||||
                    clearTimeout(node.tout);
 | 
			
		||||
                    node.tout = null;
 | 
			
		||||
                },333);
 | 
			
		||||
            }
 | 
			
		||||
            if (filename === "") {
 | 
			
		||||
                node.warn(RED._("file.errors.nofilename"));
 | 
			
		||||
                done();
 | 
			
		||||
            } else if (node.overwriteFile === "delete") {
 | 
			
		||||
                fs.unlink(fullFilename, function (err) {
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg);
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        if (RED.settings.verbose) {
 | 
			
		||||
                            node.log(RED._("file.status.deletedfile",{file:filename}));
 | 
			
		||||
                        }
 | 
			
		||||
                        nodeSend(msg);
 | 
			
		||||
                    }
 | 
			
		||||
                    done();
 | 
			
		||||
                });
 | 
			
		||||
            } else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
 | 
			
		||||
                var dir = path.dirname(fullFilename);
 | 
			
		||||
                if (node.createDir) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        fs.ensureDirSync(dir);
 | 
			
		||||
                    }
 | 
			
		||||
                    catch(err) {
 | 
			
		||||
                        node.error(RED._("file.errors.createfail",{error:err.toString()}),msg);
 | 
			
		||||
                        done();
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var data = msg.payload;
 | 
			
		||||
                if ((typeof data === "object") && (!Buffer.isBuffer(data))) {
 | 
			
		||||
                    data = JSON.stringify(data);
 | 
			
		||||
                }
 | 
			
		||||
                if (typeof data === "boolean") { data = data.toString(); }
 | 
			
		||||
                if (typeof data === "number") { data = data.toString(); }
 | 
			
		||||
                if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
 | 
			
		||||
                var buf;
 | 
			
		||||
                if (node.encoding === "setbymsg") {
 | 
			
		||||
                    buf = encode(data, msg.encoding || "none");
 | 
			
		||||
                }
 | 
			
		||||
                else { buf = encode(data, node.encoding); }
 | 
			
		||||
                if (node.overwriteFile === "true") {
 | 
			
		||||
                    var wstream = fs.createWriteStream(fullFilename, { encoding:'binary', flags:'w', autoClose:true });
 | 
			
		||||
                    node.wstream = wstream;
 | 
			
		||||
                    wstream.on("error", function(err) {
 | 
			
		||||
                        node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);
 | 
			
		||||
                        done();
 | 
			
		||||
                    });
 | 
			
		||||
                    wstream.on("open", function() {
 | 
			
		||||
                        wstream.once("close", function() {
 | 
			
		||||
                            nodeSend(msg);
 | 
			
		||||
                            done();
 | 
			
		||||
                        });
 | 
			
		||||
                        wstream.end(buf);
 | 
			
		||||
                    })
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    // Append mode
 | 
			
		||||
                    var recreateStream = !node.wstream || !node.filename;
 | 
			
		||||
                    if (node.wstream && node.wstreamIno) {
 | 
			
		||||
                        // There is already a stream open and we have the inode
 | 
			
		||||
                        // of the file. Check the file hasn't been deleted
 | 
			
		||||
                        // or deleted and recreated.
 | 
			
		||||
                        try {
 | 
			
		||||
                            var stat = fs.statSync(fullFilename);
 | 
			
		||||
                            // File exists - check the inode matches
 | 
			
		||||
                            if (stat.ino !== node.wstreamIno) {
 | 
			
		||||
                                // The file has been recreated. Close the current
 | 
			
		||||
                                // stream and recreate it
 | 
			
		||||
                                recreateStream = true;
 | 
			
		||||
                                node.wstream.end();
 | 
			
		||||
                                delete node.wstream;
 | 
			
		||||
                                delete node.wstreamIno;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        catch(err) {
 | 
			
		||||
                            // File does not exist
 | 
			
		||||
                            recreateStream = true;
 | 
			
		||||
                            node.wstream.end();
 | 
			
		||||
                            delete node.wstream;
 | 
			
		||||
                            delete node.wstreamIno;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (recreateStream) {
 | 
			
		||||
                        node.wstream = fs.createWriteStream(fullFilename, { encoding:'binary', flags:'a', autoClose:true });
 | 
			
		||||
                        node.wstream.on("open", function(fd) {
 | 
			
		||||
                            try {
 | 
			
		||||
                                var stat = fs.statSync(fullFilename);
 | 
			
		||||
                                node.wstreamIno = stat.ino;
 | 
			
		||||
                            } catch(err) {
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                        node.wstream.on("error", function(err) {
 | 
			
		||||
                            node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg);
 | 
			
		||||
                            done();
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                    if (node.filename) {
 | 
			
		||||
                        // Static filename - write and reuse the stream next time
 | 
			
		||||
                        node.wstream.write(buf, function() {
 | 
			
		||||
                            nodeSend(msg);
 | 
			
		||||
                            done();
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        // Dynamic filename - write and close the stream
 | 
			
		||||
                        node.wstream.once("close", function() {
 | 
			
		||||
                            nodeSend(msg);
 | 
			
		||||
                            delete node.wstream;
 | 
			
		||||
                            delete node.wstreamIno;
 | 
			
		||||
                            done();
 | 
			
		||||
                        });
 | 
			
		||||
                        node.wstream.end(buf);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                done();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function processQueue(queue) {
 | 
			
		||||
            var event = queue[0];
 | 
			
		||||
            processMsg(event.msg, event.send, function() {
 | 
			
		||||
                event.done();
 | 
			
		||||
                queue.shift();
 | 
			
		||||
                if (queue.length > 0) {
 | 
			
		||||
                    processQueue(queue);
 | 
			
		||||
                }
 | 
			
		||||
                else if (node.closing) {
 | 
			
		||||
                    closeNode();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.on("input", function(msg,nodeSend,nodeDone) {
 | 
			
		||||
            var msgQueue = node.msgQueue;
 | 
			
		||||
            msgQueue.push({
 | 
			
		||||
                msg: msg,
 | 
			
		||||
                send: nodeSend,
 | 
			
		||||
                done: nodeDone
 | 
			
		||||
            })
 | 
			
		||||
            if (msgQueue.length > 1) {
 | 
			
		||||
                // pending write exists
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            try {
 | 
			
		||||
                processQueue(msgQueue);
 | 
			
		||||
            }
 | 
			
		||||
            catch (e) {
 | 
			
		||||
                node.msgQueue = [];
 | 
			
		||||
                if (node.closing) {
 | 
			
		||||
                    closeNode();
 | 
			
		||||
                }
 | 
			
		||||
                throw e;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        function closeNode() {
 | 
			
		||||
            if (node.wstream) { node.wstream.end(); }
 | 
			
		||||
            if (node.tout) { clearTimeout(node.tout); }
 | 
			
		||||
            node.status({});
 | 
			
		||||
            var cb = node.closeCallback;
 | 
			
		||||
            node.closeCallback = null;
 | 
			
		||||
            node.closing = false;
 | 
			
		||||
            if (cb) {
 | 
			
		||||
                cb();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.on('close', function(done) {
 | 
			
		||||
            if (node.closing) {
 | 
			
		||||
                // already closing
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            node.closing = true;
 | 
			
		||||
            if (done) {
 | 
			
		||||
                node.closeCallback = done;
 | 
			
		||||
            }
 | 
			
		||||
            if (node.msgQueue.length > 0) {
 | 
			
		||||
                // close after queue processed
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                closeNode();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("file",FileNode);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function FileInNode(n) {
 | 
			
		||||
        // Read a file
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.filename = n.filename;
 | 
			
		||||
        this.format = n.format;
 | 
			
		||||
        this.chunk = false;
 | 
			
		||||
        this.encoding = n.encoding || "none";
 | 
			
		||||
        this.allProps = n.allProps || false;
 | 
			
		||||
        if (n.sendError === undefined) {
 | 
			
		||||
            this.sendError = true;
 | 
			
		||||
        } else {
 | 
			
		||||
            this.sendError = n.sendError;
 | 
			
		||||
        }
 | 
			
		||||
        if (this.format === "lines") { this.chunk = true; }
 | 
			
		||||
        if (this.format === "stream") { this.chunk = true; }
 | 
			
		||||
        var node = this;
 | 
			
		||||
 | 
			
		||||
        this.on("input",function(msg, nodeSend, nodeDone) {
 | 
			
		||||
            var filename = (node.filename || msg.filename || "").replace(/\t|\r|\n/g,'');
 | 
			
		||||
            var fullFilename = filename;
 | 
			
		||||
            if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
 | 
			
		||||
                fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
 | 
			
		||||
            }
 | 
			
		||||
            if (!node.filename) {
 | 
			
		||||
                node.status({fill:"grey",shape:"dot",text:filename});
 | 
			
		||||
            }
 | 
			
		||||
            if (filename === "") {
 | 
			
		||||
                node.warn(RED._("file.errors.nofilename"));
 | 
			
		||||
                nodeDone();
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                msg.filename = filename;
 | 
			
		||||
                var lines = Buffer.from([]);
 | 
			
		||||
                var spare = "";
 | 
			
		||||
                var count = 0;
 | 
			
		||||
                var type = "buffer";
 | 
			
		||||
                var ch = "";
 | 
			
		||||
                if (node.format === "lines") {
 | 
			
		||||
                    ch = "\n";
 | 
			
		||||
                    type = "string";
 | 
			
		||||
                }
 | 
			
		||||
                var getout = false;
 | 
			
		||||
 | 
			
		||||
                var rs = fs.createReadStream(fullFilename)
 | 
			
		||||
                    .on('readable', function () {
 | 
			
		||||
                        var chunk;
 | 
			
		||||
                        var m;
 | 
			
		||||
                        var hwm = rs._readableState.highWaterMark;
 | 
			
		||||
                        while (null !== (chunk = rs.read())) {
 | 
			
		||||
                            if (node.chunk === true) {
 | 
			
		||||
                                getout = true;
 | 
			
		||||
                                if (node.format === "lines") {
 | 
			
		||||
                                    spare += decode(chunk, node.encoding);
 | 
			
		||||
                                    var bits = spare.split("\n");
 | 
			
		||||
                                    for (var i=0; i < bits.length - 1; i++) {
 | 
			
		||||
                                        m = {};
 | 
			
		||||
                                        if (node.allProps == true) {
 | 
			
		||||
                                            m = RED.util.cloneMessage(msg);
 | 
			
		||||
                                        }
 | 
			
		||||
                                        else {
 | 
			
		||||
                                            m.topic = msg.topic;
 | 
			
		||||
                                            m.filename = msg.filename;
 | 
			
		||||
                                        }
 | 
			
		||||
                                        m.payload = bits[i];
 | 
			
		||||
                                        m.parts= {index:count, ch:ch, type:type, id:msg._msgid}
 | 
			
		||||
                                        count += 1;
 | 
			
		||||
                                        nodeSend(m);
 | 
			
		||||
                                    }
 | 
			
		||||
                                    spare = bits[i];
 | 
			
		||||
                                }
 | 
			
		||||
                                if (node.format === "stream") {
 | 
			
		||||
                                    m = {};
 | 
			
		||||
                                    if (node.allProps == true) {
 | 
			
		||||
                                        m = RED.util.cloneMessage(msg);
 | 
			
		||||
                                    }
 | 
			
		||||
                                    else {
 | 
			
		||||
                                        m.topic = msg.topic;
 | 
			
		||||
                                        m.filename = msg.filename;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    m.payload = chunk;
 | 
			
		||||
                                    m.parts = {index:count, ch:ch, type:type, id:msg._msgid}
 | 
			
		||||
                                    count += 1;
 | 
			
		||||
                                    if (chunk.length < hwm) { // last chunk is smaller that high water mark = eof
 | 
			
		||||
                                        getout = false;
 | 
			
		||||
                                        m.parts.count = count;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    nodeSend(m);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            else {
 | 
			
		||||
                                lines = Buffer.concat([lines,chunk]);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                    .on('error', function(err) {
 | 
			
		||||
                        node.error(err, msg);
 | 
			
		||||
                        if (node.sendError) {
 | 
			
		||||
                            var sendMessage = RED.util.cloneMessage(msg);
 | 
			
		||||
                            delete sendMessage.payload;
 | 
			
		||||
                            sendMessage.error = err;
 | 
			
		||||
                            nodeSend(sendMessage);
 | 
			
		||||
                        }
 | 
			
		||||
                        nodeDone();
 | 
			
		||||
                    })
 | 
			
		||||
                    .on('end', function() {
 | 
			
		||||
                        if (node.chunk === false) {
 | 
			
		||||
                            if (node.format === "utf8") {
 | 
			
		||||
                                msg.payload = decode(lines, node.encoding);
 | 
			
		||||
                            }
 | 
			
		||||
                            else { msg.payload = lines; }
 | 
			
		||||
                            nodeSend(msg);
 | 
			
		||||
                        }
 | 
			
		||||
                        else if (node.format === "lines") {
 | 
			
		||||
                            var m = {};
 | 
			
		||||
                            if (node.allProps) {
 | 
			
		||||
                                m = RED.util.cloneMessage(msg);
 | 
			
		||||
                            }
 | 
			
		||||
                            else {
 | 
			
		||||
                                m.topic = msg.topic;
 | 
			
		||||
                                m.filename = msg.filename;
 | 
			
		||||
                            }
 | 
			
		||||
                            m.payload = spare;
 | 
			
		||||
                            m.parts = {
 | 
			
		||||
                                index: count,
 | 
			
		||||
                                count: count + 1,
 | 
			
		||||
                                ch: ch,
 | 
			
		||||
                                type: type,
 | 
			
		||||
                                id: msg._msgid
 | 
			
		||||
                            }
 | 
			
		||||
                            nodeSend(m);
 | 
			
		||||
                        }
 | 
			
		||||
                        else if (getout) { // last chunk same size as high water mark - have to send empty extra packet.
 | 
			
		||||
                            var m = { parts:{index:count, count:count, ch:ch, type:type, id:msg._msgid} };
 | 
			
		||||
                            nodeSend(m);
 | 
			
		||||
                        }
 | 
			
		||||
                        nodeDone();
 | 
			
		||||
                    });
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        this.on('close', function() {
 | 
			
		||||
            node.status({});
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("file in",FileInNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,53 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="watch">
 | 
			
		||||
    <div class="form-row node-input-filename">
 | 
			
		||||
         <label for="node-input-files"><i class="fa fa-file"></i> <span data-i18n="watch.label.files"></span></label>
 | 
			
		||||
         <input id="node-input-files" type="text" tabindex="1" data-i18n="[placeholder]watch.placeholder.files">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label> </label>
 | 
			
		||||
        <input type="checkbox" id="node-input-recursive" style="display:inline-block; width:auto; vertical-align:top;">
 | 
			
		||||
        <label for="node-input-recursive" style="width:70%;"> <span data-i18n="watch.label.recursive"></span></label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
     <div id="node-input-tip" class="form-tips"><span data-i18n="watch.tip"></span></div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('watch',{
 | 
			
		||||
        category: 'storage',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            files: {value:"",required:true},
 | 
			
		||||
            recursive: {value:""}
 | 
			
		||||
        },
 | 
			
		||||
        color:"BurlyWood",
 | 
			
		||||
        inputs:0,
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "watch.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            return this.name||this.files||this._("watch.watch");
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,95 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    var Notify = require("fs.notify");
 | 
			
		||||
    var fs = require("fs");
 | 
			
		||||
    var path = require("path");
 | 
			
		||||
 | 
			
		||||
    var getAllDirs = function (dir, filelist) {
 | 
			
		||||
        filelist = filelist || [];
 | 
			
		||||
        fs.readdirSync(dir).forEach(file => {
 | 
			
		||||
            try {
 | 
			
		||||
                if (fs.statSync(path.join(dir, file)).isDirectory() ) {
 | 
			
		||||
                    filelist.push(path.join(dir, file));
 | 
			
		||||
                    getAllDirs(path.join(dir, file), filelist);
 | 
			
		||||
                }
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                //should we raise an error?
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        return filelist;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function WatchNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
 | 
			
		||||
        this.recursive = n.recursive || false;
 | 
			
		||||
        this.files = (n.files || "").split(",");
 | 
			
		||||
        for (var f=0; f < this.files.length; f++) {
 | 
			
		||||
            this.files[f] = this.files[f].trim();
 | 
			
		||||
        }
 | 
			
		||||
        this.p = (this.files.length === 1) ? this.files[0] : JSON.stringify(this.files);
 | 
			
		||||
        var node = this;
 | 
			
		||||
 | 
			
		||||
        if (node.recursive) {
 | 
			
		||||
            for (var fi in node.files) {
 | 
			
		||||
                if (node.files.hasOwnProperty(fi)) {
 | 
			
		||||
                    node.files = node.files.concat(getAllDirs( node.files[fi]));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var notifications = new Notify(node.files);
 | 
			
		||||
        notifications.on('change', function (file, event, fpath) {
 | 
			
		||||
            var stat;
 | 
			
		||||
            try {
 | 
			
		||||
                if (fs.statSync(fpath).isDirectory()) { fpath = path.join(fpath,file); }
 | 
			
		||||
                stat = fs.statSync(fpath);
 | 
			
		||||
            } catch(e) { }
 | 
			
		||||
            var type = "none";
 | 
			
		||||
            var msg = { payload:fpath, topic:node.p, file:file, filename:fpath };
 | 
			
		||||
            if (stat) {
 | 
			
		||||
                if (stat.isFile()) { type = "file"; msg.size = stat.size; }
 | 
			
		||||
                else if (stat.isBlockDevice()) { type = "blockdevice"; }
 | 
			
		||||
                else if (stat.isCharacterDevice()) { type = "characterdevice"; }
 | 
			
		||||
                else if (stat.isSocket()) { type = "socket"; }
 | 
			
		||||
                else if (stat.isFIFO()) { type = "fifo"; }
 | 
			
		||||
                else if (stat.isDirectory()) {
 | 
			
		||||
                    type = "directory";
 | 
			
		||||
                    if (node.recursive) {
 | 
			
		||||
                        notifications.add([fpath]);
 | 
			
		||||
                        notifications.add(getAllDirs(fpath));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else { type = "n/a"; }
 | 
			
		||||
            }
 | 
			
		||||
            msg.type = type;
 | 
			
		||||
            node.send(msg);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        notifications.on('error', function (error, fpath) {
 | 
			
		||||
            var msg = { payload:fpath };
 | 
			
		||||
            node.error(error,msg);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.close = function() {
 | 
			
		||||
            notifications.close();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("watch",WatchNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"87bd706a.aec93","type":"comment","z":"3ae4b3d9.1f77bc","name":"Output payload value to debug sidebar","info":"Debug node can be used to output payload value to debug sidebar.","x":230,"y":60,"wires":[]},{"id":"8035b07f.7547e","type":"inject","z":"3ae4b3d9.1f77bc","name":"","props":[{"p":"payload","v":"Hello, World!","vt":"str"},{"p":"topic","v":"","vt":"string"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello, World!","payloadType":"str","x":210,"y":100,"wires":[["20d1344f.931e3c"]]},{"id":"20d1344f.931e3c","type":"debug","z":"3ae4b3d9.1f77bc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":450,"y":100,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"8c66d039.44465","type":"comment","z":"e6956267.5d174","name":"Output complete object","info":"Debug node can be used to output whole object value to debug sidebar.","x":160,"y":60,"wires":[]},{"id":"dac87e40.90376","type":"inject","z":"e6956267.5d174","name":"","props":[{"p":"payload","v":"Hello, World!","vt":"str"},{"p":"topic","v":"Sample","vt":"string"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Sample","payload":"Hello, World!","payloadType":"str","x":220,"y":100,"wires":[["a77fa5e3.fac248"]]},{"id":"a77fa5e3.fac248","type":"debug","z":"e6956267.5d174","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":410,"y":100,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"fb1c3ce9.c29ee","type":"comment","z":"395f4b0d.8a8774","name":"Output to  console","info":"Debug node can be used to output values to console.","x":130,"y":60,"wires":[]},{"id":"3c24e746.9ff6a8","type":"inject","z":"395f4b0d.8a8774","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello, World!","payloadType":"str","x":170,"y":100,"wires":[["66cc7b44.82ba74"]]},{"id":"66cc7b44.82ba74","type":"debug","z":"395f4b0d.8a8774","name":"","active":true,"tosidebar":false,"console":true,"tostatus":false,"complete":"payload","targetType":"msg","x":420,"y":100,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"33791e6a.973502","type":"comment","z":"55587092.1f2b4","name":"Output to node status area","info":"Debug node can be used to output values to  status area below the node.","x":170,"y":60,"wires":[]},{"id":"a5d8e744.a034e8","type":"inject","z":"55587092.1f2b4","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello, World!","payloadType":"str","x":190,"y":100,"wires":[["b0646a4d.db4bc8"]]},{"id":"b0646a4d.db4bc8","type":"debug","z":"55587092.1f2b4","name":"","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","x":430,"y":100,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"30ed0a73.fcef86","type":"comment","z":"61feb619.5e3d68","name":"Formatting output using JSONata","info":"Debug node can format output value using JSONata expression.","x":200,"y":60,"wires":[]},{"id":"6f477e7d.3a8da","type":"inject","z":"61feb619.5e3d68","name":"","props":[{"p":"payload","v":"Hello, World!","vt":"str"},{"p":"topic","v":"Sample","vt":"string"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Sample","payload":"Hello, World!","payloadType":"str","x":220,"y":100,"wires":[["19c9408d.ac6d4f"]]},{"id":"19c9408d.ac6d4f","type":"debug","z":"61feb619.5e3d68","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"\"[\" & topic & \"] \" & payload","targetType":"jsonata","x":420,"y":100,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"a30d20e6.ec6dc","type":"link in","z":"2f2d9fa4.be6fc","name":"","links":["70d6f012.8fe6d"],"x":235,"y":240,"wires":[["6bf52c5c.d301c4"]]},{"id":"70d6f012.8fe6d","type":"link out","z":"2f2d9fa4.be6fc","name":"","links":["a30d20e6.ec6dc"],"x":315,"y":180,"wires":[]},{"id":"353c85ce.993d0a","type":"inject","z":"2f2d9fa4.be6fc","name":"","topic":"","payload":"Hello, World!","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":210,"y":180,"wires":[["70d6f012.8fe6d"]]},{"id":"6bf52c5c.d301c4","type":"debug","z":"2f2d9fa4.be6fc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":370,"y":240,"wires":[]},{"id":"62ea32aa.d73aac","type":"comment","z":"2f2d9fa4.be6fc","name":"Example: Link Node","info":"Output of link out node can be connected to input of link in node.  The connection between links in/out is not shown, so the flow representation can be simplified.","x":130,"y":40,"wires":[]},{"id":"85133fcc.e482","type":"comment","z":"2f2d9fa4.be6fc","name":"Link output of inject node to input of debug node","info":"","x":260,"y":100,"wires":[]},{"id":"c588bc36.87fec","type":"comment","z":"2f2d9fa4.be6fc","name":"↓ connect to link in node","info":"","x":410,"y":140,"wires":[]},{"id":"8abca900.6dfe78","type":"comment","z":"2f2d9fa4.be6fc","name":"↑ connect from link out node","info":"","x":340,"y":280,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"26e7643f.13ebcc","type":"comment","z":"f4cb1920.4d58c8","name":"Set any property value","info":"Change node can set value to any message property.","x":160,"y":60,"wires":[]},{"id":"4da2494d.9aff68","type":"inject","z":"f4cb1920.4d58c8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":120,"wires":[["5111e689.62e838"]]},{"id":"58ea5868.0596e8","type":"debug","z":"f4cb1920.4d58c8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":530,"y":120,"wires":[]},{"id":"5111e689.62e838","type":"change","z":"f4cb1920.4d58c8","name":"set payload & topic","rules":[{"t":"set","p":"payload","pt":"msg","to":"Hello, World!","tot":"str"},{"t":"set","p":"topic","pt":"msg","to":"Title","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":360,"y":120,"wires":[["58ea5868.0596e8"]]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"a9039cda.3649e","type":"comment","z":"a808932c.4ca77","name":"Set value using JSONata","info":"Change node can set value to using JSONata expression.","x":170,"y":60,"wires":[]},{"id":"bdcdd579.cfe668","type":"inject","z":"a808932c.4ca77","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":120,"wires":[["28d110a7.ce3e2"]]},{"id":"c6677fa5.8c111","type":"debug","z":"a808932c.4ca77","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":530,"y":120,"wires":[]},{"id":"28d110a7.ce3e2","type":"change","z":"a808932c.4ca77","name":"use JSONata","rules":[{"t":"set","p":"payload","pt":"msg","to":"Hello","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"payload & \", World!\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":120,"wires":[["c6677fa5.8c111"]]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"e9143349.a64f7","type":"comment","z":"a32e6d69.1b13b","name":"Set value from environment variable","info":"Change node can set value from environment variable.","x":200,"y":60,"wires":[]},{"id":"a7c2725.a631f9","type":"inject","z":"a32e6d69.1b13b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":120,"wires":[["e455c302.2f795"]]},{"id":"6f203119.21895","type":"debug","z":"a32e6d69.1b13b","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":530,"y":120,"wires":[]},{"id":"e455c302.2f795","type":"change","z":"a32e6d69.1b13b","name":"set env var","rules":[{"t":"set","p":"payload","pt":"msg","to":"HOME","tot":"env"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":120,"wires":[["6f203119.21895"]]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"6ecac54d.c43ffc","type":"comment","z":"87ace6c0.f01da8","name":"Set flow context","info":"Change node can set flow context.","x":140,"y":60,"wires":[]},{"id":"80e966d3.9d7a78","type":"inject","z":"87ace6c0.f01da8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":200,"y":234,"wires":[["abaee298.2d77e"]]},{"id":"60ab671d.b0bbf8","type":"debug","z":"87ace6c0.f01da8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":234,"wires":[]},{"id":"abaee298.2d77e","type":"change","z":"87ace6c0.f01da8","name":"increment count","rules":[{"t":"set","p":"count","pt":"flow","to":"$flowContext(\"count\")+1\t","tot":"jsonata"},{"t":"set","p":"payload","pt":"msg","to":"count","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":234,"wires":[["60ab671d.b0bbf8"]]},{"id":"2de2bb38.f20ff4","type":"inject","z":"87ace6c0.f01da8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":200,"y":140,"wires":[["7b96521e.a3cb0c"]]},{"id":"597b63cd.b3218c","type":"debug","z":"87ace6c0.f01da8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":140,"wires":[]},{"id":"7b96521e.a3cb0c","type":"change","z":"87ace6c0.f01da8","name":"set count to 0","rules":[{"t":"set","p":"count","pt":"flow","to":"0","tot":"num"},{"t":"set","p":"payload","pt":"msg","to":"count","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":140,"wires":[["597b63cd.b3218c"]]},{"id":"3d8cdc6d.2620e4","type":"comment","z":"87ace6c0.f01da8","name":"↓ Initialize","info":"","x":200,"y":100,"wires":[]},{"id":"d8069121.80de7","type":"comment","z":"87ace6c0.f01da8","name":"↓ Count up","info":"","x":200,"y":194,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"87bd706a.aec93","type":"comment","z":"3ae4b3d9.1f77bc","name":"Delay message","info":"Delay node can delay sending input message to output port by a specified amount of time.","x":160,"y":60,"wires":[]},{"id":"1d17715c.34170f","type":"inject","z":"3ae4b3d9.1f77bc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello, World!","payloadType":"str","x":210,"y":120,"wires":[["26b43de5.4df8f2","9930fecd.ee0c8"]]},{"id":"9930fecd.ee0c8","type":"debug","z":"3ae4b3d9.1f77bc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":410,"y":120,"wires":[]},{"id":"26b43de5.4df8f2","type":"delay","z":"3ae4b3d9.1f77bc","name":"","pauseType":"delay","timeout":"3","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":400,"y":180,"wires":[["c8c2796c.dcb9f8"]]},{"id":"c8c2796c.dcb9f8","type":"change","z":"3ae4b3d9.1f77bc","name":"Goodbye, World!","rules":[{"t":"set","p":"payload","pt":"msg","to":"Goodbye, World!","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":590,"y":180,"wires":[["c58a290e.2fa438"]]},{"id":"c58a290e.2fa438","type":"debug","z":"3ae4b3d9.1f77bc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":790,"y":180,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"c15b8c3e.955ed","type":"comment","z":"6f1773ed.b7c2fc","name":"Delay message by message property","info":"Delay node can delay sending input message to output port by a specified amount of time by  `msg.delay` property.","x":210,"y":60,"wires":[]},{"id":"a5ed5817.9df448","type":"inject","z":"6f1773ed.b7c2fc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"},{"p":"delay","v":"1000","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"delay 1s","payloadType":"str","x":180,"y":120,"wires":[["5cf53f4.25b7ec","59b7b67a.a8e888"]]},{"id":"59b7b67a.a8e888","type":"debug","z":"6f1773ed.b7c2fc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":390,"y":120,"wires":[]},{"id":"5cf53f4.25b7ec","type":"delay","z":"6f1773ed.b7c2fc","name":"","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":380,"y":180,"wires":[["fc989f41.c4114"]]},{"id":"fc989f41.c4114","type":"change","z":"6f1773ed.b7c2fc","name":"Goodbye, World!","rules":[{"t":"set","p":"payload","pt":"msg","to":"Goodbye, World!","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":570,"y":180,"wires":[["74ba3d1c.666034"]]},{"id":"74ba3d1c.666034","type":"debug","z":"6f1773ed.b7c2fc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":770,"y":180,"wires":[]},{"id":"6cdf7297.bf5a8c","type":"inject","z":"6f1773ed.b7c2fc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"},{"p":"delay","v":"10000","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"delay 10s","payloadType":"str","x":180,"y":180,"wires":[["59b7b67a.a8e888","5cf53f4.25b7ec"]]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"b0200f61.5efa5","type":"comment","z":"86a4fcf3.9f442","name":"Reset or flush pending message","info":"Delay node can reset or flush delayed message by sending it a message with `reset` or `flush` property.","x":170,"y":60,"wires":[]},{"id":"d5cd8991.e6d2e8","type":"inject","z":"86a4fcf3.9f442","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello, World!","payloadType":"str","x":170,"y":120,"wires":[["607f556b.3ec5fc","fd14cb.2044db38"]]},{"id":"fd14cb.2044db38","type":"debug","z":"86a4fcf3.9f442","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":370,"y":120,"wires":[]},{"id":"607f556b.3ec5fc","type":"delay","z":"86a4fcf3.9f442","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"minutes","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":360,"y":180,"wires":[["d1fc6763.2a30c8"]]},{"id":"d1fc6763.2a30c8","type":"change","z":"86a4fcf3.9f442","name":"Goodbye, World!","rules":[{"t":"set","p":"payload","pt":"msg","to":"Goodbye, World!","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":180,"wires":[["15486d4a.80c6f3"]]},{"id":"15486d4a.80c6f3","type":"debug","z":"86a4fcf3.9f442","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":750,"y":180,"wires":[]},{"id":"2b8b28c7.4c8978","type":"inject","z":"86a4fcf3.9f442","name":"reset","props":[{"p":"topic","vt":"str"},{"p":"reset","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":180,"wires":[["607f556b.3ec5fc"]]},{"id":"3a7e1bec.8bc3d4","type":"inject","z":"86a4fcf3.9f442","name":"flush","props":[{"p":"topic","vt":"str"},{"p":"flush","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":240,"wires":[["607f556b.3ec5fc"]]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"3dc5015b.96c97e","type":"comment","z":"73c00795.a13908","name":"Limit rate of message transfer for each topic","info":"Delay node can limit of message transmission from input to output port by a specified number of message per a specified time.\nIf `For each topic` is selected, messages are grouped by `msg.topic` value.  When grouping messages by topic, intermediate messages are dropped and the last messages received sent.","x":210,"y":60,"wires":[]},{"id":"bdafe4c6.4d5658","type":"inject","z":"73c00795.a13908","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"topic\":\"apple\",\"payload\":1},{\"topic\":\"apple\",\"payload\":2},{\"topic\":\"apple\",\"payload\":3},{\"topic\":\"orange\",\"payload\":1},{\"topic\":\"orange\",\"payload\":2},{\"topic\":\"orange\",\"payload\":3},{\"topic\":\"banana\",\"payload\":1},{\"topic\":\"banana\",\"payload\":2},{\"topic\":\"banana\",\"payload\":3}]","payloadType":"json","x":150,"y":120,"wires":[["f86dc462.195818"]]},{"id":"e0bdfcc1.cdf48","type":"delay","z":"73c00795.a13908","name":"","pauseType":"timed","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"2","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":690,"y":120,"wires":[["29d8beea.6b37f2"]]},{"id":"29d8beea.6b37f2","type":"debug","z":"73c00795.a13908","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":890,"y":120,"wires":[]},{"id":"f86dc462.195818","type":"split","z":"73c00795.a13908","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":290,"y":120,"wires":[["9feb3aac.616c38"]]},{"id":"9feb3aac.616c38","type":"change","z":"73c00795.a13908","name":"set topic&payload","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload.topic","tot":"msg"},{"t":"set","p":"payload","pt":"msg","to":"payload.payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":120,"wires":[["e0bdfcc1.cdf48"]]},{"id":"949199e8.89bc88","type":"comment","z":"73c00795.a13908","name":"↑ pass last message of each topic","info":"","x":740,"y":160,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"6a5f26a9.0cc2d8","type":"inject","z":"835cc8cc.b8cca8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello World!","payloadType":"str","x":190,"y":160,"wires":[["fc2b343c.bbe2f8"]]},{"id":"fc2b343c.bbe2f8","type":"exec","z":"835cc8cc.b8cca8","command":"echo","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":330,"y":160,"wires":[["2f3bcd73.6fedf2"],[],["3280586e.4e3d28"]]},{"id":"2f3bcd73.6fedf2","type":"debug","z":"835cc8cc.b8cca8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":510,"y":160,"wires":[]},{"id":"2b7bc9f1.ab36a6","type":"comment","z":"835cc8cc.b8cca8","name":"Execute external command appending additional args","info":"Exec node can execute external command and can receive its standard output as a payload of first message.  Standard error output can be received from second message.  The exit code of the command can be obtained from `code` property of third message payload.\n\nIf `Append msg.payload` checkbox is selected, payload value of the input message is appended to command string.\n","x":260,"y":60,"wires":[]},{"id":"3280586e.4e3d28","type":"debug","z":"835cc8cc.b8cca8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":510,"y":220,"wires":[]},{"id":"feba83da.8f227","type":"comment","z":"835cc8cc.b8cca8","name":"↓ execute echo command","info":"","x":390,"y":115,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"f507b27c.fff1","type":"inject","z":"462f83a6.d3c3cc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":180,"wires":[["28b4f75a.eae548"]]},{"id":"28b4f75a.eae548","type":"exec","z":"462f83a6.d3c3cc","command":"/non/existing/command","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":350,"y":180,"wires":[[],["27992c1f.a1c964"],["6e7ff001.2412d"]]},{"id":"6e7ff001.2412d","type":"debug","z":"462f83a6.d3c3cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":240,"wires":[]},{"id":"27992c1f.a1c964","type":"debug","z":"462f83a6.d3c3cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":180,"wires":[]},{"id":"77e85c7c.a560d4","type":"comment","z":"462f83a6.d3c3cc","name":"Execute external command and get error output","info":"Exec node can execute external command and can receive its standard output as a payload of first message.  Standard error output can be received from second message.  The exit code of the command can be obtained from `code` property of third message payload.\n","x":220,"y":80,"wires":[]},{"id":"c188c28c.f7d34","type":"comment","z":"462f83a6.d3c3cc","name":"↓ try to execute non-existing command","info":"","x":390,"y":134,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"990f33d3.3ffe2","type":"comment","z":"db89ae0c.f52b5","name":"Scale input value & round result to integer","info":"Range node can map input value to output value according to mapping specification.\nThe result value is rounded to nearest integer if `Round result to the nearest integer?` checkbox is checked.","x":240,"y":60,"wires":[]},{"id":"b2639501.5ad638","type":"inject","z":"db89ae0c.f52b5","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":190,"y":120,"wires":[["dc4509ee.773038"]]},{"id":"dc4509ee.773038","type":"range","z":"db89ae0c.f52b5","minin":"0","maxin":"9","minout":"0","maxout":"128","action":"scale","round":true,"property":"payload","name":"","x":360,"y":120,"wires":[["50991e7e.2ed24"]]},{"id":"50991e7e.2ed24","type":"debug","z":"db89ae0c.f52b5","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":520,"y":120,"wires":[]},{"id":"335e877f.d24e98","type":"inject","z":"db89ae0c.f52b5","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":190,"y":160,"wires":[["dc4509ee.773038"]]},{"id":"5d88207d.4189","type":"inject","z":"db89ae0c.f52b5","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"99","payloadType":"num","x":190,"y":200,"wires":[["dc4509ee.773038"]]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"f0bc6a80.d52578","type":"comment","z":"bdb79f11.23e1d","name":"Limit input value","info":"Range node can map input value to output value according to mapping specification.\nThe result value is limited to specified range if `Scale and limit to the target range` is selected.","x":140,"y":60,"wires":[]},{"id":"69652849.749198","type":"inject","z":"bdb79f11.23e1d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":170,"y":120,"wires":[["104f6f14.1c72e1"]]},{"id":"104f6f14.1c72e1","type":"range","z":"bdb79f11.23e1d","minin":"0","maxin":"100","minout":"0","maxout":"90","action":"clamp","round":false,"property":"payload","name":"","x":330,"y":120,"wires":[["10fcbed4.9c8d71"]]},{"id":"10fcbed4.9c8d71","type":"debug","z":"bdb79f11.23e1d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":500,"y":120,"wires":[]},{"id":"93bb4526.7d6e28","type":"inject","z":"bdb79f11.23e1d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":170,"y":160,"wires":[["104f6f14.1c72e1"]]},{"id":"6892dfd8.42386","type":"inject","z":"bdb79f11.23e1d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"100","payloadType":"num","x":170,"y":200,"wires":[["104f6f14.1c72e1"]]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"808e354d.8de148","type":"comment","z":"b1446352.d689e","name":"Scale and wrap input value","info":"Range node can map input value to output value according to mapping specification.\nThe result value is wrapped if `Scale and wrap within the target range` is selected.","x":170,"y":60,"wires":[]},{"id":"2f033c72.51dcc4","type":"inject","z":"b1446352.d689e","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":170,"y":120,"wires":[["cdaa82c4.820de"]]},{"id":"cdaa82c4.820de","type":"range","z":"b1446352.d689e","minin":"0","maxin":"9","minout":"0","maxout":"90","action":"roll","round":false,"property":"payload","name":"","x":330,"y":120,"wires":[["77f8872b.2a9968"]]},{"id":"77f8872b.2a9968","type":"debug","z":"b1446352.d689e","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":500,"y":120,"wires":[]},{"id":"84ac6a7e.77b8d8","type":"inject","z":"b1446352.d689e","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":170,"y":160,"wires":[["cdaa82c4.820de"]]},{"id":"92f554c3.b08ad8","type":"inject","z":"b1446352.d689e","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"100","payloadType":"num","x":170,"y":200,"wires":[["cdaa82c4.820de"]]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"6ec19fc7.a32ae","type":"comment","z":"e482b0ab.b1b43","name":"Check all rules","info":"Switch node apply all rules if `checking all rules` is selected in settings panel.","x":120,"y":60,"wires":[]},{"id":"1644e138.f8d1ef","type":"switch","z":"e482b0ab.b1b43","name":"","property":"payload","propertyType":"msg","rules":[{"t":"lt","v":"10","vt":"num"},{"t":"gt","v":"-10","vt":"num"}],"checkall":"true","repair":false,"outputs":2,"x":290,"y":140,"wires":[["624b4e9f.37fee"],["a89d6432.b68318"]]},{"id":"a7f64dbf.3e27b","type":"debug","z":"e482b0ab.b1b43","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":590,"y":100,"wires":[]},{"id":"a89d6432.b68318","type":"change","z":"e482b0ab.b1b43","name":">-10","rules":[{"t":"set","p":"payload","pt":"msg","to":">-10","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":180,"wires":[["f1136da2.23516"]]},{"id":"624b4e9f.37fee","type":"change","z":"e482b0ab.b1b43","name":"<10","rules":[{"t":"set","p":"payload","pt":"msg","to":"<10","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":100,"wires":[["a7f64dbf.3e27b"]]},{"id":"f1136da2.23516","type":"debug","z":"e482b0ab.b1b43","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":590,"y":180,"wires":[]},{"id":"c7e952e9.88e5e","type":"inject","z":"e482b0ab.b1b43","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"-10","payloadType":"num","x":150,"y":100,"wires":[["1644e138.f8d1ef"]]},{"id":"8cf8babd.b43db8","type":"inject","z":"e482b0ab.b1b43","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":150,"y":180,"wires":[["1644e138.f8d1ef"]]},{"id":"6a43ae86.b92ed","type":"inject","z":"e482b0ab.b1b43","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":150,"y":140,"wires":[["1644e138.f8d1ef"]]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"a12a5708.195688","type":"comment","z":"74b5d6de.372b18","name":"Stop after first match","info":"Switch node stops application of rules if `stopping after first match` is selected in settings panel and a rule evaluates to `true`.","x":160,"y":60,"wires":[]},{"id":"8aebdebf.5d7f2","type":"switch","z":"74b5d6de.372b18","name":"","property":"payload","propertyType":"msg","rules":[{"t":"lt","v":"10","vt":"num"},{"t":"gt","v":"-10","vt":"num"}],"checkall":"false","repair":false,"outputs":2,"x":310,"y":140,"wires":[["5d1851c.a9c5db"],["e8228605.f32018"]]},{"id":"60248c98.69fd44","type":"debug","z":"74b5d6de.372b18","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":610,"y":100,"wires":[]},{"id":"e8228605.f32018","type":"change","z":"74b5d6de.372b18","name":">-10","rules":[{"t":"set","p":"payload","pt":"msg","to":">-10","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":180,"wires":[["e9a51608.c322b8"]]},{"id":"5d1851c.a9c5db","type":"change","z":"74b5d6de.372b18","name":"<10","rules":[{"t":"set","p":"payload","pt":"msg","to":"<10","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":100,"wires":[["60248c98.69fd44"]]},{"id":"e9a51608.c322b8","type":"debug","z":"74b5d6de.372b18","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":610,"y":180,"wires":[]},{"id":"49222b4b.647a84","type":"inject","z":"74b5d6de.372b18","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"-10","payloadType":"num","x":170,"y":100,"wires":[["8aebdebf.5d7f2"]]},{"id":"39e8c133.a56f7e","type":"inject","z":"74b5d6de.372b18","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":170,"y":180,"wires":[["8aebdebf.5d7f2"]]},{"id":"3a22ec96.965a14","type":"inject","z":"74b5d6de.372b18","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":170,"y":140,"wires":[["8aebdebf.5d7f2"]]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"6dfd4381.eae2ec","type":"comment","z":"7d002985.82f928","name":"Select output based on type of payload value","info":"Switch node can route input message based on type of payload value.","x":210,"y":40,"wires":[]},{"id":"b139dacf.a0d818","type":"switch","z":"7d002985.82f928","name":"","property":"payload","propertyType":"msg","rules":[{"t":"istype","v":"string","vt":"string"},{"t":"istype","v":"number","vt":"number"}],"checkall":"false","repair":false,"outputs":2,"x":330,"y":120,"wires":[["7c5cd60c.e66b28"],["86cf5262.20f18"]]},{"id":"ca403f12.477a8","type":"debug","z":"7d002985.82f928","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":630,"y":160,"wires":[]},{"id":"7c5cd60c.e66b28","type":"change","z":"7d002985.82f928","name":"String","rules":[{"t":"set","p":"payload","pt":"msg","to":"string","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":80,"wires":[["d9669648.1177e8"]]},{"id":"86cf5262.20f18","type":"change","z":"7d002985.82f928","name":"Number","rules":[{"t":"set","p":"payload","pt":"msg","to":"number","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":480,"y":160,"wires":[["ca403f12.477a8"]]},{"id":"d9669648.1177e8","type":"debug","z":"7d002985.82f928","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":630,"y":80,"wires":[]},{"id":"d3f1c718.dc67e8","type":"inject","z":"7d002985.82f928","name":"Number:128","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"128","payloadType":"num","x":170,"y":80,"wires":[["b139dacf.a0d818"]]},{"id":"a59e275e.6d48c8","type":"inject","z":"7d002985.82f928","name":"String:128","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"128","payloadType":"str","x":160,"y":160,"wires":[["b139dacf.a0d818"]]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"175ceb0d.8dbe45","type":"comment","z":"17f634f4.e4bc9b","name":"Use JSONata expression for rules","info":"Switch node can use JSONata expression for calculating complex conditions.","x":200,"y":60,"wires":[]},{"id":"d89491c3.793a3","type":"switch","z":"17f634f4.e4bc9b","name":"","property":"payload","propertyType":"msg","rules":[{"t":"jsonata_exp","v":"(payload % 2) = 0","vt":"jsonata"},{"t":"jsonata_exp","v":"(payload % 2) = 1","vt":"jsonata"}],"checkall":"false","repair":false,"outputs":2,"x":310,"y":140,"wires":[["d6cb78a6.872908"],["1f0c62bb.c3d52d"]]},{"id":"9ae9a2aa.a895c","type":"debug","z":"17f634f4.e4bc9b","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":610,"y":100,"wires":[]},{"id":"1f0c62bb.c3d52d","type":"change","z":"17f634f4.e4bc9b","name":"Odd","rules":[{"t":"set","p":"payload","pt":"msg","to":"odd","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":180,"wires":[["34b0a9fb.bafdb6"]]},{"id":"d6cb78a6.872908","type":"change","z":"17f634f4.e4bc9b","name":"Even","rules":[{"t":"set","p":"payload","pt":"msg","to":"even","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":100,"wires":[["9ae9a2aa.a895c"]]},{"id":"34b0a9fb.bafdb6","type":"debug","z":"17f634f4.e4bc9b","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":610,"y":180,"wires":[]},{"id":"7beb0333.a55bac","type":"inject","z":"17f634f4.e4bc9b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"7","payloadType":"num","x":170,"y":100,"wires":[["d89491c3.793a3"]]},{"id":"a8db47cf.58ba18","type":"inject","z":"17f634f4.e4bc9b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"8","payloadType":"num","x":170,"y":180,"wires":[["d89491c3.793a3"]]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"1c09dc33.934504","type":"comment","z":"166069bc.648516","name":"Use JSONata expression for switch value","info":"Switch node can use JSONata expression for calculating complex switch value.","x":200,"y":60,"wires":[]},{"id":"c7ca4974.d638f8","type":"switch","z":"166069bc.648516","name":"","property":"(payload % 2) = 0","propertyType":"jsonata","rules":[{"t":"true"},{"t":"false"}],"checkall":"false","repair":false,"outputs":2,"x":290,"y":140,"wires":[["ac921b1d.c0dbe8"],["89adcfcc.53d6d"]]},{"id":"ab8972e0.e98b7","type":"debug","z":"166069bc.648516","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":590,"y":100,"wires":[]},{"id":"89adcfcc.53d6d","type":"change","z":"166069bc.648516","name":"Odd","rules":[{"t":"set","p":"payload","pt":"msg","to":"odd","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":180,"wires":[["426c68cf.64f0e8"]]},{"id":"ac921b1d.c0dbe8","type":"change","z":"166069bc.648516","name":"Even","rules":[{"t":"set","p":"payload","pt":"msg","to":"even","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":100,"wires":[["ab8972e0.e98b7"]]},{"id":"426c68cf.64f0e8","type":"debug","z":"166069bc.648516","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":590,"y":180,"wires":[]},{"id":"63377f1c.2fbfc","type":"inject","z":"166069bc.648516","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"7","payloadType":"num","x":150,"y":100,"wires":[["c7ca4974.d638f8"]]},{"id":"ea2fa596.ff1638","type":"inject","z":"166069bc.648516","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"8","payloadType":"num","x":150,"y":180,"wires":[["c7ca4974.d638f8"]]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"eaf91a6b.a55da8","type":"comment","z":"73a69428.bf4fec","name":"Advanced mustache example","info":"Template node can create a string value using [Mustache](http://mustache.github.io/mustache.5.html) syntax.","x":200,"y":80,"wires":[]},{"id":"61fbfe34.14a02","type":"inject","z":"73a69428.bf4fec","name":"Price of fruits","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Fruits","payload":"[{\"name\":\"apple\",\"price\":100},{\"name\":\"orange\",\"price\":80},{\"name\":\"banana\",\"price\":210}]","payloadType":"json","x":210,"y":140,"wires":[["bf0cb02.d8e4b5"]]},{"id":"bf0cb02.d8e4b5","type":"template","z":"73a69428.bf4fec","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"# Price List of {{topic}}\n\n{{! outputs list of prices }}\n{{#payload}}\n- {{name}}: {{price}}\n{{/payload}}\n","output":"str","x":380,"y":140,"wires":[["153eb0ff.5622df"]]},{"id":"153eb0ff.5622df","type":"debug","z":"73a69428.bf4fec","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":550,"y":140,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"fe821493.2e0e28","type":"comment","z":"1e6bd604.afc8fa","name":"Parse result as JSON","info":"Template node can create a string value using [Mustache](http://mustache.github.io/mustache.5.html) syntax.\nIf `Partsed JSON` output is selected, the created string is parsed as JSON format and JavaScript object is send as an output payload value.","x":160,"y":60,"wires":[]},{"id":"931f94e8.592cd8","type":"inject","z":"1e6bd604.afc8fa","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"message","payload":"Hello, World!","payloadType":"str","x":220,"y":120,"wires":[["bb2b0dad.b24b5"]]},{"id":"bb2b0dad.b24b5","type":"template","z":"1e6bd604.afc8fa","name":"JSON template","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n    \"key\" : \"{{topic}}\",\n    \"value\": \"{{payload}}\"\n}\n","output":"json","x":440,"y":120,"wires":[["baf2e48.2b97418"]]},{"id":"baf2e48.2b97418","type":"debug","z":"1e6bd604.afc8fa","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":630,"y":120,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"6ad06659.a1e4e8","type":"comment","z":"369312e8.ba755e","name":"Parse result as YAML","info":"Template node can create a string value using [Mustache](http://mustache.github.io/mustache.5.html) syntax.\nIf `Partsed YAML` output is selected, the created string is parsed as YAML format and JavaScript object is send as an output payload value.","x":180,"y":60,"wires":[]},{"id":"8d6be9a2.c3fa58","type":"inject","z":"369312e8.ba755e","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"message","payload":"Hello, World!","payloadType":"str","x":240,"y":120,"wires":[["69369c3.4a98164"]]},{"id":"69369c3.4a98164","type":"template","z":"369312e8.ba755e","name":"YAML template","field":"payload","fieldType":"msg","format":"yaml","syntax":"mustache","template":"key: {{topic}}\nvalue: {{payload}}","output":"yaml","x":460,"y":120,"wires":[["11fb2934.f5de27"]]},{"id":"11fb2934.f5de27","type":"debug","z":"369312e8.ba755e","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":650,"y":120,"wires":[]}]
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
[{"id":"ec5a531b.68b65","type":"inject","z":"90acd374.2feda","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":100,"wires":[["cb5e0c78.4bf3d"]]},{"id":"1b0f8c3e.1fd7e4","type":"debug","z":"90acd374.2feda","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":490,"y":100,"wires":[]},{"id":"cb5e0c78.4bf3d","type":"trigger","z":"90acd374.2feda","name":"","op1":"1","op2":"0","op1type":"str","op2type":"str","duration":"2","extend":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":320,"y":100,"wires":[["1b0f8c3e.1fd7e4"]]},{"id":"4e5bf6b2.b4dd58","type":"comment","z":"90acd374.2feda","name":"Oputputs two values with interval","info":"Outputs 1. Then output 0 after a certain period of time.\n\n*This could be used, for example, to blink an LED attached to a Raspberry Pi GPIO pin.*","x":170,"y":40,"wires":[]}]
 | 
			
		||||
@@ -1,135 +0,0 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "id": "9b2d7459.8dd598",
 | 
			
		||||
        "type": "http in",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "url": "/hello-param/:name",
 | 
			
		||||
        "method": "get",
 | 
			
		||||
        "upload": false,
 | 
			
		||||
        "swaggerDoc": "",
 | 
			
		||||
        "x": 290,
 | 
			
		||||
        "y": 900,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "83753c80.5e271"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "7fe50f46.46209",
 | 
			
		||||
        "type": "http response",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "statusCode": "",
 | 
			
		||||
        "headers": {},
 | 
			
		||||
        "x": 590,
 | 
			
		||||
        "y": 900,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "6e88d2b.828a92c",
 | 
			
		||||
        "type": "comment",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "Handle URL parameters in an HTTP endpoint",
 | 
			
		||||
        "info": "Named path parameters (e.g. `:name`) in the URL property can be used to identify parts of the path that can vary between requests.\n\nThe `msg.req.params` property is an object of key/value pairs for each path parameter.",
 | 
			
		||||
        "x": 290,
 | 
			
		||||
        "y": 860,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "214bc398.b3482c",
 | 
			
		||||
        "type": "http request",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "method": "GET",
 | 
			
		||||
        "ret": "txt",
 | 
			
		||||
        "paytoqs": "ignore",
 | 
			
		||||
        "url": "http://localhost:1880/hello-param/Nick",
 | 
			
		||||
        "tls": "",
 | 
			
		||||
        "persist": false,
 | 
			
		||||
        "proxy": "",
 | 
			
		||||
        "authType": "",
 | 
			
		||||
        "x": 390,
 | 
			
		||||
        "y": 1000,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "70c0eba4.5f0dc4"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "70c0eba4.5f0dc4",
 | 
			
		||||
        "type": "debug",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "active": true,
 | 
			
		||||
        "tosidebar": true,
 | 
			
		||||
        "console": false,
 | 
			
		||||
        "tostatus": false,
 | 
			
		||||
        "complete": "false",
 | 
			
		||||
        "statusVal": "",
 | 
			
		||||
        "statusType": "auto",
 | 
			
		||||
        "x": 550,
 | 
			
		||||
        "y": 1000,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "83753c80.5e271",
 | 
			
		||||
        "type": "template",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "page",
 | 
			
		||||
        "field": "payload",
 | 
			
		||||
        "fieldType": "msg",
 | 
			
		||||
        "format": "html",
 | 
			
		||||
        "syntax": "mustache",
 | 
			
		||||
        "template": "<html>\n    <head></head>\n    <body>\n        <h1>Hello {{req.params.name}}!</h1>\n    </body>\n</html>",
 | 
			
		||||
        "output": "str",
 | 
			
		||||
        "x": 470,
 | 
			
		||||
        "y": 900,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "7fe50f46.46209"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "89523bfe.6e5c18",
 | 
			
		||||
        "type": "inject",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "props": [
 | 
			
		||||
            {
 | 
			
		||||
                "p": "payload"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "p": "topic",
 | 
			
		||||
                "vt": "str"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "repeat": "",
 | 
			
		||||
        "crontab": "",
 | 
			
		||||
        "once": false,
 | 
			
		||||
        "onceDelay": 0.1,
 | 
			
		||||
        "topic": "",
 | 
			
		||||
        "payload": "",
 | 
			
		||||
        "payloadType": "date",
 | 
			
		||||
        "x": 240,
 | 
			
		||||
        "y": 1000,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "214bc398.b3482c"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "6276a6cd.5c3b18",
 | 
			
		||||
        "type": "comment",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "Send HTTP GET request: http://localhost:1880/hello-param/Nick",
 | 
			
		||||
        "info": "`http request` node can be used to send **HTTP GET** request.",
 | 
			
		||||
        "x": 350,
 | 
			
		||||
        "y": 960,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    }
 | 
			
		||||
]
 | 
			
		||||
@@ -1,126 +0,0 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "id": "59760c9d.172d94",
 | 
			
		||||
        "type": "http in",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "url": "/hello-upload",
 | 
			
		||||
        "method": "post",
 | 
			
		||||
        "upload": true,
 | 
			
		||||
        "swaggerDoc": "",
 | 
			
		||||
        "x": 270,
 | 
			
		||||
        "y": 1980,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "e30073f3.ad429"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "d0b3e8b4.2cfde8",
 | 
			
		||||
        "type": "comment",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "Post file to a flow",
 | 
			
		||||
        "info": "The `HTTP In` node can listen for POST requests for file upload.  Information for uploaded files can be accessed from `msg.req.files`.\n",
 | 
			
		||||
        "x": 200,
 | 
			
		||||
        "y": 1760,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "a888a043.ebb72",
 | 
			
		||||
        "type": "http in",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "url": "/hello-upload",
 | 
			
		||||
        "method": "get",
 | 
			
		||||
        "upload": false,
 | 
			
		||||
        "swaggerDoc": "",
 | 
			
		||||
        "x": 270,
 | 
			
		||||
        "y": 1880,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "14b950b.0ed5aaf"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "2ccb67c7.02c568",
 | 
			
		||||
        "type": "http response",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "statusCode": "",
 | 
			
		||||
        "headers": {},
 | 
			
		||||
        "x": 590,
 | 
			
		||||
        "y": 1880,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "14b950b.0ed5aaf",
 | 
			
		||||
        "type": "template",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "field": "payload",
 | 
			
		||||
        "fieldType": "msg",
 | 
			
		||||
        "format": "html",
 | 
			
		||||
        "syntax": "mustache",
 | 
			
		||||
        "template": "<html>\n    <head></head>\n    <body>\n        <form method=\"POST\" enctype=\"multipart/form-data\">\n            <div>\n                <label for=\"file\">Select file to upload:</label>\n                <input type=\"file\" id=\"file\" name=\"file\"/>\n            </div>\n            <div>\n                <button type=\"submit\">OK</button>\n            </div>\n        </form>\n    </body>\n</html>",
 | 
			
		||||
        "output": "str",
 | 
			
		||||
        "x": 440,
 | 
			
		||||
        "y": 1880,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "2ccb67c7.02c568"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "54f2edb5.62fca4",
 | 
			
		||||
        "type": "http response",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "statusCode": "",
 | 
			
		||||
        "headers": {},
 | 
			
		||||
        "x": 590,
 | 
			
		||||
        "y": 1980,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "e30073f3.ad429",
 | 
			
		||||
        "type": "template",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "field": "payload",
 | 
			
		||||
        "fieldType": "msg",
 | 
			
		||||
        "format": "html",
 | 
			
		||||
        "syntax": "mustache",
 | 
			
		||||
        "template": "<html>\n    <head></head>\n    <body>\n        <h1>Hello {{req.files.0.originalname}}</h1>\n    </body>\n</html>",
 | 
			
		||||
        "output": "str",
 | 
			
		||||
        "x": 440,
 | 
			
		||||
        "y": 1980,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "54f2edb5.62fca4"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "6c3c1bf.48cc2e4",
 | 
			
		||||
        "type": "comment",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "Create HTTP page for file upload: open http://localhost:1880/hello-upload \\n from Web browser.  Then upload a file.",
 | 
			
		||||
        "info": "",
 | 
			
		||||
        "x": 440,
 | 
			
		||||
        "y": 1820,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "c6242caa.8ec63",
 | 
			
		||||
        "type": "comment",
 | 
			
		||||
        "z": "d41b4dd3.ecd6a",
 | 
			
		||||
        "name": "Serve POST request for file upload",
 | 
			
		||||
        "info": "",
 | 
			
		||||
        "x": 320,
 | 
			
		||||
        "y": 1940,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    }
 | 
			
		||||
]
 | 
			
		||||
@@ -1,92 +0,0 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "id": "73874b9e.d985b4",
 | 
			
		||||
        "type": "tcp in",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "server": "client",
 | 
			
		||||
        "host": "localhost",
 | 
			
		||||
        "port": "1881",
 | 
			
		||||
        "datamode": "single",
 | 
			
		||||
        "datatype": "utf8",
 | 
			
		||||
        "newline": "",
 | 
			
		||||
        "topic": "",
 | 
			
		||||
        "base64": false,
 | 
			
		||||
        "x": 230,
 | 
			
		||||
        "y": 240,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "ce332113.d2c31"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "a4bd9948.dfeb58",
 | 
			
		||||
        "type": "tcp out",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "host": "localhost",
 | 
			
		||||
        "port": "1881",
 | 
			
		||||
        "beserver": "server",
 | 
			
		||||
        "base64": false,
 | 
			
		||||
        "end": true,
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "x": 390,
 | 
			
		||||
        "y": 180,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "9f80f9c7.1e3c98",
 | 
			
		||||
        "type": "inject",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "props": [
 | 
			
		||||
            {
 | 
			
		||||
                "p": "payload"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "p": "topic",
 | 
			
		||||
                "vt": "str"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "repeat": "",
 | 
			
		||||
        "crontab": "",
 | 
			
		||||
        "once": false,
 | 
			
		||||
        "onceDelay": 0.1,
 | 
			
		||||
        "topic": "",
 | 
			
		||||
        "payload": "Hello, World!",
 | 
			
		||||
        "payloadType": "str",
 | 
			
		||||
        "x": 210,
 | 
			
		||||
        "y": 180,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "a4bd9948.dfeb58"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "ce332113.d2c31",
 | 
			
		||||
        "type": "debug",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "active": true,
 | 
			
		||||
        "tosidebar": true,
 | 
			
		||||
        "console": false,
 | 
			
		||||
        "tostatus": false,
 | 
			
		||||
        "complete": "false",
 | 
			
		||||
        "statusVal": "",
 | 
			
		||||
        "statusType": "auto",
 | 
			
		||||
        "x": 410,
 | 
			
		||||
        "y": 240,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "711ed10d.1d765",
 | 
			
		||||
        "type": "comment",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "Connect to TCP out server",
 | 
			
		||||
        "info": "`TCP in` node can connect to network server using tcp protocol.  `TCP out` node can serve a network server using tcp procol.\n",
 | 
			
		||||
        "x": 190,
 | 
			
		||||
        "y": 120,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    }
 | 
			
		||||
]
 | 
			
		||||
@@ -1,92 +0,0 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "id": "511f8208.c4c20c",
 | 
			
		||||
        "type": "tcp in",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "server": "server",
 | 
			
		||||
        "host": "localhost",
 | 
			
		||||
        "port": "1882",
 | 
			
		||||
        "datamode": "single",
 | 
			
		||||
        "datatype": "utf8",
 | 
			
		||||
        "newline": "",
 | 
			
		||||
        "topic": "",
 | 
			
		||||
        "base64": false,
 | 
			
		||||
        "x": 230,
 | 
			
		||||
        "y": 460,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "6b8be121.32be9"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "ec0bc4aa.b3c828",
 | 
			
		||||
        "type": "tcp out",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "host": "localhost",
 | 
			
		||||
        "port": "1882",
 | 
			
		||||
        "beserver": "client",
 | 
			
		||||
        "base64": false,
 | 
			
		||||
        "end": true,
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "x": 390,
 | 
			
		||||
        "y": 400,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "17cf7d56.9efb03",
 | 
			
		||||
        "type": "inject",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "props": [
 | 
			
		||||
            {
 | 
			
		||||
                "p": "payload"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "p": "topic",
 | 
			
		||||
                "vt": "str"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "repeat": "",
 | 
			
		||||
        "crontab": "",
 | 
			
		||||
        "once": false,
 | 
			
		||||
        "onceDelay": 0.1,
 | 
			
		||||
        "topic": "",
 | 
			
		||||
        "payload": "Hello, World!",
 | 
			
		||||
        "payloadType": "str",
 | 
			
		||||
        "x": 210,
 | 
			
		||||
        "y": 400,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "ec0bc4aa.b3c828"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "6b8be121.32be9",
 | 
			
		||||
        "type": "debug",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "active": true,
 | 
			
		||||
        "tosidebar": true,
 | 
			
		||||
        "console": false,
 | 
			
		||||
        "tostatus": false,
 | 
			
		||||
        "complete": "false",
 | 
			
		||||
        "statusVal": "",
 | 
			
		||||
        "statusType": "auto",
 | 
			
		||||
        "x": 410,
 | 
			
		||||
        "y": 460,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "76196665.7c23e8",
 | 
			
		||||
        "type": "comment",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "Connect to TCP in server",
 | 
			
		||||
        "info": "`TCP out` node can connect to network server using tcp protocol.  `TCP in` node can serve a network server using tcp procol.\n",
 | 
			
		||||
        "x": 190,
 | 
			
		||||
        "y": 340,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    }
 | 
			
		||||
]
 | 
			
		||||
@@ -1,174 +0,0 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "id": "d9a91d7e.05f0d",
 | 
			
		||||
        "type": "tcp in",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "server": "server",
 | 
			
		||||
        "host": "localhost",
 | 
			
		||||
        "port": "1883",
 | 
			
		||||
        "datamode": "stream",
 | 
			
		||||
        "datatype": "utf8",
 | 
			
		||||
        "newline": "¥n",
 | 
			
		||||
        "topic": "",
 | 
			
		||||
        "base64": false,
 | 
			
		||||
        "x": 230,
 | 
			
		||||
        "y": 740,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "3871d8af.25e208"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "9fa8f09d.7591b",
 | 
			
		||||
        "type": "inject",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "props": [
 | 
			
		||||
            {
 | 
			
		||||
                "p": "payload"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "p": "topic",
 | 
			
		||||
                "vt": "str"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "repeat": "",
 | 
			
		||||
        "crontab": "",
 | 
			
		||||
        "once": false,
 | 
			
		||||
        "onceDelay": 0.1,
 | 
			
		||||
        "topic": "",
 | 
			
		||||
        "payload": "\"Hello, World!¥n\"",
 | 
			
		||||
        "payloadType": "jsonata",
 | 
			
		||||
        "x": 190,
 | 
			
		||||
        "y": 640,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "948a8410.ab0a08"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "33df08b.753e9f8",
 | 
			
		||||
        "type": "debug",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "active": true,
 | 
			
		||||
        "tosidebar": true,
 | 
			
		||||
        "console": false,
 | 
			
		||||
        "tostatus": false,
 | 
			
		||||
        "complete": "false",
 | 
			
		||||
        "statusVal": "",
 | 
			
		||||
        "statusType": "auto",
 | 
			
		||||
        "x": 710,
 | 
			
		||||
        "y": 640,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "948a8410.ab0a08",
 | 
			
		||||
        "type": "tcp request",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "server": "localhost",
 | 
			
		||||
        "port": "1883",
 | 
			
		||||
        "out": "char",
 | 
			
		||||
        "splitc": "\\n",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "x": 350,
 | 
			
		||||
        "y": 640,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "f6d6be6a.efb4c"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "fbd442d8.cb273",
 | 
			
		||||
        "type": "tcp out",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "host": "",
 | 
			
		||||
        "port": "",
 | 
			
		||||
        "beserver": "reply",
 | 
			
		||||
        "base64": false,
 | 
			
		||||
        "end": false,
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "x": 530,
 | 
			
		||||
        "y": 740,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "3871d8af.25e208",
 | 
			
		||||
        "type": "change",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "set result",
 | 
			
		||||
        "rules": [
 | 
			
		||||
            {
 | 
			
		||||
                "t": "set",
 | 
			
		||||
                "p": "payload",
 | 
			
		||||
                "pt": "msg",
 | 
			
		||||
                "to": "\"Received: \" & payload & \"\b\\n\"",
 | 
			
		||||
                "tot": "jsonata"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "action": "",
 | 
			
		||||
        "property": "",
 | 
			
		||||
        "from": "",
 | 
			
		||||
        "to": "",
 | 
			
		||||
        "reg": false,
 | 
			
		||||
        "x": 400,
 | 
			
		||||
        "y": 740,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "fbd442d8.cb273"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "f6d6be6a.efb4c",
 | 
			
		||||
        "type": "function",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "Buffer to String",
 | 
			
		||||
        "func": "msg.payload = msg.payload.toString();\nreturn msg;",
 | 
			
		||||
        "outputs": 1,
 | 
			
		||||
        "noerr": 0,
 | 
			
		||||
        "initialize": "",
 | 
			
		||||
        "finalize": "",
 | 
			
		||||
        "x": 540,
 | 
			
		||||
        "y": 640,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "33df08b.753e9f8"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "e1412987.91dcc8",
 | 
			
		||||
        "type": "comment",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "Send reply to client of TCP connection",
 | 
			
		||||
        "info": "Input message from `TCP in` node may be passed to `TCP out` node to return a reply to client.\n",
 | 
			
		||||
        "x": 230,
 | 
			
		||||
        "y": 580,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "5f43905f.2425d",
 | 
			
		||||
        "type": "comment",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "↓ Accept request",
 | 
			
		||||
        "info": "",
 | 
			
		||||
        "x": 220,
 | 
			
		||||
        "y": 700,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "a6f57329.c87c6",
 | 
			
		||||
        "type": "comment",
 | 
			
		||||
        "z": "cfd9159c.6f73e8",
 | 
			
		||||
        "name": "↓ Reply result",
 | 
			
		||||
        "info": "",
 | 
			
		||||
        "x": 550,
 | 
			
		||||
        "y": 700,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    }
 | 
			
		||||
]
 | 
			
		||||
@@ -1,92 +0,0 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "id": "73279b5.5151664",
 | 
			
		||||
        "type": "udp in",
 | 
			
		||||
        "z": "92236e19.9e4cb",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "iface": "",
 | 
			
		||||
        "port": "1881",
 | 
			
		||||
        "ipv": "udp4",
 | 
			
		||||
        "multicast": "false",
 | 
			
		||||
        "group": "",
 | 
			
		||||
        "datatype": "utf8",
 | 
			
		||||
        "x": 220,
 | 
			
		||||
        "y": 220,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "d98b60d3.7331e"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "fb19b98f.d5aa58",
 | 
			
		||||
        "type": "udp out",
 | 
			
		||||
        "z": "92236e19.9e4cb",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "addr": "localhost",
 | 
			
		||||
        "iface": "",
 | 
			
		||||
        "port": "1881",
 | 
			
		||||
        "ipv": "udp4",
 | 
			
		||||
        "outport": "",
 | 
			
		||||
        "base64": false,
 | 
			
		||||
        "multicast": "false",
 | 
			
		||||
        "x": 410,
 | 
			
		||||
        "y": 160,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "33f18897.35d5b8",
 | 
			
		||||
        "type": "inject",
 | 
			
		||||
        "z": "92236e19.9e4cb",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "props": [
 | 
			
		||||
            {
 | 
			
		||||
                "p": "payload"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "p": "topic",
 | 
			
		||||
                "vt": "str"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "repeat": "",
 | 
			
		||||
        "crontab": "",
 | 
			
		||||
        "once": false,
 | 
			
		||||
        "onceDelay": 0.1,
 | 
			
		||||
        "topic": "",
 | 
			
		||||
        "payload": "Hello, World!",
 | 
			
		||||
        "payloadType": "str",
 | 
			
		||||
        "x": 230,
 | 
			
		||||
        "y": 160,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "fb19b98f.d5aa58"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "d98b60d3.7331e",
 | 
			
		||||
        "type": "debug",
 | 
			
		||||
        "z": "92236e19.9e4cb",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "active": true,
 | 
			
		||||
        "tosidebar": true,
 | 
			
		||||
        "console": false,
 | 
			
		||||
        "tostatus": false,
 | 
			
		||||
        "complete": "false",
 | 
			
		||||
        "statusVal": "",
 | 
			
		||||
        "statusType": "auto",
 | 
			
		||||
        "x": 370,
 | 
			
		||||
        "y": 220,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "f86495aa.8a9848",
 | 
			
		||||
        "type": "comment",
 | 
			
		||||
        "z": "92236e19.9e4cb",
 | 
			
		||||
        "name": "Transfer data using UDP protocol",
 | 
			
		||||
        "info": "`UDP in` node can be used to receive data from `UDP out` node using UDP protocol.",
 | 
			
		||||
        "x": 230,
 | 
			
		||||
        "y": 100,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    }
 | 
			
		||||
]
 | 
			
		||||
@@ -1,96 +0,0 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "id": "13410716.69c9d9",
 | 
			
		||||
        "type": "websocket in",
 | 
			
		||||
        "z": "a8360b47.074ec8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "server": "89db22b6.9aa36",
 | 
			
		||||
        "client": "",
 | 
			
		||||
        "x": 280,
 | 
			
		||||
        "y": 180,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "c2541f10.59544"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "c2541f10.59544",
 | 
			
		||||
        "type": "debug",
 | 
			
		||||
        "z": "a8360b47.074ec8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "active": true,
 | 
			
		||||
        "tosidebar": true,
 | 
			
		||||
        "console": false,
 | 
			
		||||
        "tostatus": false,
 | 
			
		||||
        "complete": "false",
 | 
			
		||||
        "statusVal": "",
 | 
			
		||||
        "statusType": "auto",
 | 
			
		||||
        "x": 430,
 | 
			
		||||
        "y": 180,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "6788839e.04576c",
 | 
			
		||||
        "type": "inject",
 | 
			
		||||
        "z": "a8360b47.074ec8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "props": [
 | 
			
		||||
            {
 | 
			
		||||
                "p": "payload"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "p": "topic",
 | 
			
		||||
                "vt": "str"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "repeat": "",
 | 
			
		||||
        "crontab": "",
 | 
			
		||||
        "once": false,
 | 
			
		||||
        "onceDelay": 0.1,
 | 
			
		||||
        "topic": "",
 | 
			
		||||
        "payload": "Hello, World!",
 | 
			
		||||
        "payloadType": "str",
 | 
			
		||||
        "x": 290,
 | 
			
		||||
        "y": 120,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "438c232a.06c2cc"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "438c232a.06c2cc",
 | 
			
		||||
        "type": "websocket out",
 | 
			
		||||
        "z": "a8360b47.074ec8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "server": "",
 | 
			
		||||
        "client": "63620788.bda128",
 | 
			
		||||
        "x": 500,
 | 
			
		||||
        "y": 120,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "c88f97a9.4410f8",
 | 
			
		||||
        "type": "comment",
 | 
			
		||||
        "z": "a8360b47.074ec8",
 | 
			
		||||
        "name": "Connect to websocket in server",
 | 
			
		||||
        "info": "`websocket out` node can connect to web socket server.  `websocket in` node can serve a web socket server.\n",
 | 
			
		||||
        "x": 290,
 | 
			
		||||
        "y": 80,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "89db22b6.9aa36",
 | 
			
		||||
        "type": "websocket-listener",
 | 
			
		||||
        "path": "/ws1",
 | 
			
		||||
        "wholemsg": "false"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "63620788.bda128",
 | 
			
		||||
        "type": "websocket-client",
 | 
			
		||||
        "path": "ws://localhost:1880/ws1",
 | 
			
		||||
        "tls": "",
 | 
			
		||||
        "wholemsg": "false"
 | 
			
		||||
    }
 | 
			
		||||
]
 | 
			
		||||
@@ -1,96 +0,0 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "id": "759c0b2b.8a0484",
 | 
			
		||||
        "type": "websocket in",
 | 
			
		||||
        "z": "a8360b47.074ec8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "server": "",
 | 
			
		||||
        "client": "1d80bd86.93f372",
 | 
			
		||||
        "x": 340,
 | 
			
		||||
        "y": 520,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "1f7a7454.cb65ec"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "1f7a7454.cb65ec",
 | 
			
		||||
        "type": "debug",
 | 
			
		||||
        "z": "a8360b47.074ec8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "active": true,
 | 
			
		||||
        "tosidebar": true,
 | 
			
		||||
        "console": false,
 | 
			
		||||
        "tostatus": false,
 | 
			
		||||
        "complete": "false",
 | 
			
		||||
        "statusVal": "",
 | 
			
		||||
        "statusType": "auto",
 | 
			
		||||
        "x": 550,
 | 
			
		||||
        "y": 520,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "aa2fe781.e92b28",
 | 
			
		||||
        "type": "inject",
 | 
			
		||||
        "z": "a8360b47.074ec8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "props": [
 | 
			
		||||
            {
 | 
			
		||||
                "p": "payload"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "p": "topic",
 | 
			
		||||
                "vt": "str"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "repeat": "",
 | 
			
		||||
        "crontab": "",
 | 
			
		||||
        "once": false,
 | 
			
		||||
        "onceDelay": 0.1,
 | 
			
		||||
        "topic": "",
 | 
			
		||||
        "payload": "Goodbye, World!",
 | 
			
		||||
        "payloadType": "str",
 | 
			
		||||
        "x": 300,
 | 
			
		||||
        "y": 460,
 | 
			
		||||
        "wires": [
 | 
			
		||||
            [
 | 
			
		||||
                "f8bdbc9b.d82dd"
 | 
			
		||||
            ]
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "f8bdbc9b.d82dd",
 | 
			
		||||
        "type": "websocket out",
 | 
			
		||||
        "z": "a8360b47.074ec8",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "server": "40bd4295.3e4ecc",
 | 
			
		||||
        "client": "",
 | 
			
		||||
        "x": 460,
 | 
			
		||||
        "y": 460,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "c3fbc602.2e7f08",
 | 
			
		||||
        "type": "comment",
 | 
			
		||||
        "z": "a8360b47.074ec8",
 | 
			
		||||
        "name": "Connect to websocket out server",
 | 
			
		||||
        "info": "`websocket out` node can connect to web socket server.  `websocket in` node can serve a web socket server.\n",
 | 
			
		||||
        "x": 290,
 | 
			
		||||
        "y": 420,
 | 
			
		||||
        "wires": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "1d80bd86.93f372",
 | 
			
		||||
        "type": "websocket-client",
 | 
			
		||||
        "path": "ws://localhost:1880/ws2",
 | 
			
		||||
        "tls": "",
 | 
			
		||||
        "wholemsg": "false"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "id": "40bd4295.3e4ecc",
 | 
			
		||||
        "type": "websocket-listener",
 | 
			
		||||
        "path": "/ws2",
 | 
			
		||||
        "wholemsg": "false"
 | 
			
		||||
    }
 | 
			
		||||
]
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user