UNPKG

node-red-contrib-web-worldmap

Version:

A Node-RED node to provide a web page of a world map for plotting things on.

210 lines (193 loc) 7.05 kB
var Stream = require('stream').Stream; /** * StreamStack * ----------- * Turns low-level `Stream` objects into stackable stream, meant * to fill out your desired protocol stack. But also allow for * the protocol to be implemented independent of the underlying * transport. * An example overall stack could look like: * - net.Stream <- TCP Layer * - HttpRequestStack <- HTTP Layer * - `write()`s an HTTP request upstream * - Response comes back with 'gzip' transfer-encoding * - GzipDecoderStack <- Decoding Layer * - `.pipe()` into a 'fs.WriteStream' <- Save to a File */ function StreamStack(stream, events) { if (!(stream instanceof Stream)) { throw new Error("StreamStack expects an instance of 'Stream' as an argument!"); } if (!(this instanceof StreamStack)) { return new StreamStack(stream, events); } Stream.call(this); // If this is the first time the parent stream has been used in a // StreamStack, then a StackEmitter will need to be injected into the stream. if (!stream._stackEmitter) { StackEmitter.call(stream); } stream._stacks.push(this); // A reference to the parent stream for event handlers, etc. this.stream = stream; // TODO: Remove, if I can find a good reason to. events = events || {}; if (!('data' in events)) events.data = proxyEvent('data'); if (!('end' in events)) events.end = proxyEvent('end'); if (!('error' in events)) events.error = proxyEvent('error'); if (!('close' in events)) events.close = proxyEvent('close'); if (!('fd' in events)) events.fd = proxyEvent('fd'); if (!('drain' in events)) events.drain = proxyEvent('drain'); // If the StreamStack instance intends on intercepting events emitted from // the parent stream, then the handlers need to be passed as a second 'events' // object in the constructor. It takes care of attaching them to the parent // stream. Handlers are invoked in 'this' StreamStack instance. if (events) { this._stackEvents = {}; for (var ev in events) { this._stackEvents[ev] = events[ev].bind(this); stream.on(ev, this._stackEvents[ev]); } } } require('util').inherits(StreamStack, Stream); exports.StreamStack = StreamStack; // By default, just proxy all the standard ReadStream and WriteStream // functions upstream. If the StreamStack implementation needs to overwrite // or augment any of the behavior, then simply overwrite that function. // The most common is to augment the 'write()' function, such that the // passed data goes through some kind of filter before being passed to // the parent stream. StreamStack.prototype.write = function(buf, type) { return this.stream.write(buf, type); } StreamStack.prototype.end = function(buf, type) { if (buf) { this.write(buf, type); } return this.stream.end(); } StreamStack.prototype.pause = function() { if (this.stream.pause) { return this.stream.pause(); } else { return this.stream.emit('pause'); } } StreamStack.prototype.resume = function() { if (this.stream.resume) { return this.stream.resume(); } else { return this.stream.emit('resume'); } } StreamStack.prototype.destroy = function(error) { return this.stream.destroy(error); } // The 'cleanup()' function should be called after a StreamStack instance is // finished doing it's "thing", to cleanly allow another new StreamStack // instance to be attached to the parent Stream. StreamStack.prototype.cleanup = function() { // Remove 'this' from the parent Stream's '_stacks' Array var index = this.stream._stacks.indexOf(this); this.stream._stacks.splice(index, 1); // Set 'this.stream' to null. // If any events were binded through the constructor, they get unbinded here if (this._stackEvents) { for (var ev in this._stackEvents) { this.stream.removeListener(ev, this._stackEvents[ev]); } this._stackEvents = null; } // TODO: Maybe 'delete' instead? Is there any benefit? this.stream = null; } // By default, the 'readable' and 'writable' property lookups get proxied // to the parent stream. You can set the variables if needed, and to relinquish // control of the variable back upstream, set it to `undefined`. Object.defineProperty(StreamStack.prototype, "readable", { get: function() { if (this._readable != undefined) { return this._readable; } return this.stream.readable; }, set: function(value) { this._readable = value; }, enumerable: true }); Object.defineProperty(StreamStack.prototype, "writable", { get: function() { if (this._writable != undefined) { return this._writable; } return this.stream.writable; }, set: function(value) { this._writable = value; }, enumerable: true }); // Walks up the 'stream' properties until it finds and returns the top-most // stream. i.e. it gets the low-level stream this stack is based on. Object.defineProperty(StreamStack.prototype, "topStream", { get: function() { var rtn = this.stream; while (rtn.stream) { rtn = rtn.stream; } return rtn; }, enumerable: true }); // Stupid workaround. Attach a listener for the given 'eventName'. // The callback simply re-emits the events and all arguments on 'this'. function proxyEvent(eventName) { return function() { var args = [eventName]; args = args.concat(args.slice.call(arguments)); this.emit.apply(this, args); } } // Parent streams need to have StackEmitter called on them the first time a // StreamStack instance is attempting to use it. The __proto__ of the parent // stream will be injected with StackEmitter's prototype, to benefit from // the overwritten 'emit()' function. function StackEmitter() { // The Array that holds the active StreamStack instances on a parent Stream. this._stacks = []; // Get a reference to the original 'emit' function, since we're about to // monkey-patch with our own 'emit' function. this._origEmit = this.emit; // Mix-in the rest of the StackEmitter properties. for (var prop in StackEmitter.prototype) { this[prop] = StackEmitter.prototype[prop]; } } // A flag to indicate that the parent stream has already been injected. StackEmitter.prototype._stackEmitter = true; // The custom 'emit()' function is responsible for iterating through the list // of active StreamStack instances, and IFF the StreamStack instance didn't // pass a handler to the current event, it should re-emit on the child as well. StackEmitter.prototype.emit = function(eventName) { var stack; // Emit on the parent Stream first var rtn = this._origEmit.apply(this, arguments); // Next re-emit on all the active StreamStack instances (if any) for (var i=0, l=this._stacks.length; i<l; i++) { stack = this._stacks[i]; if (!stack._stackEvents || !(eventName in stack._stackEvents)) { if (!stack.emit.apply(stack, arguments)) { rtn = false; } } } return rtn; }