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
JavaScript
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;
}