123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- /*!
- * ws: a node.js websocket client
- * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
- * MIT Licensed
- */
-
- var events = require('events')
- , util = require('util')
- , EventEmitter = events.EventEmitter
- , ErrorCodes = require('./ErrorCodes')
- , bufferUtil = require('./BufferUtil').BufferUtil
- , PerMessageDeflate = require('./PerMessageDeflate');
-
- /**
- * HyBi Sender implementation
- */
-
- function Sender(socket, extensions) {
- events.EventEmitter.call(this);
-
- this._socket = socket;
- this.extensions = extensions || {};
- this.firstFragment = true;
- this.compress = false;
- this.messageHandlers = [];
- this.processing = false;
- }
-
- /**
- * Inherits from EventEmitter.
- */
-
- util.inherits(Sender, events.EventEmitter);
-
- /**
- * Sends a close instruction to the remote party.
- *
- * @api public
- */
-
- Sender.prototype.close = function(code, data, mask, cb) {
- if (typeof code !== 'undefined') {
- if (typeof code !== 'number' ||
- !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number');
- }
- code = code || 1000;
- var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0));
- writeUInt16BE.call(dataBuffer, code, 0);
- if (dataBuffer.length > 2) dataBuffer.write(data, 2);
-
- var self = this;
- this.messageHandlers.push(function(callback) {
- self.frameAndSend(0x8, dataBuffer, true, mask);
- callback();
- if (typeof cb == 'function') cb();
- });
- this.flush();
- };
-
- /**
- * Sends a ping message to the remote party.
- *
- * @api public
- */
-
- Sender.prototype.ping = function(data, options) {
- var mask = options && options.mask;
- var self = this;
- this.messageHandlers.push(function(callback) {
- self.frameAndSend(0x9, data || '', true, mask);
- callback();
- });
- this.flush();
- };
-
- /**
- * Sends a pong message to the remote party.
- *
- * @api public
- */
-
- Sender.prototype.pong = function(data, options) {
- var mask = options && options.mask;
- var self = this;
- this.messageHandlers.push(function(callback) {
- self.frameAndSend(0xa, data || '', true, mask);
- callback();
- });
- this.flush();
- };
-
- /**
- * Sends text or binary data to the remote party.
- *
- * @api public
- */
-
- Sender.prototype.send = function(data, options, cb) {
- var finalFragment = options && options.fin === false ? false : true;
- var mask = options && options.mask;
- var compress = options && options.compress;
- var opcode = options && options.binary ? 2 : 1;
- if (this.firstFragment === false) {
- opcode = 0;
- compress = false;
- } else {
- this.firstFragment = false;
- this.compress = compress;
- }
- if (finalFragment) this.firstFragment = true
-
- var compressFragment = this.compress;
-
- var self = this;
- this.messageHandlers.push(function(callback) {
- self.applyExtensions(data, finalFragment, compressFragment, function(err, data) {
- if (err) {
- if (typeof cb == 'function') cb(err);
- else self.emit('error', err);
- return;
- }
- self.frameAndSend(opcode, data, finalFragment, mask, compress, cb);
- callback();
- });
- });
- this.flush();
- };
-
- /**
- * Frames and sends a piece of data according to the HyBi WebSocket protocol.
- *
- * @api private
- */
-
- Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, compressed, cb) {
- var canModifyData = false;
-
- if (!data) {
- try {
- this._socket.write(new Buffer([opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)].concat(maskData ? [0, 0, 0, 0] : [])), 'binary', cb);
- }
- catch (e) {
- if (typeof cb == 'function') cb(e);
- else this.emit('error', e);
- }
- return;
- }
-
- if (!Buffer.isBuffer(data)) {
- canModifyData = true;
- if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) {
- data = getArrayBuffer(data);
- } else {
- data = new Buffer(data);
- }
- }
-
- var dataLength = data.length
- , dataOffset = maskData ? 6 : 2
- , secondByte = dataLength;
-
- if (dataLength >= 65536) {
- dataOffset += 8;
- secondByte = 127;
- }
- else if (dataLength > 125) {
- dataOffset += 2;
- secondByte = 126;
- }
-
- var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData);
- var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset;
- var outputBuffer = new Buffer(totalLength);
- outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode;
- if (compressed) outputBuffer[0] |= 0x40;
-
- switch (secondByte) {
- case 126:
- writeUInt16BE.call(outputBuffer, dataLength, 2);
- break;
- case 127:
- writeUInt32BE.call(outputBuffer, 0, 2);
- writeUInt32BE.call(outputBuffer, dataLength, 6);
- }
-
- if (maskData) {
- outputBuffer[1] = secondByte | 0x80;
- var mask = this._randomMask || (this._randomMask = getRandomMask());
- outputBuffer[dataOffset - 4] = mask[0];
- outputBuffer[dataOffset - 3] = mask[1];
- outputBuffer[dataOffset - 2] = mask[2];
- outputBuffer[dataOffset - 1] = mask[3];
- if (mergeBuffers) {
- bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength);
- try {
- this._socket.write(outputBuffer, 'binary', cb);
- }
- catch (e) {
- if (typeof cb == 'function') cb(e);
- else this.emit('error', e);
- }
- }
- else {
- bufferUtil.mask(data, mask, data, 0, dataLength);
- try {
- this._socket.write(outputBuffer, 'binary');
- this._socket.write(data, 'binary', cb);
- }
- catch (e) {
- if (typeof cb == 'function') cb(e);
- else this.emit('error', e);
- }
- }
- }
- else {
- outputBuffer[1] = secondByte;
- if (mergeBuffers) {
- data.copy(outputBuffer, dataOffset);
- try {
- this._socket.write(outputBuffer, 'binary', cb);
- }
- catch (e) {
- if (typeof cb == 'function') cb(e);
- else this.emit('error', e);
- }
- }
- else {
- try {
- this._socket.write(outputBuffer, 'binary');
- this._socket.write(data, 'binary', cb);
- }
- catch (e) {
- if (typeof cb == 'function') cb(e);
- else this.emit('error', e);
- }
- }
- }
- };
-
- /**
- * Execute message handler buffers
- *
- * @api private
- */
-
- Sender.prototype.flush = function() {
- if (this.processing) return;
-
- var handler = this.messageHandlers.shift();
- if (!handler) return;
-
- this.processing = true;
-
- var self = this;
-
- handler(function() {
- self.processing = false;
- self.flush();
- });
- };
-
- /**
- * Apply extensions to message
- *
- * @api private
- */
-
- Sender.prototype.applyExtensions = function(data, fin, compress, callback) {
- if (compress && data) {
- this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback);
- } else {
- callback(null, data);
- }
- };
-
- module.exports = Sender;
-
- function writeUInt16BE(value, offset) {
- this[offset] = (value & 0xff00)>>8;
- this[offset+1] = value & 0xff;
- }
-
- function writeUInt32BE(value, offset) {
- this[offset] = (value & 0xff000000)>>24;
- this[offset+1] = (value & 0xff0000)>>16;
- this[offset+2] = (value & 0xff00)>>8;
- this[offset+3] = value & 0xff;
- }
-
- function getArrayBuffer(data) {
- // data is either an ArrayBuffer or ArrayBufferView.
- var array = new Uint8Array(data.buffer || data)
- , l = data.byteLength || data.length
- , o = data.byteOffset || 0
- , buffer = new Buffer(l);
- for (var i = 0; i < l; ++i) {
- buffer[i] = array[o+i];
- }
- return buffer;
- }
-
- function getRandomMask() {
- return new Buffer([
- ~~(Math.random() * 255),
- ~~(Math.random() * 255),
- ~~(Math.random() * 255),
- ~~(Math.random() * 255)
- ]);
- }
|