/*! * ws: a node.js websocket client * Copyright(c) 2011 Einar Otto Stangvik * MIT Licensed */ var events = require('events') , util = require('util') , EventEmitter = events.EventEmitter , ErrorCodes = require('./ErrorCodes') , bufferUtil = require('./BufferUtil').BufferUtil; /** * HyBi Sender implementation */ function Sender(socket) { this._socket = socket; this.firstFragment = true; } /** * 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) { 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); this.frameAndSend(0x8, dataBuffer, true, mask); } /** * Sends a ping message to the remote party. * * @api public */ Sender.prototype.ping = function(data, options) { var mask = options && options.mask; this.frameAndSend(0x9, data || '', true, mask); } /** * Sends a pong message to the remote party. * * @api public */ Sender.prototype.pong = function(data, options) { var mask = options && options.mask; this.frameAndSend(0xa, data || '', true, mask); } /** * 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 opcode = options && options.binary ? 2 : 1; if (this.firstFragment === false) opcode = 0; else this.firstFragment = false; if (finalFragment) this.firstFragment = true this.frameAndSend(opcode, data, finalFragment, mask, cb); } /** * Frames and sends a piece of data according to the HyBi WebSocket protocol. * * @api private */ Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, 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; 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); } } } } 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) ]); }