/*! binary.js build:0.2.1, development. Copyright(c) 2012 Eric Zhang MIT Licensed */ (function(exports){ var binaryFeatures = {}; binaryFeatures.useBlobBuilder = (function(){ try { new Blob([]); return false; } catch (e) { return true; } })(); binaryFeatures.useArrayBufferView = !binaryFeatures.useBlobBuilder && (function(){ try { return (new Blob([new Uint8Array([])])).size === 0; } catch (e) { return true; } })(); binaryFeatures.supportsBinaryWebsockets = (function(){ try { var wstest = new WebSocket('ws://null'); wstest.onerror = function(){}; if (typeof(wstest.binaryType) !== "undefined") { return true; } else { return false; } wstest.close(); wstest = null; } catch (e) { return false; } })(); exports.binaryFeatures = binaryFeatures; exports.BlobBuilder = window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder || window.BlobBuilder; function BufferBuilder(){ this._pieces = []; this._parts = []; } BufferBuilder.prototype.append = function(data) { if(typeof data === 'number') { this._pieces.push(data); } else { this.flush(); this._parts.push(data); } }; BufferBuilder.prototype.flush = function() { if (this._pieces.length > 0) { var buf = new Uint8Array(this._pieces); if(!binaryFeatures.useArrayBufferView) { buf = buf.buffer; } this._parts.push(buf); this._pieces = []; } }; BufferBuilder.prototype.getBuffer = function() { this.flush(); if(binaryFeatures.useBlobBuilder) { var builder = new BlobBuilder(); for(var i = 0, ii = this._parts.length; i < ii; i++) { builder.append(this._parts[i]); } return builder.getBlob(); } else { return new Blob(this._parts); } }; exports.BinaryPack = { unpack: function(data){ var unpacker = new Unpacker(data); return unpacker.unpack(); }, pack: function(data){ var packer = new Packer(); packer.pack(data); var buffer = packer.getBuffer(); return buffer; } }; function Unpacker (data){ // Data is ArrayBuffer this.index = 0; this.dataBuffer = data; this.dataView = new Uint8Array(this.dataBuffer); this.length = this.dataBuffer.byteLength; } Unpacker.prototype.unpack = function(){ var type = this.unpack_uint8(); if (type < 0x80){ var positive_fixnum = type; return positive_fixnum; } else if ((type ^ 0xe0) < 0x20){ var negative_fixnum = (type ^ 0xe0) - 0x20; return negative_fixnum; } var size; if ((size = type ^ 0xa0) <= 0x0f){ return this.unpack_raw(size); } else if ((size = type ^ 0xb0) <= 0x0f){ return this.unpack_string(size); } else if ((size = type ^ 0x90) <= 0x0f){ return this.unpack_array(size); } else if ((size = type ^ 0x80) <= 0x0f){ return this.unpack_map(size); } switch(type){ case 0xc0: return null; case 0xc1: return undefined; case 0xc2: return false; case 0xc3: return true; case 0xca: return this.unpack_float(); case 0xcb: return this.unpack_double(); case 0xcc: return this.unpack_uint8(); case 0xcd: return this.unpack_uint16(); case 0xce: return this.unpack_uint32(); case 0xcf: return this.unpack_uint64(); case 0xd0: return this.unpack_int8(); case 0xd1: return this.unpack_int16(); case 0xd2: return this.unpack_int32(); case 0xd3: return this.unpack_int64(); case 0xd4: return undefined; case 0xd5: return undefined; case 0xd6: return undefined; case 0xd7: return undefined; case 0xd8: size = this.unpack_uint16(); return this.unpack_string(size); case 0xd9: size = this.unpack_uint32(); return this.unpack_string(size); case 0xda: size = this.unpack_uint16(); return this.unpack_raw(size); case 0xdb: size = this.unpack_uint32(); return this.unpack_raw(size); case 0xdc: size = this.unpack_uint16(); return this.unpack_array(size); case 0xdd: size = this.unpack_uint32(); return this.unpack_array(size); case 0xde: size = this.unpack_uint16(); return this.unpack_map(size); case 0xdf: size = this.unpack_uint32(); return this.unpack_map(size); } } Unpacker.prototype.unpack_uint8 = function(){ var byte = this.dataView[this.index] & 0xff; this.index++; return byte; }; Unpacker.prototype.unpack_uint16 = function(){ var bytes = this.read(2); var uint16 = ((bytes[0] & 0xff) * 256) + (bytes[1] & 0xff); this.index += 2; return uint16; } Unpacker.prototype.unpack_uint32 = function(){ var bytes = this.read(4); var uint32 = ((bytes[0] * 256 + bytes[1]) * 256 + bytes[2]) * 256 + bytes[3]; this.index += 4; return uint32; } Unpacker.prototype.unpack_uint64 = function(){ var bytes = this.read(8); var uint64 = ((((((bytes[0] * 256 + bytes[1]) * 256 + bytes[2]) * 256 + bytes[3]) * 256 + bytes[4]) * 256 + bytes[5]) * 256 + bytes[6]) * 256 + bytes[7]; this.index += 8; return uint64; } Unpacker.prototype.unpack_int8 = function(){ var uint8 = this.unpack_uint8(); return (uint8 < 0x80 ) ? uint8 : uint8 - (1 << 8); }; Unpacker.prototype.unpack_int16 = function(){ var uint16 = this.unpack_uint16(); return (uint16 < 0x8000 ) ? uint16 : uint16 - (1 << 16); } Unpacker.prototype.unpack_int32 = function(){ var uint32 = this.unpack_uint32(); return (uint32 < Math.pow(2, 31) ) ? uint32 : uint32 - Math.pow(2, 32); } Unpacker.prototype.unpack_int64 = function(){ var uint64 = this.unpack_uint64(); return (uint64 < Math.pow(2, 63) ) ? uint64 : uint64 - Math.pow(2, 64); } Unpacker.prototype.unpack_raw = function(size){ if ( this.length < this.index + size){ throw new Error('BinaryPackFailure: index is out of range' + ' ' + this.index + ' ' + size + ' ' + this.length); } var buf = this.dataBuffer.slice(this.index, this.index + size); this.index += size; //buf = util.bufferToString(buf); return buf; } Unpacker.prototype.unpack_string = function(size){ var bytes = this.read(size); var i = 0, str = '', c, code; while(i < size){ c = bytes[i]; if ( c < 128){ str += String.fromCharCode(c); i++; } else if ((c ^ 0xc0) < 32){ code = ((c ^ 0xc0) << 6) | (bytes[i+1] & 63); str += String.fromCharCode(code); i += 2; } else { code = ((c & 15) << 12) | ((bytes[i+1] & 63) << 6) | (bytes[i+2] & 63); str += String.fromCharCode(code); i += 3; } } this.index += size; return str; } Unpacker.prototype.unpack_array = function(size){ var objects = new Array(size); for(var i = 0; i < size ; i++){ objects[i] = this.unpack(); } return objects; } Unpacker.prototype.unpack_map = function(size){ var map = {}; for(var i = 0; i < size ; i++){ var key = this.unpack(); var value = this.unpack(); map[key] = value; } return map; } Unpacker.prototype.unpack_float = function(){ var uint32 = this.unpack_uint32(); var sign = uint32 >> 31; var exp = ((uint32 >> 23) & 0xff) - 127; var fraction = ( uint32 & 0x7fffff ) | 0x800000; return (sign == 0 ? 1 : -1) * fraction * Math.pow(2, exp - 23); } Unpacker.prototype.unpack_double = function(){ var h32 = this.unpack_uint32(); var l32 = this.unpack_uint32(); var sign = h32 >> 31; var exp = ((h32 >> 20) & 0x7ff) - 1023; var hfrac = ( h32 & 0xfffff ) | 0x100000; var frac = hfrac * Math.pow(2, exp - 20) + l32 * Math.pow(2, exp - 52); return (sign == 0 ? 1 : -1) * frac; } Unpacker.prototype.read = function(length){ var j = this.index; if (j + length <= this.length) { return this.dataView.subarray(j, j + length); } else { throw new Error('BinaryPackFailure: read index out of range'); } } function Packer(){ this.bufferBuilder = new BufferBuilder(); } Packer.prototype.getBuffer = function(){ return this.bufferBuilder.getBuffer(); } Packer.prototype.pack = function(value){ var type = typeof(value); if (type == 'string'){ this.pack_string(value); } else if (type == 'number'){ if (Math.floor(value) === value){ this.pack_integer(value); } else{ this.pack_double(value); } } else if (type == 'boolean'){ if (value === true){ this.bufferBuilder.append(0xc3); } else if (value === false){ this.bufferBuilder.append(0xc2); } } else if (type == 'undefined'){ this.bufferBuilder.append(0xc0); } else if (type == 'object'){ if (value === null){ this.bufferBuilder.append(0xc0); } else { var constructor = value.constructor; if (constructor == Array){ this.pack_array(value); } else if (constructor == Blob || constructor == File) { this.pack_bin(value); } else if (constructor == ArrayBuffer) { if(binaryFeatures.useArrayBufferView) { this.pack_bin(new Uint8Array(value)); } else { this.pack_bin(value); } } else if ('BYTES_PER_ELEMENT' in value){ if(binaryFeatures.useArrayBufferView) { this.pack_bin(new Uint8Array(value.buffer)); } else { this.pack_bin(value.buffer); } } else if (constructor == Object){ this.pack_object(value); } else if (constructor == Date){ this.pack_string(value.toString()); } else if (typeof value.toBinaryPack == 'function'){ this.bufferBuilder.append(value.toBinaryPack()); } else { throw new Error('Type "' + constructor.toString() + '" not yet supported'); } } } else { throw new Error('Type "' + type + '" not yet supported'); } this.bufferBuilder.flush(); } Packer.prototype.pack_bin = function(blob){ var length = blob.length || blob.byteLength || blob.size; if (length <= 0x0f){ this.pack_uint8(0xa0 + length); } else if (length <= 0xffff){ this.bufferBuilder.append(0xda) ; this.pack_uint16(length); } else if (length <= 0xffffffff){ this.bufferBuilder.append(0xdb); this.pack_uint32(length); } else{ throw new Error('Invalid length'); return; } this.bufferBuilder.append(blob); } Packer.prototype.pack_string = function(str){ var length = utf8Length(str); if (length <= 0x0f){ this.pack_uint8(0xb0 + length); } else if (length <= 0xffff){ this.bufferBuilder.append(0xd8) ; this.pack_uint16(length); } else if (length <= 0xffffffff){ this.bufferBuilder.append(0xd9); this.pack_uint32(length); } else{ throw new Error('Invalid length'); return; } this.bufferBuilder.append(str); } Packer.prototype.pack_array = function(ary){ var length = ary.length; if (length <= 0x0f){ this.pack_uint8(0x90 + length); } else if (length <= 0xffff){ this.bufferBuilder.append(0xdc) this.pack_uint16(length); } else if (length <= 0xffffffff){ this.bufferBuilder.append(0xdd); this.pack_uint32(length); } else{ throw new Error('Invalid length'); } for(var i = 0; i < length ; i++){ this.pack(ary[i]); } } Packer.prototype.pack_integer = function(num){ if ( -0x20 <= num && num <= 0x7f){ this.bufferBuilder.append(num & 0xff); } else if (0x00 <= num && num <= 0xff){ this.bufferBuilder.append(0xcc); this.pack_uint8(num); } else if (-0x80 <= num && num <= 0x7f){ this.bufferBuilder.append(0xd0); this.pack_int8(num); } else if ( 0x0000 <= num && num <= 0xffff){ this.bufferBuilder.append(0xcd); this.pack_uint16(num); } else if (-0x8000 <= num && num <= 0x7fff){ this.bufferBuilder.append(0xd1); this.pack_int16(num); } else if ( 0x00000000 <= num && num <= 0xffffffff){ this.bufferBuilder.append(0xce); this.pack_uint32(num); } else if (-0x80000000 <= num && num <= 0x7fffffff){ this.bufferBuilder.append(0xd2); this.pack_int32(num); } else if (-0x8000000000000000 <= num && num <= 0x7FFFFFFFFFFFFFFF){ this.bufferBuilder.append(0xd3); this.pack_int64(num); } else if (0x0000000000000000 <= num && num <= 0xFFFFFFFFFFFFFFFF){ this.bufferBuilder.append(0xcf); this.pack_uint64(num); } else{ throw new Error('Invalid integer'); } } Packer.prototype.pack_double = function(num){ var sign = 0; if (num < 0){ sign = 1; num = -num; } var exp = Math.floor(Math.log(num) / Math.LN2); var frac0 = num / Math.pow(2, exp) - 1; var frac1 = Math.floor(frac0 * Math.pow(2, 52)); var b32 = Math.pow(2, 32); var h32 = (sign << 31) | ((exp+1023) << 20) | (frac1 / b32) & 0x0fffff; var l32 = frac1 % b32; this.bufferBuilder.append(0xcb); this.pack_int32(h32); this.pack_int32(l32); } Packer.prototype.pack_object = function(obj){ var keys = Object.keys(obj); var length = keys.length; if (length <= 0x0f){ this.pack_uint8(0x80 + length); } else if (length <= 0xffff){ this.bufferBuilder.append(0xde); this.pack_uint16(length); } else if (length <= 0xffffffff){ this.bufferBuilder.append(0xdf); this.pack_uint32(length); } else{ throw new Error('Invalid length'); } for(var prop in obj){ if (obj.hasOwnProperty(prop)){ this.pack(prop); this.pack(obj[prop]); } } } Packer.prototype.pack_uint8 = function(num){ this.bufferBuilder.append(num); } Packer.prototype.pack_uint16 = function(num){ this.bufferBuilder.append(num >> 8); this.bufferBuilder.append(num & 0xff); } Packer.prototype.pack_uint32 = function(num){ var n = num & 0xffffffff; this.bufferBuilder.append((n & 0xff000000) >>> 24); this.bufferBuilder.append((n & 0x00ff0000) >>> 16); this.bufferBuilder.append((n & 0x0000ff00) >>> 8); this.bufferBuilder.append((n & 0x000000ff)); } Packer.prototype.pack_uint64 = function(num){ var high = num / Math.pow(2, 32); var low = num % Math.pow(2, 32); this.bufferBuilder.append((high & 0xff000000) >>> 24); this.bufferBuilder.append((high & 0x00ff0000) >>> 16); this.bufferBuilder.append((high & 0x0000ff00) >>> 8); this.bufferBuilder.append((high & 0x000000ff)); this.bufferBuilder.append((low & 0xff000000) >>> 24); this.bufferBuilder.append((low & 0x00ff0000) >>> 16); this.bufferBuilder.append((low & 0x0000ff00) >>> 8); this.bufferBuilder.append((low & 0x000000ff)); } Packer.prototype.pack_int8 = function(num){ this.bufferBuilder.append(num & 0xff); } Packer.prototype.pack_int16 = function(num){ this.bufferBuilder.append((num & 0xff00) >> 8); this.bufferBuilder.append(num & 0xff); } Packer.prototype.pack_int32 = function(num){ this.bufferBuilder.append((num >>> 24) & 0xff); this.bufferBuilder.append((num & 0x00ff0000) >>> 16); this.bufferBuilder.append((num & 0x0000ff00) >>> 8); this.bufferBuilder.append((num & 0x000000ff)); } Packer.prototype.pack_int64 = function(num){ var high = Math.floor(num / Math.pow(2, 32)); var low = num % Math.pow(2, 32); this.bufferBuilder.append((high & 0xff000000) >>> 24); this.bufferBuilder.append((high & 0x00ff0000) >>> 16); this.bufferBuilder.append((high & 0x0000ff00) >>> 8); this.bufferBuilder.append((high & 0x000000ff)); this.bufferBuilder.append((low & 0xff000000) >>> 24); this.bufferBuilder.append((low & 0x00ff0000) >>> 16); this.bufferBuilder.append((low & 0x0000ff00) >>> 8); this.bufferBuilder.append((low & 0x000000ff)); } function _utf8Replace(m){ var code = m.charCodeAt(0); if(code <= 0x7ff) return '00'; if(code <= 0xffff) return '000'; if(code <= 0x1fffff) return '0000'; if(code <= 0x3ffffff) return '00000'; return '000000'; } function utf8Length(str){ if (str.length > 600) { // Blob method faster for large strings return (new Blob([str])).size; } else { return str.replace(/[^\u0000-\u007F]/g, _utf8Replace).length; } } /** * Light EventEmitter. Ported from Node.js/events.js * Eric Zhang */ /** * EventEmitter class * Creates an object with event registering and firing methods */ function EventEmitter() { // Initialise required storage variables this._events = {}; } var isArray = Array.isArray; EventEmitter.prototype.addListener = function(type, listener, scope, once) { if ('function' !== typeof listener) { throw new Error('addListener only takes instances of Function'); } // To avoid recursion in the case that type == "newListeners"! Before // adding it to the listeners, first emit "newListeners". this.emit('newListener', type, typeof listener.listener === 'function' ? listener.listener : listener); if (!this._events[type]) { // Optimize the case of one listener. Don't need the extra array object. this._events[type] = listener; } else if (isArray(this._events[type])) { // If we've already got an array, just append. this._events[type].push(listener); } else { // Adding the second element, need to change to array. this._events[type] = [this._events[type], listener]; } }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.once = function(type, listener, scope) { if ('function' !== typeof listener) { throw new Error('.once only takes instances of Function'); } var self = this; function g() { self.removeListener(type, g); listener.apply(this, arguments); }; g.listener = listener; self.on(type, g); return this; }; EventEmitter.prototype.removeListener = function(type, listener, scope) { if ('function' !== typeof listener) { throw new Error('removeListener only takes instances of Function'); } // does not use listeners(), so no side effect of creating _events[type] if (!this._events[type]) return this; var list = this._events[type]; if (isArray(list)) { var position = -1; for (var i = 0, length = list.length; i < length; i++) { if (list[i] === listener || (list[i].listener && list[i].listener === listener)) { position = i; break; } } if (position < 0) return this; list.splice(position, 1); if (list.length == 0) delete this._events[type]; } else if (list === listener || (list.listener && list.listener === listener)) { delete this._events[type]; } return this; }; EventEmitter.prototype.off = EventEmitter.prototype.removeListener; EventEmitter.prototype.removeAllListeners = function(type) { if (arguments.length === 0) { this._events = {}; return this; } // does not use listeners(), so no side effect of creating _events[type] if (type && this._events && this._events[type]) this._events[type] = null; return this; }; EventEmitter.prototype.listeners = function(type) { if (!this._events[type]) this._events[type] = []; if (!isArray(this._events[type])) { this._events[type] = [this._events[type]]; } return this._events[type]; }; EventEmitter.prototype.emit = function(type) { var type = arguments[0]; var handler = this._events[type]; if (!handler) return false; if (typeof handler == 'function') { switch (arguments.length) { // fast cases case 1: handler.call(this); break; case 2: handler.call(this, arguments[1]); break; case 3: handler.call(this, arguments[1], arguments[2]); break; // slower default: var l = arguments.length; var args = new Array(l - 1); for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; handler.apply(this, args); } return true; } else if (isArray(handler)) { var l = arguments.length; var args = new Array(l - 1); for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; var listeners = handler.slice(); for (var i = 0, l = listeners.length; i < l; i++) { listeners[i].apply(this, args); } return true; } else { return false; } }; var util = { inherits: function(ctor, superCtor) { ctor.super_ = superCtor; ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); }, extend: function(dest, source) { for(var key in source) { if(source.hasOwnProperty(key)) { dest[key] = source[key]; } } return dest; }, pack: BinaryPack.pack, unpack: BinaryPack.unpack, setZeroTimeout: (function(global) { var timeouts = []; var messageName = 'zero-timeout-message'; // Like setTimeout, but only takes a function argument. There's // no time argument (always zero) and no arguments (you have to // use a closure). function setZeroTimeoutPostMessage(fn) { timeouts.push(fn); global.postMessage(messageName, '*'); } function handleMessage(event) { if (event.source == global && event.data == messageName) { if (event.stopPropagation) { event.stopPropagation(); } if (timeouts.length) { timeouts.shift()(); } } } if (global.addEventListener) { global.addEventListener('message', handleMessage, true); } else if (global.attachEvent) { global.attachEvent('onmessage', handleMessage); } return setZeroTimeoutPostMessage; }(this)) }; function Stream() { EventEmitter.call(this); } util.inherits(Stream, EventEmitter); Stream.prototype.pipe = function(dest, options) { var source = this; function ondata(chunk) { if (dest.writable) { if (false === dest.write(chunk) && source.pause) { source.pause(); } } } source.on('data', ondata); function ondrain() { if (source.readable && source.resume) { source.resume(); } } dest.on('drain', ondrain); // If the 'end' option is not supplied, dest.end() will be called when // source gets the 'end' or 'close' events. Only dest.end() once. if (!dest._isStdio && (!options || options.end !== false)) { source.on('end', onend); source.on('close', onclose); } var didOnEnd = false; function onend() { if (didOnEnd) return; didOnEnd = true; dest.end(); } function onclose() { if (didOnEnd) return; didOnEnd = true; dest.destroy(); } // don't leave dangling pipes when there are errors. function onerror(er) { cleanup(); if (this.listeners('error').length === 0) { throw er; // Unhandled stream error in pipe. } } source.on('error', onerror); dest.on('error', onerror); // remove all the event listeners that were added. function cleanup() { source.removeListener('data', ondata); dest.removeListener('drain', ondrain); source.removeListener('end', onend); source.removeListener('close', onclose); source.removeListener('error', onerror); dest.removeListener('error', onerror); source.removeListener('end', cleanup); source.removeListener('close', cleanup); dest.removeListener('end', cleanup); dest.removeListener('close', cleanup); } source.on('end', cleanup); source.on('close', cleanup); dest.on('end', cleanup); dest.on('close', cleanup); dest.emit('pipe', source); // Allow for unix-like usage: A.pipe(B).pipe(C) return dest; }; function BlobReadStream(source, options){ Stream.call(this); options = util.extend({ readDelay: 0, paused: false }, options); this._source = source; this._start = 0; this._readChunkSize = options.chunkSize || source.size; this._readDelay = options.readDelay; this.readable = true; this.paused = options.paused; this._read(); } util.inherits(BlobReadStream, Stream); BlobReadStream.prototype.pause = function(){ this.paused = true; }; BlobReadStream.prototype.resume = function(){ this.paused = false; this._read(); }; BlobReadStream.prototype.destroy = function(){ this.readable = false; clearTimeout(this._timeoutId); }; BlobReadStream.prototype._read = function(){ var self = this; function emitReadChunk(){ self._emitReadChunk(); } var readDelay = this._readDelay; if (readDelay !== 0){ this._timeoutId = setTimeout(emitReadChunk, readDelay); } else { util.setZeroTimeout(emitReadChunk); } }; BlobReadStream.prototype._emitReadChunk = function(){ if(this.paused || !this.readable) return; var chunkSize = Math.min(this._source.size - this._start, this._readChunkSize); if(chunkSize === 0){ this.readable = false; this.emit("end"); return; } var sourceEnd = this._start + chunkSize; var chunk = (this._source.slice || this._source.webkitSlice || this._source.mozSlice).call(this._source, this._start, sourceEnd); this._start = sourceEnd; this._read(); this.emit("data", chunk); }; /* function BlobWriteStream(options){ stream.Stream.call(this); options = _.extend({ onFull: onFull, onEnd: function(){}, minBlockAllocSize: 0, drainDelay:0 }, options); this._onFull = options.onFull; this._onEnd = options.onEnd; this._onWrite = options.onWrite; this._minBlockAllocSize = options.minBlockAllocSize; this._maxBlockAllocSize = options.maxBlockAllocSize; this._drainDelay = options.drainDelay; this._buffer = new Buffer(options.minBlockAllocSize); this._destination = this._buffer; this._destinationPos = 0; this._writeQueue = []; this._pendingOnFull = false; this._pendingQueueDrain = false; this.writable = true; this.bytesWritten = 0; } util.inherits(BlobWriteStream, stream.Stream); BlobWriteStream.prototype.getBuffer = function(){ return this._buffer; }; BlobWriteStream.prototype.write = function(data, encoding){ if(!this.writable){ throw new Error("stream is not writable"); } if(!Buffer.isBuffer(data)){ data = new Buffer(data, encoding); } if(data.length){ this._writeQueue.push(data); } this._commit(); return this._writeQueue.length === 0; }; BlobWriteStream.prototype._commit = function(){ var self = this; var destination = this._destination; var writeQueue = this._writeQueue; var startDestinationPos = this._destinationPos; while(writeQueue.length && destination.length){ var head = writeQueue[0]; var copySize = Math.min(destination.length, head.length); head.copy(destination, 0, 0, copySize); head = head.slice(copySize); destination = destination.slice(copySize); this.bytesWritten += copySize; this._destinationPos += copySize; if(head.length === 0){ writeQueue.shift(); } else{ writeQueue[0] = head; } } this._destination = destination; bytesCommitted = this._destinationPos - startDestinationPos; if(bytesCommitted){ if(this._onWrite){ if(writeQueue.length){ this._pendingQueueDrain = true; } // By locking destination the buffer is frozen and the onWrite // callback cannot miss any write commits this._destination = emptyBuffer; var consumer = this._onWrite; this._onWrite = null; consumer.call(this, function(nextCallback){ util.setZeroTimeout(function(){ self._destination = destination; self._onWrite = nextCallback; self._commit(); }); }, consumer); return; } } if(writeQueue.length){ this._pendingQueueDrain = true; this._growBuffer(); } else if(this._pendingQueueDrain){ this._pendingQueueDrain = false; if(this._drainDelay !== 0){ setTimeout(function(){ self.emit("drain"); }, this._drainDelay); } else{ util.setZeroTimeout(function(){ self.emit("drain"); }); } } }; BlobWriteStream.prototype._growBuffer = function(){ var self = this; var writeQueue = this._writeQueue; var requestSize = this._minBlockAllocSize; var maxBlockAllocSize = this._maxBlockAllocSize; var add = (maxBlockAllocSize === undefined ? function(a, b){return a + b;} : function(a, b){return Math.min(a + b, maxBlockAllocSize);}); for(var i = 0, queueLength = writeQueue.length; i < queueLength; i++){ requestSize = add(requestSize, writeQueue[i].length); } // Prevent concurrent onFull callbacks if(this._pendingOnFull){ return; } this._pendingOnFull = true; this._onFull(this._buffer, requestSize, function(buffer, destination){ util.setZeroTimeout(function(){ self._pendingOnFull = false; if(!destination){ if(self.writable){ self.emit("error", new Error("buffer is full")); } self.destroy(); return; } self._buffer = buffer; self._destination = destination; self._commit(); }); }); }; BlobWriteStream.prototype.end = function(data, encoding){ var self = this; function _end(){ self.writable = false; self._onEnd(); } if(data){ if(this.write(data, encoding)){ _end(); }else{ self.writable = false; this.once("drain", _end); } } else{ _end(); } }; BlobWriteStream.prototype.destroy = function(){ this.writable = false; this._pendingQueueDrain = false; this._writeQueue = []; }; BlobWriteStream.prototype.consume = function(consume){ this._buffer = this._buffer.slice(consume); this._destinationPos -= consume; }; BlobWriteStream.prototype.getCommittedSlice = function(){ return this._buffer.slice(0, this._destinationPos); }; function onFull(buffer, extraSize, callback){ var newBuffer = new Buffer(buffer.length + extraSize); buffer.copy(newBuffer); callback(newBuffer, newBuffer.slice(buffer.length)); } */ exports.BlobReadStream = BlobReadStream; function BinaryStream(socket, id, create, meta) { if (!(this instanceof BinaryStream)) return new BinaryStream(options); var self = this; Stream.call(this); this.id = id; this._socket = socket; this.writable = true; this.readable = true; this.paused = false; this._closed = false; this._ended = false; if(create) { // This is a stream we are creating this._write(1, meta, this.id); } } util.inherits(BinaryStream, Stream); BinaryStream.prototype._onDrain = function() { if(!this.paused) { this.emit('drain'); } }; BinaryStream.prototype._onClose = function() { // Emit close event if (this._closed) { return; } this.readable = false; this.writable = false; this._closed = true; this.emit('close'); }; BinaryStream.prototype._onError = function(error){ this.readable = false; this.writable = false; this.emit('error', error); }; // Write stream BinaryStream.prototype._onPause = function() { // Emit pause event this.paused = true; this.emit('pause'); }; BinaryStream.prototype._onResume = function() { // Emit resume event this.paused = false; this.emit('resume'); this.emit('drain'); }; BinaryStream.prototype._write = function(code, data, bonus) { if (this._socket.readyState !== this._socket.constructor.OPEN) { return false; } var message = util.pack([code, data, bonus]); return this._socket.send(message) !== false; }; BinaryStream.prototype.write = function(data) { if(this.writable) { var out = this._write(2, data, this.id); return !this.paused && out; } else { throw new Error('Stream is not writable'); } }; BinaryStream.prototype.end = function() { this._ended = true; this.readable = false; this._write(5, null, this.id); }; BinaryStream.prototype.destroy = BinaryStream.prototype.destroySoon = function() { this._onClose(); this._write(6, null, this.id); }; // Read stream BinaryStream.prototype._onEnd = function() { if(this._ended) { return; } this._ended = true; this.readable = false; this.emit('end'); }; BinaryStream.prototype._onData = function(data) { // Dispatch this.emit('data', data); }; BinaryStream.prototype.pause = function() { this._onPause(); this._write(3, null, this.id); }; BinaryStream.prototype.resume = function() { this._onResume(); this._write(4, null, this.id); }; function BinaryClient(socket, options) { if (!(this instanceof BinaryClient)) return new BinaryClient(socket, options); EventEmitter.call(this); var self = this; this._options = util.extend({ chunkSize: 40960 }, options); this.streams = {}; if(typeof socket === 'string') { this._nextId = 0; this._socket = new WebSocket(socket); } else { // Use odd numbered ids for server originated streams this._nextId = 1; this._socket = socket; } this._socket.binaryType = 'arraybuffer'; this._socket.addEventListener('open', function(){ self.emit('open'); }); this._socket.addEventListener('error', function(error){ var ids = Object.keys(self.streams); for (var i = 0, ii = ids.length; i < ii; i++) { self.streams[ids[i]]._onError(error); } self.emit('error', error); }); this._socket.addEventListener('close', function(code, message){ var ids = Object.keys(self.streams); for (var i = 0, ii = ids.length; i < ii; i++) { self.streams[ids[i]]._onClose(); } self.emit('close', code, message); }); this._socket.addEventListener('message', function(data, flags){ util.setZeroTimeout(function(){ // Message format // [type, payload, bonus ] // // Reserved // [ 0 , X , X ] // // // New stream // [ 1 , Meta , new streamId ] // // // Data // [ 2 , Data , streamId ] // // // Pause // [ 3 , null , streamId ] // // // Resume // [ 4 , null , streamId ] // // // End // [ 5 , null , streamId ] // // // Close // [ 6 , null , streamId ] // data = data.data; try { data = util.unpack(data); } catch (ex) { return self.emit('error', new Error('Received unparsable message: ' + ex)); } if (!(data instanceof Array)) return self.emit('error', new Error('Received non-array message')); if (data.length != 3) return self.emit('error', new Error('Received message with wrong part count: ' + data.length)); if ('number' != typeof data[0]) return self.emit('error', new Error('Received message with non-number type: ' + data[0])); switch(data[0]) { case 0: // Reserved break; case 1: var meta = data[1]; var streamId = data[2]; var binaryStream = self._receiveStream(streamId); self.emit('stream', binaryStream, meta); break; case 2: var payload = data[1]; var streamId = data[2]; var binaryStream = self.streams[streamId]; if(binaryStream) { binaryStream._onData(payload); } else { self.emit('error', new Error('Received `data` message for unknown stream: ' + streamId)); } break; case 3: var streamId = data[2]; var binaryStream = self.streams[streamId]; if(binaryStream) { binaryStream._onPause(); } else { self.emit('error', new Error('Received `pause` message for unknown stream: ' + streamId)); } break; case 4: var streamId = data[2]; var binaryStream = self.streams[streamId]; if(binaryStream) { binaryStream._onResume(); } else { self.emit('error', new Error('Received `resume` message for unknown stream: ' + streamId)); } break; case 5: var streamId = data[2]; var binaryStream = self.streams[streamId]; if(binaryStream) { binaryStream._onEnd(); } else { self.emit('error', new Error('Received `end` message for unknown stream: ' + streamId)); } break; case 6: var streamId = data[2]; var binaryStream = self.streams[streamId]; if(binaryStream) { binaryStream._onClose(); } else { self.emit('error', new Error('Received `close` message for unknown stream: ' + streamId)); } break; default: self.emit('error', new Error('Unrecognized message type received: ' + data[0])); } }); }); } util.inherits(BinaryClient, EventEmitter); BinaryClient.prototype.send = function(data, meta){ var stream = this.createStream(meta); if(data instanceof Stream) { data.pipe(stream); } else if (util.isNode === true) { if(Buffer.isBuffer(data)) { (new BufferReadStream(data, {chunkSize: this._options.chunkSize})).pipe(stream); } else { stream.write(data); } } else if (util.isNode !== true) { if(data.constructor == Blob || data.constructor == File) { (new BlobReadStream(data, {chunkSize: this._options.chunkSize})).pipe(stream); } else if (data.constructor == ArrayBuffer) { var blob; if(binaryFeatures.useArrayBufferView) { data = new Uint8Array(data); } if(binaryFeatures.useBlobBuilder) { var builder = new BlobBuilder(); builder.append(data); blob = builder.getBlob() } else { blob = new Blob([data]); } (new BlobReadStream(blob, {chunkSize: this._options.chunkSize})).pipe(stream); } else if (typeof data === 'object' && 'BYTES_PER_ELEMENT' in data) { var blob; if(!binaryFeatures.useArrayBufferView) { // Warn data = data.buffer; } if(binaryFeatures.useBlobBuilder) { var builder = new BlobBuilder(); builder.append(data); blob = builder.getBlob() } else { blob = new Blob([data]); } (new BlobReadStream(blob, {chunkSize: this._options.chunkSize})).pipe(stream); } else { stream.write(data); } } return stream; }; BinaryClient.prototype._receiveStream = function(streamId){ var self = this; var binaryStream = new BinaryStream(this._socket, streamId, false); binaryStream.on('close', function(){ delete self.streams[streamId]; }); this.streams[streamId] = binaryStream; return binaryStream; }; BinaryClient.prototype.createStream = function(meta){ if(this._socket.readyState !== WebSocket.OPEN) { throw new Error('Client is not yet connected or has closed'); return; } var self = this; var streamId = this._nextId; this._nextId += 2; var binaryStream = new BinaryStream(this._socket, streamId, true, meta); binaryStream.on('close', function(){ delete self.streams[streamId]; }); this.streams[streamId] = binaryStream; return binaryStream; }; BinaryClient.prototype.close = BinaryClient.prototype.destroy = function() { this._socket.close(); }; exports.BinaryClient = BinaryClient; })(this);