Node-Red is pretty useful for wiring together IOF (Internet of Things) devices, but its currently tied to node.js. Here is a way to run it clientside (on a browser) for those things that aren't allowed to run node.js.
Situation
I had devices that could only run JS through a chrome-like browser. Yet I needed a way to quickly deploy similar workflows to these devices. Each device would also act as a hub for other hardware on the system, this other hardware was easiest to connect through node-red.
Thus node-red didn't need any UI, but it did need a way to run the given flows on a browser. The following needed to be done:
- Add a route to get node-red configuration for this device
- flows
 - credentials
 
 - Removing node-red's filesystem dependancy
 - Polyfilling any node.js modules node-red uses (that aren't browser compatible)
 
Node-Red
I didn't have to make too many changes to node-red itself to support this. I simply added a flag: settings.noFileSystem, and made sure any code that auto-loaded data from the filesystem on startup didn't get a chance to run if the flag was on. Thus in the browser, I would add this flag, but on the main node-red server, I would not.
(see the commit here)
Second, you can't set the active flow (since it's supposed to be loaded by the filesystem), so I needed a way to set that up too. This was quite easy as well.
    // red/nodes/flows.js
    setActiveFlow: function(flow) {
        activeFlow = flow;
    },
Finally, there was a problem with the credentials requiring way too many
var needsPermission = require("../api/auth").needsPermission;
While I was at it, I realize it would be nice to be able to use inject nodes in the browser as well, so I added an easy function to do so. (see the commit here)
    // Usage:
    nodeRedInject('node-name');
Browser Usage
To use node-red in the browser I needed to get the configuration, this basically means a bunch of flows to run.
Getting each flow was easy enough, added a route to the server, and do an Ajax call
/**
 * Gets a subflow
 *
 * @param flowId
 * @param callback
 * @returns subflow
 */
function getSubflow(flowId, callback) {
    jQuery.ajax('//' + window.location.hostname + '/subflow/' + flowId, {
        data: {},
    }).error(function(xhr) {
        return callback("Server Error: " + xhr.status + ". Can't get subflow(" + flowId + ")");
    }).done(function(subflow) {
        if (!_.isArray(subflow)) {
            return callback("Server Error: Invalid return");
        }
        return callback(null, subflow);
    });
}
Of course we can load more than one flow, so this waits for all the data to come back, and merges them all into one flow.
/**
 * Loads the given subflow Ids
 * @param flowIds
 */
function initSubflows(flowIds, callback) {
    var syncs = [];
    for (var i=0; i < flowIds.length; i++) {
        var flowId = flowIds[i];
        var defer = jQuery.Deferred();
        (function scope(defer, flowId) {
            getSubflow(flowId, function(err, subflow) {
                if (err) {
                    defer.resolve([]);
                    return console.error(err);
                }
                defer.resolve(subflow);
            });
        })(defer, flowId);
        syncs.push(defer.promise());
    }
    jQuery.when.apply(null, syncs).then(function() {
        var flowData = [];
        // Merge the subflows
        var args = Array.prototype.slice.call(arguments);
        for (var i in args) {
            flowData = flowData.concat(args[i]);
        }
        callback(null, flowData);
    });
}
Finally here is the way the entire thing is called. Note that the last callback is the one that loads the flows into node-red itself, starting it.
    // Load the flows that we need
    var flowIds = params.flows;
    if (flowIds) {
        flowIds = flowIds.split(",");
        initSubflows(flowIds,  function onSubflowData(err, flowData) {
            RED.loadFlowData(flowData);
        });
    }