Comparing version 0.4.32 to 0.5.0
@@ -0,1 +1,11 @@ | ||
v0.5 - November 20th 2013 | ||
===================== | ||
* Fixed a file descriptor leak. | ||
* Removed wscat as cli client from ws and released as separate module (wscat) | ||
* Fixed memory leak caused by EventEmitters. | ||
* Protocol errors now return a 401 error instead of 404 which is more suitable. | ||
* Code refactor to support strict mode. | ||
* Updated dependencies to latest versions | ||
v0.4.31 - September 23th, 2013 | ||
@@ -2,0 +12,0 @@ ===================== |
47
index.js
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
/*! | ||
@@ -7,21 +9,42 @@ * ws: a node.js websocket client | ||
module.exports = require('./lib/WebSocket'); | ||
module.exports.Server = require('./lib/WebSocketServer'); | ||
module.exports.Sender = require('./lib/Sender'); | ||
module.exports.Receiver = require('./lib/Receiver'); | ||
var WS = module.exports = require('./lib/WebSocket'); | ||
module.exports.createServer = function (options, connectionListener) { | ||
var server = new module.exports.Server(options); | ||
if (typeof connectionListener === 'function') { | ||
server.on('connection', connectionListener); | ||
WS.Server = require('./lib/WebSocketServer'); | ||
WS.Sender = require('./lib/Sender'); | ||
WS.Receiver = require('./lib/Receiver'); | ||
/** | ||
* Create a new WebSocket server. | ||
* | ||
* @param {Object} options Server options | ||
* @param {Function} fn Optional connection listener. | ||
* @returns {WS.Server} | ||
* @api public | ||
*/ | ||
WS.createServer = function createServer(options, fn) { | ||
var server = new WS.Server(options); | ||
if (typeof fn === 'function') { | ||
server.on('connection', fn); | ||
} | ||
return server; | ||
}; | ||
module.exports.connect = module.exports.createConnection = function (address, openListener) { | ||
var client = new module.exports(address); | ||
if (typeof openListener === 'function') { | ||
client.on('open', openListener); | ||
/** | ||
* Create a new WebSocket connection. | ||
* | ||
* @param {String} address The URL/address we need to connect to. | ||
* @param {Function} fn Open listener. | ||
* @returns {WS} | ||
* @api public | ||
*/ | ||
WS.connect = WS.createConnection = function connect(address, fn) { | ||
var client = new WS(address); | ||
if (typeof fn === 'function') { | ||
client.on('open', fn); | ||
} | ||
return client; | ||
}; |
@@ -16,2 +16,4 @@ /*! | ||
function Sender(socket) { | ||
events.EventEmitter.call(this); | ||
this.socket = socket; | ||
@@ -18,0 +20,0 @@ this.continuationFrame = false; |
@@ -18,2 +18,4 @@ /*! | ||
function Sender(socket) { | ||
events.EventEmitter.call(this); | ||
this._socket = socket; | ||
@@ -20,0 +22,0 @@ this.firstFragment = true; |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
/*! | ||
@@ -7,9 +9,9 @@ * ws: a node.js websocket client | ||
var util = require('util') | ||
, events = require('events') | ||
var url = require('url') | ||
, util = require('util') | ||
, http = require('http') | ||
, https = require('https') | ||
, crypto = require('crypto') | ||
, url = require('url') | ||
, stream = require('stream') | ||
, Ultron = require('ultron') | ||
, Options = require('options') | ||
@@ -19,3 +21,4 @@ , Sender = require('./Sender') | ||
, SenderHixie = require('./Sender.hixie') | ||
, ReceiverHixie = require('./Receiver.hixie'); | ||
, ReceiverHixie = require('./Receiver.hixie') | ||
, EventEmitter = require('events').EventEmitter; | ||
@@ -32,11 +35,17 @@ /** | ||
var closeTimeout = 30000; // Allow 5 seconds to terminate the connection cleanly | ||
var closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly | ||
/** | ||
* WebSocket implementation | ||
* | ||
* @constructor | ||
* @param {String} address Connection address. | ||
* @param {String|Array} protocols WebSocket protocols. | ||
* @param {Object} options Additional connection options. | ||
* @api public | ||
*/ | ||
function WebSocket(address, protocols, options) { | ||
EventEmitter.call(this); | ||
if (protocols && !Array.isArray(protocols) && 'object' == typeof protocols) { | ||
if (protocols && !Array.isArray(protocols) && 'object' === typeof protocols) { | ||
// accept the "options" Object as the 2nd argument | ||
@@ -46,11 +55,15 @@ options = protocols; | ||
} | ||
if ('string' == typeof protocols) { | ||
if ('string' === typeof protocols) { | ||
protocols = [ protocols ]; | ||
} | ||
if (!Array.isArray(protocols)) { | ||
protocols = []; | ||
} | ||
// TODO: actually handle the `Sub-Protocols` part of the WebSocket client | ||
this._socket = null; | ||
this._ultron = null; | ||
this.bytesReceived = 0; | ||
@@ -70,10 +83,8 @@ this.readyState = null; | ||
*/ | ||
util.inherits(WebSocket, EventEmitter); | ||
util.inherits(WebSocket, events.EventEmitter); | ||
/** | ||
* Ready States | ||
*/ | ||
["CONNECTING", "OPEN", "CLOSING", "CLOSED"].forEach(function (state, index) { | ||
["CONNECTING", "OPEN", "CLOSING", "CLOSED"].forEach(function each(state, index) { | ||
WebSocket.prototype[state] = WebSocket[state] = index; | ||
@@ -88,9 +99,13 @@ }); | ||
*/ | ||
WebSocket.prototype.close = function close(code, data) { | ||
if ( | ||
this.readyState === WebSocket.CLOSING | ||
|| this.readyState === WebSocket.CLOSED | ||
) return; | ||
WebSocket.prototype.close = function(code, data) { | ||
if (this.readyState == WebSocket.CLOSING || this.readyState == WebSocket.CLOSED) return; | ||
if (this.readyState == WebSocket.CONNECTING) { | ||
if (this.readyState === WebSocket.CONNECTING) { | ||
this.readyState = WebSocket.CLOSED; | ||
return; | ||
} | ||
try { | ||
@@ -102,10 +117,8 @@ this.readyState = WebSocket.CLOSING; | ||
this._sender.close(code, data, mask); | ||
} | ||
catch (e) { | ||
} catch (e) { | ||
this.emit('error', e); | ||
} | ||
finally { | ||
} finally { | ||
this.terminate(); | ||
} | ||
} | ||
}; | ||
@@ -117,7 +130,7 @@ /** | ||
*/ | ||
WebSocket.prototype.pause = function pauser() { | ||
if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); | ||
WebSocket.prototype.pause = function() { | ||
if (this.readyState != WebSocket.OPEN) throw new Error('not opened'); | ||
return this._socket.pause(); | ||
} | ||
}; | ||
@@ -132,12 +145,14 @@ /** | ||
*/ | ||
WebSocket.prototype.ping = function(data, options, dontFailWhenClosed) { | ||
if (this.readyState != WebSocket.OPEN) { | ||
WebSocket.prototype.ping = function ping(data, options, dontFailWhenClosed) { | ||
if (this.readyState !== WebSocket.OPEN) { | ||
if (dontFailWhenClosed === true) return; | ||
throw new Error('not opened'); | ||
} | ||
options = options || {}; | ||
if (typeof options.mask == 'undefined') options.mask = !this._isServer; | ||
if (typeof options.mask === 'undefined') options.mask = !this._isServer; | ||
this._sender.ping(data, options); | ||
} | ||
}; | ||
@@ -152,12 +167,14 @@ /** | ||
*/ | ||
WebSocket.prototype.pong = function(data, options, dontFailWhenClosed) { | ||
if (this.readyState != WebSocket.OPEN) { | ||
if (this.readyState !== WebSocket.OPEN) { | ||
if (dontFailWhenClosed === true) return; | ||
throw new Error('not opened'); | ||
} | ||
options = options || {}; | ||
if (typeof options.mask == 'undefined') options.mask = !this._isServer; | ||
if (typeof options.mask === 'undefined') options.mask = !this._isServer; | ||
this._sender.pong(data, options); | ||
} | ||
}; | ||
@@ -169,7 +186,7 @@ /** | ||
*/ | ||
WebSocket.prototype.resume = function resume() { | ||
if (this.readyState !== WebSocket.OPEN) throw new Error('not opened'); | ||
WebSocket.prototype.resume = function() { | ||
if (this.readyState != WebSocket.OPEN) throw new Error('not opened'); | ||
return this._socket.resume(); | ||
} | ||
}; | ||
@@ -185,12 +202,14 @@ /** | ||
WebSocket.prototype.send = function(data, options, cb) { | ||
if (typeof options == 'function') { | ||
WebSocket.prototype.send = function send(data, options, cb) { | ||
if (typeof options === 'function') { | ||
cb = options; | ||
options = {}; | ||
} | ||
if (this.readyState != WebSocket.OPEN) { | ||
if (typeof cb == 'function') cb(new Error('not opened')); | ||
if (this.readyState !== WebSocket.OPEN) { | ||
if (typeof cb === 'function') cb(new Error('not opened')); | ||
else throw new Error('not opened'); | ||
return; | ||
} | ||
if (!data) data = ''; | ||
@@ -202,5 +221,7 @@ if (this._queue) { | ||
} | ||
options = options || {}; | ||
options.fin = true; | ||
if (typeof options.binary == 'undefined') { | ||
if (typeof options.binary === 'undefined') { | ||
options.binary = (data instanceof ArrayBuffer || data instanceof Buffer || | ||
@@ -216,14 +237,24 @@ data instanceof Uint8Array || | ||
} | ||
if (typeof options.mask == 'undefined') options.mask = !this._isServer; | ||
var readable = typeof stream.Readable == 'function' ? stream.Readable : stream.Stream; | ||
if (typeof options.mask === 'undefined') options.mask = !this._isServer; | ||
var readable = typeof stream.Readable === 'function' | ||
? stream.Readable | ||
: stream.Stream; | ||
if (data instanceof readable) { | ||
startQueue(this); | ||
var self = this; | ||
sendStream(this, data, options, function(error) { | ||
process.nextTick(function() { executeQueueSends(self); }); | ||
if (typeof cb == 'function') cb(error); | ||
sendStream(this, data, options, function send(error) { | ||
process.nextTick(function tock() { | ||
executeQueueSends(self); | ||
}); | ||
if (typeof cb === 'function') cb(error); | ||
}); | ||
} else { | ||
this._sender.send(data, options, cb); | ||
} | ||
else this._sender.send(data, options, cb); | ||
} | ||
}; | ||
@@ -237,25 +268,32 @@ /** | ||
*/ | ||
WebSocket.prototype.stream = function(options, cb) { | ||
if (typeof options == 'function') { | ||
WebSocket.prototype.stream = function stream(options, cb) { | ||
if (typeof options === 'function') { | ||
cb = options; | ||
options = {}; | ||
} | ||
var self = this; | ||
if (typeof cb != 'function') throw new Error('callback must be provided'); | ||
if (this.readyState != WebSocket.OPEN) { | ||
if (typeof cb == 'function') cb(new Error('not opened')); | ||
if (typeof cb !== 'function') throw new Error('callback must be provided'); | ||
if (this.readyState !== WebSocket.OPEN) { | ||
if (typeof cb === 'function') cb(new Error('not opened')); | ||
else throw new Error('not opened'); | ||
return; | ||
} | ||
if (this._queue) { | ||
this._queue.push(function() { self.stream(options, cb); }); | ||
this._queue.push(function () { self.stream(options, cb); }); | ||
return; | ||
} | ||
options = options || {}; | ||
if (typeof options.mask == 'undefined') options.mask = !this._isServer; | ||
if (typeof options.mask === 'undefined') options.mask = !this._isServer; | ||
startQueue(this); | ||
var send = function(data, final) { | ||
function send(data, final) { | ||
try { | ||
if (self.readyState != WebSocket.OPEN) throw new Error('not opened'); | ||
if (self.readyState !== WebSocket.OPEN) throw new Error('not opened'); | ||
options.fin = final === true; | ||
@@ -265,5 +303,4 @@ self._sender.send(data, options); | ||
else executeQueueSends(self); | ||
} | ||
catch (e) { | ||
if (typeof cb == 'function') cb(e); | ||
} catch (e) { | ||
if (typeof cb === 'function') cb(e); | ||
else { | ||
@@ -275,4 +312,5 @@ delete self._queue; | ||
} | ||
process.nextTick(cb.bind(null, null, send)); | ||
} | ||
}; | ||
@@ -284,10 +322,8 @@ /** | ||
*/ | ||
WebSocket.prototype.terminate = function terminate() { | ||
if (this.readyState === WebSocket.CLOSED) return; | ||
WebSocket.prototype.terminate = function() { | ||
if (this.readyState == WebSocket.CLOSED) return; | ||
if (this._socket) { | ||
try { | ||
// End the connection | ||
this._socket.end(); | ||
} | ||
// End the connection | ||
try { this._socket.end(); } | ||
catch (e) { | ||
@@ -303,4 +339,3 @@ // Socket error during end() call, so just destroy it right now | ||
this._closeTimer = setTimeout(cleanupWebsocketResources.bind(this, true), closeTimeout); | ||
} | ||
else if (this.readyState == WebSocket.CONNECTING) { | ||
} else if (this.readyState === WebSocket.CONNECTING) { | ||
cleanupWebsocketResources.call(this, true); | ||
@@ -315,3 +350,2 @@ } | ||
*/ | ||
Object.defineProperty(WebSocket.prototype, 'bufferedAmount', { | ||
@@ -333,3 +367,2 @@ get: function get() { | ||
*/ | ||
['open', 'error', 'close', 'message'].forEach(function(method) { | ||
@@ -343,3 +376,2 @@ Object.defineProperty(WebSocket.prototype, 'on' + method, { | ||
*/ | ||
get: function get() { | ||
@@ -357,3 +389,2 @@ var listener = this.listeners(method)[0]; | ||
*/ | ||
set: function set(listener) { | ||
@@ -375,30 +406,39 @@ this.removeAllListeners(method); | ||
var target = this; | ||
function onMessage (data, flags) { | ||
listener.call(target, new MessageEvent(data, flags.binary ? 'Binary' : 'Text', target)); | ||
} | ||
function onClose (code, message) { | ||
listener.call(target, new CloseEvent(code, message, target)); | ||
} | ||
function onError (event) { | ||
event.target = target; | ||
listener.call(target, event); | ||
} | ||
function onOpen () { | ||
listener.call(target, new OpenEvent(target)); | ||
} | ||
if (typeof listener === 'function') { | ||
if (method === 'message') { | ||
function onMessage (data, flags) { | ||
listener.call(this, new MessageEvent(data, flags.binary ? 'Binary' : 'Text', target)); | ||
} | ||
// store a reference so we can return the original function from the addEventListener hook | ||
// store a reference so we can return the original function from the | ||
// addEventListener hook | ||
onMessage._listener = listener; | ||
this.on(method, onMessage); | ||
} else if (method === 'close') { | ||
function onClose (code, message) { | ||
listener.call(this, new CloseEvent(code, message, target)); | ||
} | ||
// store a reference so we can return the original function from the addEventListener hook | ||
// store a reference so we can return the original function from the | ||
// addEventListener hook | ||
onClose._listener = listener; | ||
this.on(method, onClose); | ||
} else if (method === 'error') { | ||
function onError (event) { | ||
event.target = target; | ||
listener.call(this, event); | ||
} | ||
// store a reference so we can return the original function from the addEventListener hook | ||
// store a reference so we can return the original function from the | ||
// addEventListener hook | ||
onError._listener = listener; | ||
this.on(method, onError); | ||
} else if (method === 'open') { | ||
function onOpen () { | ||
listener.call(this, new OpenEvent(target)); | ||
} | ||
// store a reference so we can return the original function from the addEventListener hook | ||
// store a reference so we can return the original function from the | ||
// addEventListener hook | ||
onOpen._listener = listener; | ||
@@ -410,3 +450,3 @@ this.on(method, onOpen); | ||
} | ||
} | ||
}; | ||
@@ -419,5 +459,5 @@ module.exports = WebSocket; | ||
* @see http://www.w3.org/TR/html5/comms.html | ||
* @constructor | ||
* @api private | ||
*/ | ||
function MessageEvent(dataArg, typeArg, target) { | ||
@@ -433,7 +473,7 @@ this.data = dataArg; | ||
* @see http://www.w3.org/TR/html5/comms.html | ||
* @constructor | ||
* @api private | ||
*/ | ||
function CloseEvent(code, reason, target) { | ||
this.wasClean = (typeof code == 'undefined' || code == 1000); | ||
this.wasClean = (typeof code === 'undefined' || code === 1000); | ||
this.code = code; | ||
@@ -448,5 +488,5 @@ this.reason = reason; | ||
* @see http://www.w3.org/TR/html5/comms.html | ||
* @constructor | ||
* @api private | ||
*/ | ||
function OpenEvent(target) { | ||
@@ -460,3 +500,2 @@ this.target = target; | ||
*/ | ||
function initAsServerClient(req, socket, upgradeHead, options) { | ||
@@ -471,3 +510,3 @@ options = new Options({ | ||
this.protocolVersion = options.value.protocolVersion; | ||
this.supports.binary = (this.protocolVersion != 'hixie-76'); | ||
this.supports.binary = (this.protocolVersion !== 'hixie-76'); | ||
this.upgradeReq = req; | ||
@@ -478,4 +517,7 @@ this.readyState = WebSocket.CONNECTING; | ||
// establish connection | ||
if (options.value.protocolVersion == 'hixie-76') establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead); | ||
else establishConnection.call(this, Receiver, Sender, socket, upgradeHead); | ||
if (options.value.protocolVersion === 'hixie-76') { | ||
establishConnection.call(this, ReceiverHixie, SenderHixie, socket, upgradeHead); | ||
} else { | ||
establishConnection.call(this, Receiver, Sender, socket, upgradeHead); | ||
} | ||
} | ||
@@ -501,7 +543,8 @@ | ||
}).merge(options); | ||
if (options.value.protocolVersion != 8 && options.value.protocolVersion != 13) { | ||
if (options.value.protocolVersion !== 8 && options.value.protocolVersion !== 13) { | ||
throw new Error('unsupported protocol version'); | ||
} | ||
// verify url and establish http class | ||
// verify URL and establish http class | ||
var serverUrl = url.parse(address); | ||
@@ -519,3 +562,3 @@ var isUnixSocket = serverUrl.protocol === 'ws+unix:'; | ||
this.protocolVersion = options.value.protocolVersion; | ||
this.supports.binary = (this.protocolVersion != 'hixie-76'); | ||
this.supports.binary = (this.protocolVersion !== 'hixie-76'); | ||
@@ -531,5 +574,6 @@ // begin handshake | ||
var headerHost = serverUrl.hostname; | ||
// Append port number to Host and Origin header, only if specified in the url and non-default | ||
if(serverUrl.port) { | ||
if((isSecure && (port != 443)) || (!isSecure && (port != 80))){ | ||
// Append port number to Host and Origin header, only if specified in the url | ||
// and non-default | ||
if (serverUrl.port) { | ||
if ((isSecure && (port !== 443)) || (!isSecure && (port !== 80))){ | ||
headerHost = headerHost + ':' + port; | ||
@@ -554,3 +598,3 @@ } | ||
if (auth) { | ||
requestOptions.headers['Authorization'] = 'Basic ' + new Buffer(auth).toString('base64'); | ||
requestOptions.headers.Authorization = 'Basic ' + new Buffer(auth).toString('base64'); | ||
} | ||
@@ -563,3 +607,3 @@ | ||
if (options.value.host) { | ||
requestOptions.headers['Host'] = options.value.host; | ||
requestOptions.headers.Host = options.value.host; | ||
} | ||
@@ -608,3 +652,3 @@ | ||
if (options.value.protocolVersion < 13) requestOptions.headers['Sec-WebSocket-Origin'] = options.value.origin; | ||
else requestOptions.headers['Origin'] = options.value.origin; | ||
else requestOptions.headers.Origin = options.value.origin; | ||
} | ||
@@ -615,3 +659,3 @@ | ||
req.on('error', function(error) { | ||
req.on('error', function onerror(error) { | ||
self.emit('error', error); | ||
@@ -621,13 +665,16 @@ cleanupWebsocketResources.call(this, error); | ||
req.once('response', function(res) { | ||
req.once('response', function response(res) { | ||
var error; | ||
if (!self.emit('unexpected-response', req, res)) { | ||
var error = new Error('unexpected server response (' + res.statusCode + ')'); | ||
error = new Error('unexpected server response (' + res.statusCode + ')'); | ||
req.abort(); | ||
self.emit('error', error); | ||
} | ||
cleanupWebsocketResources.call(this, error); | ||
}); | ||
req.once('upgrade', function(res, socket, upgradeHead) { | ||
if (self.readyState == WebSocket.CLOSED) { | ||
req.once('upgrade', function upgrade(res, socket, upgradeHead) { | ||
if (self.readyState === WebSocket.CLOSED) { | ||
// client closed before server accepted connection | ||
@@ -639,4 +686,5 @@ self.emit('close'); | ||
} | ||
var serverKey = res.headers['sec-websocket-accept']; | ||
if (typeof serverKey == 'undefined' || serverKey !== expectedServerKey) { | ||
if (typeof serverKey === 'undefined' || serverKey !== expectedServerKey) { | ||
self.emit('error', 'invalid server key'); | ||
@@ -651,16 +699,18 @@ self.removeAllListeners(); | ||
var protError = null; | ||
if (!options.value.protocol && serverProt) { | ||
protError = 'server sent a subprotocol even though none requested'; | ||
protError = 'server sent a subprotocol even though none requested'; | ||
} else if (options.value.protocol && !serverProt) { | ||
protError = 'server sent no subprotocol even though requested'; | ||
protError = 'server sent no subprotocol even though requested'; | ||
} else if (serverProt && protList.indexOf(serverProt) === -1) { | ||
protError = 'server responded with an invalid protocol'; | ||
protError = 'server responded with an invalid protocol'; | ||
} | ||
if (protError) { | ||
self.emit('error', protError); | ||
self.removeAllListeners(); | ||
socket.end(); | ||
return; | ||
self.emit('error', protError); | ||
self.removeAllListeners(); | ||
socket.end(); | ||
return; | ||
} else if (serverProt) { | ||
self.protocol = serverProt; | ||
self.protocol = serverProt; | ||
} | ||
@@ -681,3 +731,5 @@ | ||
function establishConnection(ReceiverClass, SenderClass, socket, upgradeHead) { | ||
var ultron = this._ultron = new Ultron(socket); | ||
this._socket = socket; | ||
socket.setTimeout(0); | ||
@@ -689,9 +741,10 @@ socket.setNoDelay(true); | ||
// socket cleanup handlers | ||
socket.on('end', cleanupWebsocketResources.bind(this)); | ||
socket.on('close', cleanupWebsocketResources.bind(this)); | ||
socket.on('error', cleanupWebsocketResources.bind(this)); | ||
ultron.on('end', cleanupWebsocketResources.bind(this)); | ||
ultron.on('close', cleanupWebsocketResources.bind(this)); | ||
ultron.on('error', cleanupWebsocketResources.bind(this)); | ||
// ensure that the upgradeHead is added to the receiver | ||
function firstHandler(data) { | ||
if (self.readyState != WebSocket.OPEN) return; | ||
if (self.readyState !== WebSocket.OPEN) return; | ||
if (upgradeHead && upgradeHead.length > 0) { | ||
@@ -703,3 +756,5 @@ self.bytesReceived += upgradeHead.length; | ||
} | ||
dataHandler = realHandler; | ||
if (data) { | ||
@@ -710,2 +765,3 @@ self.bytesReceived += data.length; | ||
} | ||
// subsequent packets are pushed straight to the receiver | ||
@@ -716,3 +772,5 @@ function realHandler(data) { | ||
} | ||
var dataHandler = firstHandler; | ||
// if data was passed along with the http upgrade, | ||
@@ -726,26 +784,39 @@ // this will schedule a push of that on to the receiver. | ||
// receiver event handlers | ||
self._receiver.ontext = function (data, flags) { | ||
self._receiver.ontext = function ontext(data, flags) { | ||
flags = flags || {}; | ||
self.emit('message', data, flags); | ||
}; | ||
self._receiver.onbinary = function (data, flags) { | ||
self._receiver.onbinary = function onbinary(data, flags) { | ||
flags = flags || {}; | ||
flags.binary = true; | ||
self.emit('message', data, flags); | ||
}; | ||
self._receiver.onping = function(data, flags) { | ||
self._receiver.onping = function onping(data, flags) { | ||
flags = flags || {}; | ||
self.pong(data, {mask: !self._isServer, binary: flags.binary === true}, true); | ||
self.pong(data, { | ||
mask: !self._isServer, | ||
binary: flags.binary === true | ||
}, true); | ||
self.emit('ping', data, flags); | ||
}; | ||
self._receiver.onpong = function(data, flags) { | ||
self.emit('pong', data, flags); | ||
self._receiver.onpong = function onpong(data, flags) { | ||
self.emit('pong', data, flags || {}); | ||
}; | ||
self._receiver.onclose = function(code, data, flags) { | ||
self._receiver.onclose = function onclose(code, data, flags) { | ||
flags = flags || {}; | ||
self.close(code, data); | ||
}; | ||
self._receiver.onerror = function(reason, errorCode) { | ||
self._receiver.onerror = function onerror(reason, errorCode) { | ||
// close the connection when the receiver reports a HyBi error code | ||
self.close(typeof errorCode != 'undefined' ? errorCode : 1002, ''); | ||
self.close(typeof errorCode !== 'undefined' ? errorCode : 1002, ''); | ||
self.emit('error', reason, errorCode); | ||
@@ -756,10 +827,11 @@ }; | ||
this._sender = new SenderClass(socket); | ||
this._sender.on('error', function(error) { | ||
this._sender.on('error', function onerror(error) { | ||
self.close(1002, ''); | ||
self.emit('error', error); | ||
}); | ||
this.readyState = WebSocket.OPEN; | ||
this.emit('open'); | ||
socket.on('data', dataHandler); | ||
ultron.on('data', dataHandler); | ||
} | ||
@@ -773,3 +845,4 @@ | ||
var queue = instance._queue; | ||
if (typeof queue == 'undefined') return; | ||
if (typeof queue === 'undefined') return; | ||
delete instance._queue; | ||
@@ -782,5 +855,5 @@ for (var i = 0, l = queue.length; i < l; ++i) { | ||
function sendStream(instance, stream, options, cb) { | ||
stream.on('data', function(data) { | ||
if (instance.readyState != WebSocket.OPEN) { | ||
if (typeof cb == 'function') cb(new Error('not opened')); | ||
stream.on('data', function incoming(data) { | ||
if (instance.readyState !== WebSocket.OPEN) { | ||
if (typeof cb === 'function') cb(new Error('not opened')); | ||
else { | ||
@@ -792,8 +865,10 @@ delete instance._queue; | ||
} | ||
options.fin = false; | ||
instance._sender.send(data, options); | ||
}); | ||
stream.on('end', function() { | ||
if (instance.readyState != WebSocket.OPEN) { | ||
if (typeof cb == 'function') cb(new Error('not opened')); | ||
stream.on('end', function end() { | ||
if (instance.readyState !== WebSocket.OPEN) { | ||
if (typeof cb === 'function') cb(new Error('not opened')); | ||
else { | ||
@@ -805,5 +880,7 @@ delete instance._queue; | ||
} | ||
options.fin = true; | ||
instance._sender.send(null, options); | ||
if (typeof cb == 'function') cb(null); | ||
if (typeof cb === 'function') cb(null); | ||
}); | ||
@@ -813,4 +890,5 @@ } | ||
function cleanupWebsocketResources(error) { | ||
if (this.readyState == WebSocket.CLOSED) return; | ||
var emitClose = this.readyState != WebSocket.CONNECTING; | ||
if (this.readyState === WebSocket.CLOSED) return; | ||
var emitClose = this.readyState !== WebSocket.CONNECTING; | ||
this.readyState = WebSocket.CLOSED; | ||
@@ -820,18 +898,23 @@ | ||
this._closeTimer = null; | ||
if (emitClose) this.emit('close', this._closeCode || 1000, this._closeMessage || ''); | ||
if (emitClose) { | ||
this.emit('close', this._closeCode || 1000, this._closeMessage || ''); | ||
} | ||
if (this._socket) { | ||
this._socket.removeAllListeners(); | ||
// catch all socket error after removing all standard handlers | ||
var socket = this._socket; | ||
this._socket.on('error', function() { | ||
try { socket.destroy(); } catch (e) {} | ||
if (this._ultron) this._ultron.destroy(); | ||
this._socket.on('error', function onerror() { | ||
try { this.destroy(); } | ||
catch (e) {} | ||
}); | ||
try { | ||
if (!error) this._socket.end(); | ||
else this._socket.destroy(); | ||
} | ||
catch (e) { /* Ignore termination errors */ } | ||
} catch (e) { /* Ignore termination errors */ } | ||
this._socket = null; | ||
this._ultron = null; | ||
} | ||
if (this._sender) { | ||
@@ -841,2 +924,3 @@ this._sender.removeAllListeners(); | ||
} | ||
if (this._receiver) { | ||
@@ -846,5 +930,6 @@ this._receiver.cleanup(); | ||
} | ||
this.removeAllListeners(); | ||
this.on('error', function() {}); // catch all errors after this | ||
this.on('error', function onerror() {}); // catch all errors after this | ||
delete this._queue; | ||
} |
@@ -21,2 +21,4 @@ /*! | ||
function WebSocketServer(options, callback) { | ||
events.EventEmitter.call(this); | ||
options = new Options({ | ||
@@ -250,3 +252,3 @@ host: '0.0.0.0', | ||
callbackCalled = true; | ||
if (!result) abortConnection(socket, 404, 'Unauthorized') | ||
if (!result) abortConnection(socket, 401, 'Unauthorized'); | ||
else completeHybiUpgrade2(protocol); | ||
@@ -253,0 +255,0 @@ }); |
@@ -5,3 +5,4 @@ { | ||
"description": "simple to use, blazing fast and thoroughly tested websocket client, server and console for node.js, up-to-date against RFC-6455", | ||
"version": "0.4.32", | ||
"version": "0.5.0", | ||
"license": "MIT", | ||
"keywords": [ | ||
@@ -20,5 +21,2 @@ "Hixie", | ||
}, | ||
"bin": { | ||
"wscat": "./bin/wscat" | ||
}, | ||
"scripts": { | ||
@@ -28,17 +26,14 @@ "test": "make test", | ||
}, | ||
"engines": { | ||
"node": ">=0.4.0" | ||
}, | ||
"dependencies": { | ||
"commander": "~2.1.0", | ||
"nan": "~1.0.0", | ||
"tinycolor": "0.x", | ||
"options": ">=0.0.5" | ||
"nan": "1.4.x", | ||
"options": ">=0.0.5", | ||
"ultron": "1.0.x" | ||
}, | ||
"devDependencies": { | ||
"mocha": "1.12.0", | ||
"should": "1.2.x", | ||
"expect.js": "0.2.x", | ||
"ansi": "0.3.x", | ||
"benchmark": "0.3.x", | ||
"ansi": "latest" | ||
"expect.js": "0.3.x", | ||
"mocha": "2.0.x", | ||
"should": "4.3.x", | ||
"tinycolor": "0.0.x" | ||
}, | ||
@@ -45,0 +40,0 @@ "browser": "./lib/browser.js", |
178
README.md
@@ -1,40 +0,42 @@ | ||
[![Build Status](https://secure.travis-ci.org/einaros/ws.png)](http://travis-ci.org/einaros/ws) | ||
# ws: a node.js websocket library | ||
# ws: a node.js websocket library # | ||
[![Build Status](https://travis-ci.org/einaros/ws.svg?branch=master)](https://travis-ci.org/einaros/ws) | ||
`ws` is a simple to use websocket implementation, up-to-date against RFC-6455, and [probably the fastest WebSocket library for node.js](http://web.archive.org/web/20130314230536/http://hobbycoding.posterous.com/the-fastest-websocket-module-for-nodejs). | ||
`ws` is a simple to use WebSocket implementation, up-to-date against RFC-6455, | ||
and [probably the fastest WebSocket library for node.js][archive]. | ||
Passes the quite extensive Autobahn test suite. See http://einaros.github.com/ws for the full reports. | ||
Passes the quite extensive Autobahn test suite. See http://einaros.github.com/ws | ||
for the full reports. | ||
Comes with a command line utility, `wscat`, which can either act as a server (--listen), or client (--connect); Use it to debug simple websocket services. | ||
## Protocol support | ||
## Protocol support ## | ||
* **Hixie draft 76** (Old and deprecated, but still in use by Safari and Opera. | ||
Added to ws version 0.4.2, but server only. Can be disabled by setting the | ||
`disableHixie` option to true.) | ||
* **HyBi drafts 07-12** (Use the option `protocolVersion: 8`) | ||
* **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`) | ||
* **Hixie draft 76** (Old and deprecated, but still in use by Safari and Opera. Added to ws version 0.4.2, but server only. Can be disabled by setting the `disableHixie` option to true.) | ||
* **HyBi drafts 07-12** (Use the option `protocolVersion: 8`, or argument `-p 8` for wscat) | ||
* **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`, or argument `-p 13` for wscat) | ||
### Installing | ||
_See the echo.websocket.org example below for how to use the `protocolVersion` option._ | ||
``` | ||
npm install ---save ws | ||
``` | ||
## Usage ## | ||
### Sending and receiving text data | ||
### Installing ### | ||
`npm install ws` | ||
### Sending and receiving text data ### | ||
```js | ||
var WebSocket = require('ws'); | ||
var ws = new WebSocket('ws://www.host.com/path'); | ||
ws.on('open', function() { | ||
ws.send('something'); | ||
ws.on('open', function open() { | ||
ws.send('something'); | ||
}); | ||
ws.on('message', function(data, flags) { | ||
// flags.binary will be set if a binary data is received | ||
// flags.masked will be set if the data was masked | ||
// flags.binary will be set if a binary data is received. | ||
// flags.masked will be set if the data was masked. | ||
}); | ||
``` | ||
### Sending binary data ### | ||
### Sending binary data | ||
@@ -44,37 +46,47 @@ ```js | ||
var ws = new WebSocket('ws://www.host.com/path'); | ||
ws.on('open', function() { | ||
var array = new Float32Array(5); | ||
for (var i = 0; i < array.length; ++i) array[i] = i / 2; | ||
ws.send(array, {binary: true, mask: true}); | ||
ws.on('open', function open() { | ||
var array = new Float32Array(5); | ||
for (var i = 0; i < array.length; ++i) { | ||
array[i] = i / 2; | ||
} | ||
ws.send(array, { binary: true, mask: true }); | ||
}); | ||
``` | ||
Setting `mask`, as done for the send options above, will cause the data to be masked according to the websocket protocol. The same option applies for text data. | ||
Setting `mask`, as done for the send options above, will cause the data to be | ||
masked according to the WebSocket protocol. The same option applies for text | ||
data. | ||
### Server example ### | ||
### Server example | ||
```js | ||
var WebSocketServer = require('ws').Server | ||
, wss = new WebSocketServer({port: 8080}); | ||
wss.on('connection', function(ws) { | ||
ws.on('message', function(message) { | ||
console.log('received: %s', message); | ||
}); | ||
ws.send('something'); | ||
, wss = new WebSocketServer({ port: 8080 }); | ||
wss.on('connection', function connection(ws) { | ||
ws.on('message', function incoming(message) { | ||
console.log('received: %s', message); | ||
}); | ||
ws.send('something'); | ||
}); | ||
``` | ||
### Server sending broadcast data ### | ||
### Server sending broadcast data | ||
```js | ||
var WebSocketServer = require('ws').Server | ||
, wss = new WebSocketServer({port: 8080}); | ||
wss.broadcast = function(data) { | ||
for(var i in this.clients) | ||
this.clients[i].send(data); | ||
, wss = new WebSocketServer({ port: 8080 }); | ||
wss.broadcast = function broadcast(data) { | ||
for(var i in this.clients) { | ||
this.clients[i].send(data); | ||
} | ||
}; | ||
``` | ||
### Error handling best practices ### | ||
### Error handling best practices | ||
@@ -85,67 +97,65 @@ ```js | ||
// Errors (both immediate and async write errors) can be detected in an optional callback. | ||
// The callback is also the only way of being notified that data has actually been sent. | ||
ws.send('something', function(error) { | ||
// if error is null, the send has been completed, | ||
// otherwise the error object will indicate what failed. | ||
// Errors (both immediate and async write errors) can be detected in an optional | ||
// callback. The callback is also the only way of being notified that data has | ||
// actually been sent. | ||
ws.send('something', function ack(error) { | ||
// if error is null, the send has been completed, | ||
// otherwise the error object will indicate what failed. | ||
}); | ||
// Immediate errors can also be handled with try/catch-blocks, but **note** | ||
// that since sends are inherently asynchronous, socket write failures will *not* | ||
// be captured when this technique is used. | ||
try { | ||
ws.send('something'); | ||
} | ||
catch (e) { | ||
// handle error | ||
} | ||
// Immediate errors can also be handled with try/catch-blocks, but **note** that | ||
// since sends are inherently asynchronous, socket write failures will *not* be | ||
// captured when this technique is used. | ||
try { ws.send('something'); } | ||
catch (e) { /* handle error */ } | ||
``` | ||
### echo.websocket.org demo ### | ||
### echo.websocket.org demo | ||
```js | ||
var WebSocket = require('ws'); | ||
var ws = new WebSocket('ws://echo.websocket.org/', {protocolVersion: 8, origin: 'http://websocket.org'}); | ||
ws.on('open', function() { | ||
console.log('connected'); | ||
ws.send(Date.now().toString(), {mask: true}); | ||
var ws = new WebSocket('ws://echo.websocket.org/', { | ||
protocolVersion: 8, | ||
origin: 'http://websocket.org' | ||
}); | ||
ws.on('close', function() { | ||
console.log('disconnected'); | ||
ws.on('open', function open() { | ||
console.log('connected'); | ||
ws.send(Date.now().toString(), {mask: true}); | ||
}); | ||
ws.on('message', function(data, flags) { | ||
console.log('Roundtrip time: ' + (Date.now() - parseInt(data)) + 'ms', flags); | ||
setTimeout(function() { | ||
ws.send(Date.now().toString(), {mask: true}); | ||
}, 500); | ||
ws.on('close', function close() { | ||
console.log('disconnected'); | ||
}); | ||
``` | ||
### wscat against echo.websocket.org ### | ||
ws.on('message', function message(data, flags) { | ||
console.log('Roundtrip time: ' + (Date.now() - parseInt(data)) + 'ms', flags); | ||
$ npm install -g ws | ||
$ wscat -c ws://echo.websocket.org | ||
connected (press CTRL+C to quit) | ||
> hi there | ||
< hi there | ||
> are you a happy parrot? | ||
< are you a happy parrot? | ||
setTimeout(function timeout() { | ||
ws.send(Date.now().toString(), {mask: true}); | ||
}, 500); | ||
}); | ||
``` | ||
### Other examples ### | ||
### Other examples | ||
For a full example with a browser client communicating with a ws server, see the examples folder. | ||
For a full example with a browser client communicating with a ws server, see the | ||
examples folder. | ||
Note that the usage together with Express 3.0 is quite different from Express 2.x. The difference is expressed in the two different serverstats-examples. | ||
Note that the usage together with Express 3.0 is quite different from Express | ||
2.x. The difference is expressed in the two different serverstats-examples. | ||
Otherwise, see the test cases. | ||
### Running the tests ### | ||
### Running the tests | ||
`make test` | ||
``` | ||
make test | ||
``` | ||
## API Docs ## | ||
## API Docs | ||
See the doc/ directory for Node.js-like docs for the ws classes. | ||
## License ## | ||
## License | ||
@@ -174,1 +184,3 @@ (The MIT License) | ||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
[archive]: http://web.archive.org/web/20130314230536/http://hobbycoding.posterous.com/the-fastest-websocket-module-for-nodejs |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No License Found
License(Experimental) License information could not be found.
Found 1 instance in 1 package
110182
3
27
0
2386
184
6
+ Addedultron@1.0.x
+ Addednan@1.4.3(transitive)
+ Addedultron@1.0.2(transitive)
- Removedcommander@~2.1.0
- Removedtinycolor@0.x
- Removedcommander@2.1.0(transitive)
- Removednan@1.0.0(transitive)
- Removedtinycolor@0.0.1(transitive)
Updatednan@1.4.x