UNPKG

@wearemothership/socket.io-stream

Version:
288 lines (240 loc) 6.5 kB
var util = require('util'); var EventEmitter = require('events').EventEmitter; var bind = require('component-bind'); var IOStream = require('./iostream'); var parser = require('./parser'); var debug = require('debug')('socket.io-stream:socket'); var emit = EventEmitter.prototype.emit; var on = EventEmitter.prototype.on; var slice = Array.prototype.slice; exports = module.exports = Socket; /** * Base event name for messaging. * * @api public */ exports.event = '$stream'; exports.events = [ 'error', 'newListener', 'removeListener' ]; util.inherits(Socket, EventEmitter); /** * Bidirectional stream socket which wraps Socket.IO. * * @param {socket.io#Socket} socket.io * @api public */ function Socket(sio, options) { if (!(this instanceof Socket)) { return new Socket(sio, options); } EventEmitter.call(this); options = options || {}; this.sio = sio; this.forceBase64 = !!options.forceBase64; this.streams = {}; this.encoder = new parser.Encoder(); this.decoder = new parser.Decoder(); var eventName = exports.event; sio.on(eventName, bind(this, emit)); sio.on(eventName + '-read', bind(this, '_onread')); sio.on(eventName + '-write', bind(this, '_onwrite')); sio.on(eventName + '-end', bind(this, '_onend')); sio.on(eventName + '-error', bind(this, '_onerror')); sio.on('error', bind(this, emit, 'error')); sio.on('disconnect', bind(this, '_ondisconnect')); this.encoder.on('stream', bind(this, '_onencode')); this.decoder.on('stream', bind(this, '_ondecode')); } /** * Original emit function. * * @api private */ Socket.prototype.$emit = emit; /** * Emits streams to this corresponding server/client. * * @return {Socket} self * @api public */ Socket.prototype.emit = function(type) { if (~exports.events.indexOf(type)) { return emit.apply(this, arguments); } this._stream.apply(this, arguments); return this; }; Socket.prototype.on = function(type, listener) { if (~exports.events.indexOf(type)) { return on.apply(this, arguments); } this._onstream(type, listener); return this; }; /** * Sends a new stream request. * * @param {String} event type * @api private */ Socket.prototype._stream = function(type) { debug('sending new streams'); var self = this; var args = slice.call(arguments, 1); var ack = args[args.length - 1]; if ('function' == typeof ack) { args[args.length - 1] = function() { var args = slice.call(arguments); args = self.decoder.decode(args); ack.apply(this, args); }; } args = this.encoder.encode(args); var sio = this.sio; sio.emit.apply(sio, [exports.event, type].concat(args)); }; /** * Notifies the read event. * * @api private */ Socket.prototype._read = function(id, size) { this.sio.emit(exports.event + '-read', id, size); }; /** * Requests to write a chunk. * * @api private */ Socket.prototype._write = function(id, chunk, encoding, callback) { if (Buffer.isBuffer(chunk)) { if (this.forceBase64) { encoding = 'base64'; chunk = chunk.toString(encoding); } else if (!global.Buffer) { // socket.io can't handle Buffer when using browserify. if (chunk.toArrayBuffer) { chunk = chunk.toArrayBuffer(); } else { chunk = chunk.buffer; } } } this.sio.emit(exports.event + '-write', id, chunk, encoding, callback); }; Socket.prototype._end = function(id) { this.sio.emit(exports.event + '-end', id); }; Socket.prototype._error = function(id, err) { this.sio.emit(exports.event + '-error', id, err.message || err); }; /** * Handles a new stream request. * * @param {String} event type * @param {Function} listener * * @api private */ Socket.prototype._onstream = function(type, listener) { if ('function' != typeof listener) { throw TypeError('listener must be a function'); } function onstream() { debug('new streams'); var self = this; var args = slice.call(arguments); var ack = args[args.length - 1]; if ('function' == typeof ack) { args[args.length - 1] = function() { var args = slice.call(arguments); args = self.encoder.encode(args); ack.apply(this, args); }; } args = this.decoder.decode(args); listener.apply(this, args); } // for removeListener onstream.listener = listener; on.call(this, type, onstream); }; Socket.prototype._onread = function(id, size) { debug('read: "%s"', id); var stream = this.streams[id]; if (stream) { stream._onread(size); } else { debug('ignore invalid stream id'); } }; Socket.prototype._onwrite = function(id, chunk, encoding, callback) { debug('write: "%s"', id); var stream = this.streams[id]; if (!stream) { callback('invalid stream id: ' + id); return; } if (global.ArrayBuffer && chunk instanceof ArrayBuffer) { // make sure that chunk is a buffer for stream chunk = new Buffer(new Uint8Array(chunk)); } stream._onwrite(chunk, encoding, callback); }; Socket.prototype._onend = function(id) { debug('end: "%s"', id); var stream = this.streams[id]; if (!stream) { debug('ignore non-existent stream id: "%s"', id); return; } stream._end(); }; Socket.prototype._onerror = function(id, message) { debug('error: "%s", "%s"', id, message); var stream = this.streams[id]; if (!stream) { debug('invalid stream id: "%s"', id); return; } var err = new Error(message); err.remote = true; stream.emit('error', err); }; Socket.prototype._ondisconnect = function() { var stream; for (var id in this.streams) { stream = this.streams[id]; stream.destroy(); // Close streams when the underlaying // socket.io connection is closed (regardless why) stream.emit('close'); stream.emit('error', new Error('Connection aborted')); } }; Socket.prototype._onencode = function(stream) { if (stream.socket || stream.destroyed) { throw new Error('stream has already been sent.'); } var id = stream.id; if (this.streams[id]) { throw new Error('Encoded stream already exists: ' + id); } this.streams[id] = stream; stream.socket = this; }; Socket.prototype._ondecode = function(stream) { var id = stream.id; if (this.streams[id]) { this._error(id, new Error('Decoded stream already exists: ' + id)); return; } this.streams[id] = stream; stream.socket = this; }; Socket.prototype.cleanup = function(id) { delete this.streams[id]; };