2 * ws: a node.js websocket client
3 * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
7 var events = require('events')
8 , util = require('util')
9 , EventEmitter = events.EventEmitter
10 , ErrorCodes = require('./ErrorCodes')
11 , bufferUtil = require('./BufferUtil').BufferUtil;
14 * HyBi Sender implementation
17 function Sender(socket) {
18 this._socket = socket;
19 this.firstFragment = true;
23 * Inherits from EventEmitter.
26 util.inherits(Sender, events.EventEmitter);
29 * Sends a close instruction to the remote party.
34 Sender.prototype.close = function(code, data, mask) {
35 if (typeof code !== 'undefined') {
36 if (typeof code !== 'number' ||
37 !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number');
40 var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0));
41 writeUInt16BE.call(dataBuffer, code, 0);
42 if (dataBuffer.length > 2) dataBuffer.write(data, 2);
43 this.frameAndSend(0x8, dataBuffer, true, mask);
47 * Sends a ping message to the remote party.
52 Sender.prototype.ping = function(data, options) {
53 var mask = options && options.mask;
54 this.frameAndSend(0x9, data || '', true, mask);
58 * Sends a pong message to the remote party.
63 Sender.prototype.pong = function(data, options) {
64 var mask = options && options.mask;
65 this.frameAndSend(0xa, data || '', true, mask);
69 * Sends text or binary data to the remote party.
74 Sender.prototype.send = function(data, options, cb) {
75 var finalFragment = options && options.fin === false ? false : true;
76 var mask = options && options.mask;
77 var opcode = options && options.binary ? 2 : 1;
78 if (this.firstFragment === false) opcode = 0;
79 else this.firstFragment = false;
80 if (finalFragment) this.firstFragment = true
81 this.frameAndSend(opcode, data, finalFragment, mask, cb);
85 * Frames and sends a piece of data according to the HyBi WebSocket protocol.
90 Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, cb) {
91 var canModifyData = false;
95 this._socket.write(new Buffer([opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)].concat(maskData ? [0, 0, 0, 0] : [])), 'binary', cb);
98 if (typeof cb == 'function') cb(e);
99 else this.emit('error', e);
104 if (!Buffer.isBuffer(data)) {
105 canModifyData = true;
106 if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) {
107 data = getArrayBuffer(data);
109 data = new Buffer(data);
113 var dataLength = data.length
114 , dataOffset = maskData ? 6 : 2
115 , secondByte = dataLength;
117 if (dataLength >= 65536) {
121 else if (dataLength > 125) {
126 var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData);
127 var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset;
128 var outputBuffer = new Buffer(totalLength);
129 outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode;
131 switch (secondByte) {
133 writeUInt16BE.call(outputBuffer, dataLength, 2);
136 writeUInt32BE.call(outputBuffer, 0, 2);
137 writeUInt32BE.call(outputBuffer, dataLength, 6);
141 outputBuffer[1] = secondByte | 0x80;
142 var mask = this._randomMask || (this._randomMask = getRandomMask());
143 outputBuffer[dataOffset - 4] = mask[0];
144 outputBuffer[dataOffset - 3] = mask[1];
145 outputBuffer[dataOffset - 2] = mask[2];
146 outputBuffer[dataOffset - 1] = mask[3];
148 bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength);
150 this._socket.write(outputBuffer, 'binary', cb);
153 if (typeof cb == 'function') cb(e);
154 else this.emit('error', e);
158 bufferUtil.mask(data, mask, data, 0, dataLength);
160 this._socket.write(outputBuffer, 'binary');
161 this._socket.write(data, 'binary', cb);
164 if (typeof cb == 'function') cb(e);
165 else this.emit('error', e);
170 outputBuffer[1] = secondByte;
172 data.copy(outputBuffer, dataOffset);
174 this._socket.write(outputBuffer, 'binary', cb);
177 if (typeof cb == 'function') cb(e);
178 else this.emit('error', e);
183 this._socket.write(outputBuffer, 'binary');
184 this._socket.write(data, 'binary', cb);
187 if (typeof cb == 'function') cb(e);
188 else this.emit('error', e);
194 module.exports = Sender;
196 function writeUInt16BE(value, offset) {
197 this[offset] = (value & 0xff00)>>8;
198 this[offset+1] = value & 0xff;
201 function writeUInt32BE(value, offset) {
202 this[offset] = (value & 0xff000000)>>24;
203 this[offset+1] = (value & 0xff0000)>>16;
204 this[offset+2] = (value & 0xff00)>>8;
205 this[offset+3] = value & 0xff;
208 function getArrayBuffer(data) {
209 // data is either an ArrayBuffer or ArrayBufferView.
210 var array = new Uint8Array(data.buffer || data)
211 , l = data.byteLength || data.length
212 , o = data.byteOffset || 0
213 , buffer = new Buffer(l);
214 for (var i = 0; i < l; ++i) {
215 buffer[i] = array[o+i];
220 function getRandomMask() {
222 ~~(Math.random() * 255),
223 ~~(Math.random() * 255),
224 ~~(Math.random() * 255),
225 ~~(Math.random() * 255)