Compare commits

...

37 Commits
0.4.1 ... 0.5

Author SHA1 Message Date
Guillermo Rauch
ae159b5f7d Updated README 2010-07-29 01:57:59 -07:00
Guillermo Rauch
e738e7c26e Added json.js for the chat example on IE 2010-07-29 01:48:59 -07:00
Guillermo Rauch
4e7db51c8c Had accidentally removed an all-too-important line. Fix for htmlfile 2010-07-28 22:45:16 -07:00
Guillermo Rauch
f87d883d79 Updated client
heartbitTimeout > heartbeatTimeout
2010-07-28 21:25:10 -07:00
Guillermo Rauch
0a2d007179 Detect disconnections of xhr-polling faster, by listening on end event (if the connection is forcibly closed by the UA before the 20 seconds of polling pass).
Possible speed optimization noted
Make sure disconnection timeouts are not called more than once (check for .connected in onClose)
2010-07-28 20:31:34 -07:00
Guillermo Rauch
223c58b248 timeout and heartbeatInterval options moved to client options
Disabled heartbeats for xhr-polling, since we can leverage close timeout instead (connections are reopened frequently)
2010-07-28 20:09:18 -07:00
Guillermo Rauch
4ea73fc889 Removed legacy __super__ call 2010-07-28 20:01:26 -07:00
Guillermo Rauch
830fe231d5 Escaping for hax0rs 2010-07-28 19:51:35 -07:00
Guillermo Rauch
79faa40dbf Temporarily dropping the unicode characters because flash socket has no support. 2010-07-28 19:44:55 -07:00
Guillermo Rauch
303b485775 Escape quotes for htmlfile 2010-07-28 19:34:34 -07:00
Guillermo Rauch
4322dd23de Make sure to pass the unframed message to _onHeartbeat
Fixed string-number heartbeat comparison
2010-07-28 18:42:13 -07:00
Guillermo Rauch
9686771535 Fixed bogus this reference 2010-07-28 18:27:04 -07:00
Guillermo Rauch
7da19a2c38 Added heartbeatInterval option
Added default timeout with 8000ms
Make sure to re-trigger the heartbeat timeout after validating one
2010-07-28 18:10:07 -07:00
Guillermo Rauch
15613de7d4 Chat example correction
Make sure Flashsocket calls WebSocket constructor, not Client
Adapted framing to match client's
2010-07-28 16:53:46 -07:00
Guillermo Rauch
3d11b176fe Expose listener class
Bumped node version
Updated client
Make sure constructors are called
Added tests
2010-07-28 16:33:41 -07:00
Guillermo Rauch
910de83942 Added node-websocket-client for testing 2010-07-28 14:31:04 -07:00
Guillermo Rauch
b2c0b78130 Fixed example
Fixed broken references
2010-07-28 14:29:50 -07:00
Guillermo Rauch
22ef7918b3 Changed chat example to match the new more node-friendly syntax
Clients now fire `disconnect` event.
2010-07-28 11:44:10 -07:00
Guillermo Rauch
7b1dba192c Updated client 2010-07-28 11:29:18 -07:00
Guillermo Rauch
18d78f5448 Merge branch 'master' of github.com:LearnBoost/Socket.IO-node
Conflicts:
	lib/socket.io/client.js
2010-07-28 11:16:28 -07:00
Guillermo Rauch
0edd16d575 Clients now emit message event and inherit EventEmitter
More encoding tests
Encoding fix for non-array arguments
Leveraged Array.isArray
Handling of undefined and null values
2010-07-28 11:14:58 -07:00
Guillermo Rauch
41100e5a51 Clients now emit message event and inherit EventEmitter 2010-07-28 10:58:08 -07:00
Guillermo Rauch
7b3781c2c8 Updated protocol notes 2010-07-28 10:09:43 -07:00
Guillermo Rauch
deb59f6725 Deleted DS_Store committed accidentally 2010-07-28 10:05:48 -07:00
Guillermo Rauch
f95988ff59 README clarification 2010-07-28 10:04:20 -07:00
Guillermo Rauch
fc40156a8c Fixed encoding / decoding
Moved library files into lib/socket.io for future npm support
Added tests for encoding / decoding
Fixed inheritance of certain classes
Fixed options supports
2010-07-28 09:59:19 -07:00
Guillermo Rauch
ce6fa8611b Path restructuring 2010-07-19 15:13:11 -07:00
Guillermo Rauch
5ba6035260 Removed oo requirement / global
Removed sys requirement
2010-07-19 15:10:31 -07:00
Guillermo Rauch
18068b56cd Added frame after message length
Make sure message is always a string
2010-07-19 03:00:25 -07:00
Guillermo Rauch
ca4589d929 Removed dependency on js-oo
Removed legacy utilities and simplified into utils.js
More usage of module.exports
2010-07-19 02:50:33 -07:00
Guillermo Rauch
dc08ca5fb6 Switched main classes to module exports 2010-07-19 02:16:40 -07:00
Guillermo Rauch
fa90d2c520 Added expresso submodule for testing 2010-07-19 02:11:11 -07:00
Guillermo Rauch
dc960f9bc6 Moving test to example 2010-07-19 02:08:01 -07:00
Guillermo Rauch
1f75828eb7 Updated README 2010-07-19 02:06:24 -07:00
Guillermo Rauch
0a9743d56f Updating client submodule to new location 2010-07-19 02:05:56 -07:00
Guillermo Rauch
0985193ae1 Decoding checks for proper framing 2010-07-19 02:04:43 -07:00
Guillermo Rauch
1aa7845c62 New timeout system based on heartbeats 2010-07-19 01:55:44 -07:00
26 changed files with 725 additions and 676 deletions

13
.gitmodules vendored
View File

@@ -1,6 +1,9 @@
[submodule "lib/vendor/js-oo"]
path = lib/vendor/js-oo
url = git://github.com/visionmedia/js-oo.git
[submodule "test/client"]
path = test/client
[submodule "example/client"]
path = example/client
url = git://github.com/LearnBoost/Socket.IO.git
[submodule "tests/support/expresso"]
path = tests/support/expresso
url = git://github.com/visionmedia/expresso.git
[submodule "tests/support/node-websocket-client"]
path = tests/support/node-websocket-client
url = git://github.com/pgriess/node-websocket-client.git

2
Makefile Normal file
View File

@@ -0,0 +1,2 @@
test:
./tests/support/expresso/bin/expresso -I lib $(TESTFLAGS) tests/*.test.js

View File

@@ -11,7 +11,7 @@ The `Socket.IO` server provides seamless supports for a variety of transports in
Requirements
------------
- Node v0.1.94+
- Node v0.1.102+
- [Socket.IO client](http://github.com/LearnBoost/Socket.IO) to connect from the browser
How to use
@@ -48,7 +48,7 @@ Another, once cloned
## Demo
To run the demo, go to `test` directory and run
To run the demo, go to `example` directory and run
sudo node server.js
@@ -176,7 +176,7 @@ One of the design goals is that you should be able to implement whatever protoco
* Message batching
In order to optimize the resources, messages are batched. In the event of the server trying to send multiple messages while the client is temporarily disconnected (ie: xhr polling), and messages are stacked, or messages being stacked prior to the handshake being successful, a JSON object containing a list (array) of messages is sent to the client.
In order to optimize the resources, messages are buffered. In the event of the server trying to send multiple messages while the client is temporarily disconnected (eg: xhr polling), messages are stacked, then encoded in a lightweight way and sent to the client whenever he becomes available.
Despite this extra layer, your messages are delivered unaltered to the different event listeners. You can JSON.stringify() objects, send XML, or maybe plain text.

View File

@@ -3,6 +3,7 @@
<head>
<title>socket.io client test</title>
<script src="/json.js"></script> <!-- for ie -->
<script src="/client/socket.io.js"></script>
</head>
<body>
@@ -12,8 +13,8 @@
function message(obj){
var el = document.createElement('p');
if ('announcement' in obj) el.innerHTML = '<em>' + obj.announcement;
else if ('message' in obj) el.innerHTML = '<b>' + obj.message[0] + ':</b> ' + obj.message[1];
if ('announcement' in obj) el.innerHTML = '<em>' + esc(obj.announcement) + '</em>';
else if ('message' in obj) el.innerHTML = '<b>' + esc(obj.message[0]) + ':</b> ' + esc(obj.message[1]);
document.getElementById('chat').appendChild(el);
document.getElementById('chat').scrollTop = 1000000;
}
@@ -24,6 +25,10 @@
message({ message: ['you', val] });
document.getElementById('text').value = '';
}
function esc(msg){
return msg.replace(/</g, '&lt;').replace(/>/g, '&gt;');
};
var socket = new io.Socket(null, {rememberTransport: false, port: 8080});
socket.connect();

1
example/client Submodule

Submodule example/client added at 7a5e2b8cac

18
example/json.js Normal file
View File

@@ -0,0 +1,18 @@
if(!this.JSON){JSON=function(){function f(n){return n<10?'0'+n:n;}
Date.prototype.toJSON=function(){return this.getUTCFullYear()+'-'+
f(this.getUTCMonth()+1)+'-'+
f(this.getUTCDate())+'T'+
f(this.getUTCHours())+':'+
f(this.getUTCMinutes())+':'+
f(this.getUTCSeconds())+'Z';};var m={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};function stringify(value,whitelist){var a,i,k,l,r=/["\\\x00-\x1f\x7f-\x9f]/g,v;switch(typeof value){case'string':return r.test(value)?'"'+value.replace(r,function(a){var c=m[a];if(c){return c;}
c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+
(c%16).toString(16);})+'"':'"'+value+'"';case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
if(typeof value.toJSON==='function'){return stringify(value.toJSON());}
a=[];if(typeof value.length==='number'&&!(value.propertyIsEnumerable('length'))){l=value.length;for(i=0;i<l;i+=1){a.push(stringify(value[i],whitelist)||'null');}
return'['+a.join(',')+']';}
if(whitelist){l=whitelist.length;for(i=0;i<l;i+=1){k=whitelist[i];if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}else{for(k in value){if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}
return'{'+a.join(',')+'}';}}
return{stringify:stringify,parse:function(text,filter){var j;function walk(k,v){var i,n;if(v&&typeof v==='object'){for(i in v){if(Object.prototype.hasOwnProperty.apply(v,[i])){n=walk(i,v[i]);if(n!==undefined){v[i]=n;}}}}
return filter(k,v);}
if(/^[\],:{}\s]*$/.test(text.replace(/\\./g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof filter==='function'?walk('',j):j;}
throw new SyntaxError('parseJSON');}};}();}

View File

@@ -1,7 +1,7 @@
var http = require('http'),
url = require('url'),
fs = require('fs'),
io = require('../lib/socket.io'),
io = require('../'),
sys = require('sys'),
send404 = function(res){
@@ -29,7 +29,7 @@ server = http.createServer(function(req, res){
res.end();
} catch(e){
send404(res);
}
}
break;
}
@@ -42,26 +42,22 @@ server.listen(8080);
// socket.io, I choose you
// simplest chat application evar
var buffer = [], json = JSON.stringify;
var buffer = [],
json = JSON.stringify,
io = io.listen(server);
io.on('connection', function(client){
client.send(json({ buffer: buffer }));
client.broadcast(json({ announcement: client.sessionId + ' connected' }));
io.listen(server, {
onClientConnect: function(client){
client.send(json({ buffer: buffer }));
client.broadcast(json({ announcement: client.sessionId + ' connected' }));
},
onClientDisconnect: function(client){
client.broadcast(json({ announcement: client.sessionId + ' disconnected' }));
},
onClientMessage: function(message, client){
client.on('message', function(message){
var msg = { message: [client.sessionId, message] };
buffer.push(msg);
if (buffer.length > 15) {
buffer.shift();
}
if (buffer.length > 15) buffer.shift();
client.broadcast(json(msg));
}
});
client.on('disconnect', function(){
client.broadcast(json({ announcement: client.sessionId + ' disconnected' }));
});
});

4
index.js Normal file
View File

@@ -0,0 +1,4 @@
exports.Listener = require('./lib/socket.io/listener');
exports.listen = function(server, options){
return new exports.Listener(server, options);
};

View File

@@ -1,9 +0,0 @@
require.paths.unshift(__dirname + '/vendor/js-oo/lib');
require('oo');
var sys = require('sys'),
Listener = require('./socket.io/listener').Listener;
this.listen = function(server, options){
return new Listener(server, options);
};

View File

@@ -1,131 +1,172 @@
var options = require('./util/options').options, urlparse = require('url').parse;
var urlparse = require('url').parse,
options = require('./utils').options,
frame = '~m~',
exports.Client = Class({
Client = module.exports = function(listener, req, res, options, head){
process.EventEmitter.call(this);
this.listener = listener;
this.options({
timeout: 8000,
heartbeatInterval: 10000,
closeTimeout: 0
}, options);
this.connections = 0;
this.connected = false;
this._heartbeats = 0;
this.upgradeHead = head;
this._onConnect(req, res);
};
require('sys').inherits(Client, process.EventEmitter);
Client.prototype.send = function(message){
if (!this.connected || !(this.connection.readyState === 'open' ||
this.connection.readyState === 'writeOnly')){
return this._queue(message);
}
this._write(this._encode(message));
return this;
};
Client.prototype.broadcast = function(message){
if (!('sessionId' in this)) return this;
this.listener.broadcast(message, this.sessionId);
return this;
};
Client.prototype._onMessage = function(data){
var messages = this._decode(data);
if (messages === false) return this.listener.options.log('Bad message received from client ' + this.sessionId);
for (var i = 0, l = messages.length; i < l; i++){
if (messages[i].substr(0, 3) == '~h~'){
return this._onHeartbeat(messages[i].substr(3));
}
this.emit('message', messages[i]);
this.listener._onClientMessage(messages[i], this);
}
};
Client.prototype._onConnect = function(req, res){
var self = this;
this.request = req;
this.response = res;
this.connection = this.request.connection;
if (this._disconnectTimeout) clearTimeout(this._disconnectTimeout);
};
include: [options],
Client.prototype._encode = function(messages){
var ret = '', message,
messages = Array.isArray(messages) ? messages : [messages];
for (var i = 0, l = messages.length; i < l; i++){
message = messages[i] === null || messages[i] === undefined ? '' : String(messages[i]);
ret += frame + message.length + frame + message;
}
return ret;
};
options: {
closeTimeout: 0,
heartbeatInterval: 5000
},
init: function(listener, req, res, options, head){
this.listener = listener;
this.setOptions(options);
this.connections = 0;
this.connected = false;
this.upgradeHead = head;
this._onConnect(req, res);
},
send: function(message){
if (!this.connected || !(this.connection.readyState === 'open' ||
this.connection.readyState === 'writeOnly')) {
return this._queue(message);
Client.prototype._decode = function(data){
var messages = [], number, n;
do {
if (data.substr(0, 3) !== frame) return messages;
data = data.substr(3);
number = '', n = '';
for (var i = 0, l = data.length; i < l; i++){
n = Number(data.substr(i, 1));
if (data.substr(i, 1) == n){
number += n;
} else {
data = data.substr(number.length + frame.length)
number = Number(number);
break;
}
}
this._write(JSON.stringify({messages: [message]}));
return this;
},
messages.push(data.substr(0, number)); // here
data = data.substr(number);
} while(data !== '');
return messages;
};
Client.prototype._payload = function(){
var payload = [];
this.connections++;
this.connected = true;
if (!this.handshaked){
this._generateSessionId();
payload.push(this.sessionId);
this.handshaked = true;
}
payload = payload.concat(this._writeQueue || []);
this._writeQueue = [];
if (payload.length) this._write(this._encode(payload));
if (this.connections === 1) this.listener._onClientConnect(this);
if (this.options.timeout) this._heartbeat();
};
Client.prototype._heartbeat = function(){
var self = this;
setTimeout(function(){
self.send('~h~' + ++self._heartbeats);
self._heartbeatTimeout = setTimeout(function(){
self._onClose();
}, self.options.timeout);
}, self.options.heartbeatInterval);
};
Client.prototype._onHeartbeat = function(h){
if (h == this._heartbeats){
clearTimeout(this._heartbeatTimeout);
this._heartbeat();
}
};
broadcast: function(message){
if (!('sessionId' in this)) {
return this;
}
this.listener.broadcast(message, this.sessionId);
return this;
},
_onMessage: function(data){
try {
var messages = JSON.parse(data);
} catch(e){
return this.listener.options.log('Bad message received from client ' + this.sessionId);
}
for (var i = 0, l = messages.length; i < l; i++){
this.listener._onClientMessage(messages[i], this);
}
},
_onConnect: function(req, res){
Client.prototype._onClose = function(){
if (this.connected){
var self = this;
this.request = req;
this.response = res;
this.connection = this.request.connection;
if (this._disconnectTimeout) {
clearTimeout(this._disconnectTimeout);
}
},
_payload: function(){
var payload = [];
this.connections++;
this.connected = true;
if (!this.handshaked){
this._generateSessionId();
payload.push(JSON.stringify({
sessionid: this.sessionId
}));
this.handshaked = true;
}
payload = payload.concat(this._writeQueue || []);
this._writeQueue = [];
if (payload.length) {
this._write(JSON.stringify({messages: payload}));
}
if (this.connections === 1) {
this.listener._onClientConnect(this);
}
},
_onClose: function(){
var self = this;
if (this._heartbeatInterval) {
clearInterval(this._heartbeatInterval);
}
if ('_heartbeatTimeout' in this) clearTimeout(this._heartbeatTimeout);
this.connected = false;
this._disconnectTimeout = setTimeout(function(){
self._onDisconnect();
}, this.options.closeTimeout);
},
_onDisconnect: function(){
if (!this.finalized){
this._writeQueue = [];
this.connected = false;
this.finalized = true;
if (this.handshaked) {
this.listener._onClientDisconnect(this);
}
}
},
_queue: function(message){
if (!('_writeQueue' in this)) {
this._writeQueue = [];
}
this._writeQueue.push(message);
return this;
},
_generateSessionId: function(){
if (this.sessionId) {
return this.listener.options.log('This client already has a session id');
}
this.sessionId = Math.random().toString().substr(2);
return this;
},
_verifyOrigin: function(origin){
var parts = urlparse(origin), origins = this.listener.options.origins;
return origins.indexOf('*:*') !== -1 ||
origins.indexOf(parts.host + ':' + parts.port) !== -1 ||
origins.indexOf(parts.host + ':*') !== -1 ||
origins.indexOf('*:' + parts.port) !== -1;
}
};
});
Client.prototype._onDisconnect = function(){
if (!this.finalized){
this._writeQueue = [];
this.connected = false;
this.finalized = true;
if (this.handshaked){
this.emit('disconnect');
this.listener._onClientDisconnect(this);
}
}
};
Client.prototype._queue = function(message){
if (!('_writeQueue' in this)){
this._writeQueue = [];
}
this._writeQueue.push(message);
return this;
};
Client.prototype._generateSessionId = function(){
if (this.sessionId) return this.listener.options.log('This client already has a session id');
this.sessionId = Math.random().toString().substr(2);
return this;
};
Client.prototype._verifyOrigin = function(origin){
var parts = urlparse(origin), origins = this.listener.options.origins;
return origins.indexOf('*:*') !== -1 ||
origins.indexOf(parts.host + ':' + parts.port) !== -1 ||
origins.indexOf(parts.host + ':*') !== -1 ||
origins.indexOf('*:' + parts.port) !== -1;
};
for (var i in options) Client.prototype[i] = options[i];

View File

@@ -1,119 +1,124 @@
var url = require('url'),
options = require('./util/options').options,
Client = require('./client').Client,
transports = {};
sys = require('sys'),
options = require('./utils').options,
Client = require('./client'),
transports = {
'flashsocket': require('./transports/flashsocket'),
'htmlfile': require('./transports/htmlfile'),
'websocket': require('./transports/websocket'),
'xhr-multipart': require('./transports/xhr-multipart'),
'xhr-polling': require('./transports/xhr-polling')
},
var Listener = exports.Listener = Class({
include: [process.EventEmitter.prototype, options],
options: {
Listener = module.exports = function(server, options){
process.EventEmitter.call(this);
var self = this;
this.server = server;
this.options({
origins: '*:*',
resource: 'socket.io',
transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling'],
transportOptions: {},
transportOptions: {
'xhr-polling': {
timeout: null, // no heartbeats for polling
closeTimeout: 8000,
duration: 20000
}
},
log: function(message){
require('sys').log(message);
}
},
init: function(server, options){
var self = this;
process.EventEmitter.call(this);
this.server = server;
this.setOptions(options);
this.clients = [];
this.clientsIndex = {};
var listeners = this.server.listeners('request');
this.server.removeAllListeners('request');
this.server.addListener('request', function(req, res){
if (self.check(req, res)) return;
for (var i = 0; i < listeners.length; i++) {
listeners[i].call(this, req, res);
}
});
this.server.addListener('upgrade', function(req, socket, head){
if (!self.check(req, socket, true, head)){
socket.destroy();
}
});
this.options.transports.forEach(function(t){
if (!(t in transports)){
transports[t] = require('./transports/' + t)[t];
if (transports[t].init) transports[t].init(this);
}
}, this);
this.options.log('socket.io ready - accepting connections');
},
broadcast: function(message, except){
for (var i = 0, l = this.clients.length; i < l; i++){
if (this.clients[i] && (!except || [].concat(except).indexOf(this.clients[i].sessionId) == -1)){
this.clients[i].send(message);
}
}, options);
this.clients = [];
this.clientsIndex = {};
var listeners = this.server.listeners('request');
this.server.removeAllListeners('request');
this.server.addListener('request', function(req, res){
if (self.check(req, res)) return;
for (var i = 0; i < listeners.length; i++){
listeners[i].call(this, req, res);
}
return this;
},
check: function(req, res, httpUpgrade, head){
var path = url.parse(req.url).pathname, parts, cn;
if (path.indexOf('/' + this.options.resource) === 0){
parts = path.substr(1).split('/');
if (parts[2]){
cn = this._lookupClient(parts[2]);
if (cn){
cn._onConnect(req, res);
} else {
req.connection.end();
this.options.log('Couldnt find client with session id "' + parts[2] + '"');
}
} else {
this._onConnection(parts[1], req, res, httpUpgrade, head);
}
return true;
});
this.server.addListener('upgrade', function(req, socket, head){
if (!self.check(req, socket, true, head)){
socket.destroy();
}
return false;
},
});
_lookupClient: function(sid){
return this.clientsIndex[sid];
},
_onClientConnect: function(client){
if (!(client instanceof Client) || !client.sessionId){
return this.options.log('Invalid client');
}
client.i = this.clients.length;
this.clients.push(client);
this.clientsIndex[client.sessionId] = client;
this.options.log('Client '+ client.sessionId +' connected');
this.emit('clientConnect', client);
},
_onClientMessage: function(data, client){
this.emit('clientMessage', data, client);
},
_onClientDisconnect: function(client){
this.clientsIndex[client.sessionId] = null;
this.clients[client.i] = null;
this.options.log('Client '+ client.sessionId +' disconnected');
this.emit('clientDisconnect', client);
},
// new connections (no session id)
_onConnection: function(transport, req, res, httpUpgrade, head){
if (this.options.transports.indexOf(transport) === -1 || (httpUpgrade && !transports[transport].httpUpgrade)){
httpUpgrade ? res.destroy() : req.connection.destroy();
return this.options.log('Illegal transport "'+ transport +'"');
}
this.options.log('Initializing client with transport "'+ transport +'"');
new transports[transport](this, req, res, this.options.transportOptions[transport], head);
for (var i in transports){
if ('init' in transports[i]) transports[i].init(this);
}
});
this.options.log('socket.io ready - accepting connections');
};
sys.inherits(Listener, process.EventEmitter);
for (var i in options) Listener.prototype[i] = options[i];
Listener.prototype.broadcast = function(message, except){
for (var i = 0, l = this.clients.length; i < l; i++){
if (this.clients[i] && (!except || [].concat(except).indexOf(this.clients[i].sessionId) == -1)){
this.clients[i].send(message);
}
}
return this;
};
Listener.prototype.check = function(req, res, httpUpgrade, head){
var path = url.parse(req.url).pathname, parts, cn;
if (path.indexOf('/' + this.options.resource) === 0){
parts = path.substr(1).split('/');
if (parts[2]){
cn = this._lookupClient(parts[2]);
if (cn){
cn._onConnect(req, res);
} else {
req.connection.end();
this.options.log('Couldnt find client with session id "' + parts[2] + '"');
}
} else {
this._onConnection(parts[1], req, res, httpUpgrade, head);
}
return true;
}
return false;
};
Listener.prototype._lookupClient = function(sid){
return this.clientsIndex[sid];
};
Listener.prototype._onClientConnect = function(client){
if (!(client instanceof Client) || !client.sessionId){
return this.options.log('Invalid client');
}
client.i = this.clients.length;
this.clients.push(client);
this.clientsIndex[client.sessionId] = client;
this.options.log('Client '+ client.sessionId +' connected');
this.emit('clientConnect', client);
this.emit('connection', client);
};
Listener.prototype._onClientMessage = function(data, client){
this.emit('clientMessage', data, client);
};
Listener.prototype._onClientDisconnect = function(client){
this.clientsIndex[client.sessionId] = null;
this.clients[client.i] = null;
this.options.log('Client '+ client.sessionId +' disconnected');
this.emit('clientDisconnect', client);
};
Listener.prototype._onConnection = function(transport, req, res, httpUpgrade, head){
if (this.options.transports.indexOf(transport) === -1 || (httpUpgrade && !transports[transport].httpUpgrade)){
httpUpgrade ? res.destroy() : req.connection.destroy();
return this.options.log('Illegal transport "'+ transport +'"');
}
this.options.log('Initializing client with transport "'+ transport +'"');
new transports[transport](this, req, res, this.options.transportOptions[transport], head);
};

View File

@@ -1,25 +1,39 @@
var websocket = require('./websocket').websocket,
net = require('net'),
listeners = [];
var net = require('net'),
WebSocket = require('./websocket'),
listeners = [],
netserver,
exports.flashsocket = websocket.extend({});
exports.flashsocket.httpUpgrade = true;
exports.flashsocket.init = function(listener){
listeners.push(listener);
Flashsocket = module.exports = function(){
WebSocket.apply(this, arguments);
};
net.createServer(function(socket){
socket.write('<?xml version="1.0"?>\n');
socket.write('<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">\n');
socket.write('<cross-domain-policy>\n');
require('sys').inherits(Flashsocket, WebSocket);
listeners.forEach(function(l){
[].concat(l.options.origins).forEach(function(origin){
var parts = origin.split(':');
socket.write('<allow-access-from domain="' + parts[0] + '" to-ports="'+ parts[1] +'"/>\n');
});
Flashsocket.httpUpgrade = true;
Flashsocket.init = function(listener){
listeners.push(listener);
listener.server.on('close', function(){
try {
netserver.close();
} catch(e){}
});
};
socket.write('</cross-domain-policy>\n');
socket.end();
}).listen(843);
try {
netserver = net.createServer(function(socket){
socket.write('<?xml version="1.0"?>\n');
socket.write('<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">\n');
socket.write('<cross-domain-policy>\n');
listeners.forEach(function(l){
[].concat(l.options.origins).forEach(function(origin){
var parts = origin.split(':');
socket.write('<allow-access-from domain="' + parts[0] + '" to-ports="'+ parts[1] +'"/>\n');
});
});
socket.write('</cross-domain-policy>\n');
socket.end();
}).listen(843);
} catch(e){}

View File

@@ -1,50 +1,48 @@
var Client = require('../client').Client,
qs = require('querystring');
var Client = require('../client'),
qs = require('querystring'),
exports.htmlfile = Client.extend({
HTMLFile = module.exports = function(){
Client.apply(this, arguments);
};
require('sys').inherits(HTMLFile, Client);
_onConnect: function(req, res){
var self = this, body = '';
switch (req.method){
case 'GET':
this.__super__(req, res);
this.request.connection.addListener('close', function(){ self._onClose(); });
this.response.useChunkedEncodingByDefault = true;
this.response.shouldKeepAlive = true;
this.response.writeHead(200, {
'Content-Type': 'text/html',
'Connection': 'keep-alive',
'Transfer-Encoding': 'chunked'
});
this.response.write('<html><body>' + new Array(244).join(' '));
if ('flush' in this.response) this.response.flush();
this._payload();
this._heartbeatInterval = setInterval(function(){
self.response.write('<!-- heartbeat -->');
if ('flush' in self.response) self.response.flush();
}, this.options.heartbeatInterval);
break;
case 'POST':
req.addListener('data', function(message){
body += message;
});
req.addListener('end', function(){
try {
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){}
res.writeHead(200);
res.write('ok');
res.end();
});
break;
}
},
_write: function(message){
this.response.write('<script>parent.s._('+ message +', document);</script>');
if ('flush' in this.response) this.response.flush();
HTMLFile.prototype._onConnect = function(req, res){
var self = this, body = '';
switch (req.method){
case 'GET':
Client.prototype._onConnect.apply(this, [req, res]);
this.request.connection.addListener('close', function(){ self._onClose(); });
this.response.useChunkedEncodingByDefault = true;
this.response.shouldKeepAlive = true;
this.response.writeHead(200, {
'Content-Type': 'text/html',
'Connection': 'keep-alive',
'Transfer-Encoding': 'chunked'
});
this.response.write('<html><body>' + new Array(244).join(' '));
if ('flush' in this.response) this.response.flush();
this._payload();
break;
case 'POST':
req.addListener('data', function(message){
body += message;
});
req.addListener('end', function(){
try {
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){}
res.writeHead(200);
res.write('ok');
res.end();
});
break;
}
};
});
HTMLFile.prototype._write = function(message){
this.response.write('<script>parent.s._('+ JSON.stringify(message) +', document);</script>'); //json for escaping
if ('flush' in this.response) this.response.flush();
};

View File

@@ -1,140 +1,139 @@
var Client = require('../client').Client,
var Client = require('../client'),
url = require('url'),
Buffer = require('buffer').Buffer,
crypto = require('crypto');
crypto = require('crypto'),
exports.websocket = Client.extend({
WebSocket = module.exports = function(){
Client.apply(this, arguments);
};
_onConnect: function(req, socket){
var self = this, headers = [];
this.request = req;
this.connection = socket;
this.data = '';
require('sys').inherits(WebSocket, Client);
if (this.request.headers.upgrade !== 'WebSocket' || !this._verifyOrigin(this.request.headers.origin)){
this.listener.options.log('WebSocket connection invalid');
this.connection.end();
}
WebSocket.prototype._onConnect = function(req, socket){
var self = this, headers = [];
this.request = req;
this.connection = socket;
this.data = '';
this.connection.setTimeout(0);
this.connection.setEncoding('utf8');
this.connection.setNoDelay(true);
if (this.request.headers.upgrade !== 'WebSocket' || !this._verifyOrigin(this.request.headers.origin)){
this.listener.options.log('WebSocket connection invalid');
this.connection.end();
}
if ('sec-websocket-key1' in this.request.headers) {
this.draft = 76;
}
this.connection.setTimeout(0);
this.connection.setEncoding('utf8');
this.connection.setNoDelay(true);
if (this.draft == 76) {
if ('sec-websocket-key1' in this.request.headers){
this.draft = 76;
}
var origin = this.request.headers.origin;
headers = [
'HTTP/1.1 101 WebSocket Protocol Handshake',
'Upgrade: WebSocket',
'Connection: Upgrade',
'Sec-WebSocket-Origin: ' + (origin || 'null'),
'Sec-WebSocket-Location: ws://' + this.request.headers.host + this.request.url
];
if ('sec-websocket-protocol' in this.request.headers) {
headers.push('Sec-WebSocket-Protocol: ' + this.request.headers['sec-websocket-protocol']);
}
}
else {
headers = [
'HTTP/1.1 101 Web Socket Protocol Handshake',
'Upgrade: WebSocket',
'Connection: Upgrade',
'WebSocket-Origin: ' + this.request.headers.origin,
'WebSocket-Location: ws://' + this.request.headers.host + this.request.url
];
try {
this.connection.write(headers.concat('', '').join('\r\n'));
} catch(e){
this._onClose();
}
}
if (this.draft == 76){
var origin = this.request.headers.origin;
this.connection.addListener('end', function(){self._onClose();});
this.connection.addListener('data', function(data){self._handle(data);});
if (this._proveReception(headers)){
this._payload();
}
setInterval(function(){
self._write(JSON.stringify({heartbeat: '1'}));
}, 10000);
},
_handle: function(data){
var chunk, chunks, chunk_count;
this.data += data;
chunks = this.data.split('\ufffd');
chunk_count = chunks.length - 1;
for (var i = 0; i < chunk_count; i++) {
chunk = chunks[i];
if (chunk[0] !== '\u0000') {
this.listener.options.log('Data incorrectly framed by UA. Dropping connection');
this.connection.destroy();
return false;
}
this._onMessage(chunk.slice(1));
}
this.data = chunks[chunks.length - 1];
},
// http://www.whatwg.org/specs/web-apps/current-work/complete/network.html#opening-handshake
_proveReception: function(headers){
var k1 = this.request.headers['sec-websocket-key1'],
k2 = this.request.headers['sec-websocket-key2'];
headers = [
'HTTP/1.1 101 WebSocket Protocol Handshake',
'Upgrade: WebSocket',
'Connection: Upgrade',
'Sec-WebSocket-Origin: ' + (origin || 'null'),
'Sec-WebSocket-Location: ws://' + this.request.headers.host + this.request.url
];
if (k1 && k2) {
var md5 = crypto.createHash('md5');
[k1, k2].forEach(function(k) {
var n = parseInt(k.replace(/[^\d]/g, '')),
spaces = k.replace(/[^ ]/g, '').length;
if (spaces === 0 || n % spaces !== 0) {
this.listener.options.log('Invalid WebSocket key: "' + k + '". Dropping connection');
this.connection.destroy();
return false;
}
n /= spaces;
md5.update(String.fromCharCode(
n >> 24 & 0xFF,
n >> 16 & 0xFF,
n >> 8 & 0xFF,
n & 0xFF));
});
md5.update(this.upgradeHead.toString('binary'));
try {
this.connection.write(headers.concat('', '').join('\r\n') + md5.digest('binary'), 'binary');
} catch(e){
this._onClose();
}
if ('sec-websocket-protocol' in this.request.headers){
headers.push('Sec-WebSocket-Protocol: ' + this.request.headers['sec-websocket-protocol']);
}
} else {
headers = [
'HTTP/1.1 101 Web Socket Protocol Handshake',
'Upgrade: WebSocket',
'Connection: Upgrade',
'WebSocket-Origin: ' + this.request.headers.origin,
'WebSocket-Location: ws://' + this.request.headers.host + this.request.url
];
return true;
},
_write: function(message){
try {
this.connection.write('\u0000', 'binary');
this.connection.write(message, 'utf8');
this.connection.write('\uffff', 'binary');
this.connection.write(headers.concat('', '').join('\r\n'));
} catch(e){
this._onClose();
}
}
this.connection.addListener('end', function(){
self._onClose();
});
this.connection.addListener('data', function(data){
self._handle(data);
});
});
if (this._proveReception(headers)) this._payload();
};
exports.websocket.httpUpgrade = true;
WebSocket.prototype._handle = function(data){
var chunk, chunks, chunk_count;
this.data += data;
chunks = this.data.split('\ufffd');
chunk_count = chunks.length - 1;
for (var i = 0; i < chunk_count; i++){
chunk = chunks[i];
if (chunk[0] !== '\u0000'){
this.listener.options.log('Data incorrectly framed by UA. Dropping connection');
this.connection.destroy();
return false;
}
this._onMessage(chunk.slice(1));
}
this.data = chunks[chunks.length - 1];
};
// http://www.whatwg.org/specs/web-apps/current-work/complete/network.html#opening-handshake
WebSocket.prototype._proveReception = function(headers){
var k1 = this.request.headers['sec-websocket-key1'],
k2 = this.request.headers['sec-websocket-key2'];
if (k1 && k2){
var md5 = crypto.createHash('md5');
[k1, k2].forEach(function(k){
var n = parseInt(k.replace(/[^\d]/g, '')),
spaces = k.replace(/[^ ]/g, '').length;
if (spaces === 0 || n % spaces !== 0){
this.listener.options.log('Invalid WebSocket key: "' + k + '". Dropping connection');
this.connection.destroy();
return false;
}
n /= spaces;
md5.update(String.fromCharCode(
n >> 24 & 0xFF,
n >> 16 & 0xFF,
n >> 8 & 0xFF,
n & 0xFF));
});
md5.update(this.upgradeHead.toString('binary'));
try {
this.connection.write(headers.concat('', '').join('\r\n') + md5.digest('binary'), 'binary');
} catch(e){
this._onClose();
}
}
return true;
};
WebSocket.prototype._write = function(message){
try {
this.connection.write('\u0000', 'binary');
this.connection.write(message, 'utf8');
this.connection.write('\uffff', 'binary');
} catch(e){
this._onClose();
}
};
WebSocket.httpUpgrade = true;

View File

@@ -1,69 +1,62 @@
var Client = require('../client').Client,
qs = require('querystring');
var Client = require('../client'),
qs = require('querystring'),
exports['xhr-multipart'] = Client.extend({
Multipart = module.exports = function(){
Client.apply(this, arguments);
};
options: {
pingInterval: 7000
},
_pingInterval: null,
require('sys').inherits(Multipart, Client);
_onConnect: function(req, res){
var self = this, body = '', headers = {};
// https://developer.mozilla.org/En/HTTP_Access_Control
if (req.headers.origin && this._verifyOrigin(req.headers.origin)) {
headers['Access-Control-Allow-Origin'] = req.headers.origin;
headers['Access-Control-Allow-Credentials'] = 'true';
}
if (typeof req.headers['access-control-request-method'] !== 'undefined') {
// CORS preflight message
headers['Access-Control-Allow-Methods'] = req.headers['access-control-request-method'];
res.writeHead(200, headers);
res.write('ok');
res.end();
return;
}
switch (req.method){
case 'GET':
this.__super__(req, res);
headers['Content-Type'] = 'multipart/x-mixed-replace;boundary="socketio"';
headers['Connection'] = 'keep-alive';
this.request.connection.addListener('end', function(){ self._onClose(); });
this.response.useChunkedEncodingByDefault = false;
this.response.shouldKeepAlive = true;
this.response.writeHead(200, headers);
this.response.write("--socketio\n");
if ('flush' in this.response) this.response.flush();
this._payload();
this._heartbeatInterval = setInterval(function(){
self._write(String.fromCharCode(6));
}, this.options.heartbeatInterval);
break;
case 'POST':
req.addListener('data', function(message){
body += message.toString();
});
req.addListener('end', function(){
try {
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){}
res.writeHead(200, headers);
res.write('ok');
res.end();
body = '';
});
break;
}
},
_write: function(message){
this.response.write("Content-Type: text/plain" + (message.length === 1 && message.charCodeAt(0) === 6 ? "; charset=us-ascii" : "") + "\n\n");
this.response.write(message + "\n");
this.response.write("--socketio\n");
if ('flush' in this.response) this.response.flush();
Multipart.prototype._onConnect = function(req, res){
var self = this, body = '', headers = {};
// https://developer.mozilla.org/En/HTTP_Access_Control
if (req.headers.origin && this._verifyOrigin(req.headers.origin)){
headers['Access-Control-Allow-Origin'] = req.headers.origin;
headers['Access-Control-Allow-Credentials'] = 'true';
}
if (typeof req.headers['access-control-request-method'] !== 'undefined'){
// CORS preflight message
headers['Access-Control-Allow-Methods'] = req.headers['access-control-request-method'];
res.writeHead(200, headers);
res.write('ok');
res.end();
return;
}
switch (req.method){
case 'GET':
Client.prototype._onConnect.apply(this, [req, res]);
headers['Content-Type'] = 'multipart/x-mixed-replace;boundary="socketio"';
headers['Connection'] = 'keep-alive';
this.request.connection.addListener('end', function(){ self._onClose(); });
this.response.useChunkedEncodingByDefault = false;
this.response.shouldKeepAlive = true;
this.response.writeHead(200, headers);
this.response.write("--socketio\n");
if ('flush' in this.response) this.response.flush();
this._payload();
break;
case 'POST':
req.addListener('data', function(message){
body += message.toString();
});
req.addListener('end', function(){
try {
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){}
res.writeHead(200, headers);
res.write('ok');
res.end();
body = '';
});
break;
}
};
});
Multipart.prototype._write = function(message){
this.response.write("Content-Type: text/plain" + (message.length === 1 && message.charCodeAt(0) === 6 ? "; charset=us-ascii" : "") + "\n\n");
this.response.write(message + "\n");
this.response.write("--socketio\n");
if ('flush' in this.response) this.response.flush();
};

View File

@@ -1,57 +1,52 @@
var Client = require('../client').Client,
qs = require('querystring');
var Client = require('../client'),
qs = require('querystring'),
exports['xhr-polling'] = Client.extend({
options: {
closeTimeout: 5000,
duration: 20000
},
_onConnect: function(req, res){
var self = this, body = '';
switch (req.method){
case 'GET':
this.__super__(req, res);
this._closeTimeout = setTimeout(function(){
self._write('');
}, this.options.duration);
this._payload();
break;
case 'POST':
req.addListener('data', function(message){
body += message;
});
req.addListener('end', function(){
try {
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){}
res.writeHead(200);
res.write('ok');
res.end();
});
break;
}
},
_write: function(message){
if (this._closeTimeout) {
clearTimeout(this._closeTimeout);
}
var headers = {'Content-Type': 'text/plain', 'Content-Length': message.length};
// https://developer.mozilla.org/En/HTTP_Access_Control
if (this.request.headers.origin && this._verifyOrigin(this.request.headers.origin)) {
headers['Access-Control-Allow-Origin'] = this.request.headersorigin;
if (this.request.headers.cookie) {
headers['Access-Control-Allow-Credentials'] = 'true';
}
}
this.response.writeHead(200, headers);
this.response.write(message);
this.response.end();
this._onClose();
Polling = module.exports = function(){
Client.apply(this, arguments);
};
require('sys').inherits(Polling, Client);
Polling.prototype._onConnect = function(req, res){
var self = this, body = '';
switch (req.method){
case 'GET':
Client.prototype._onConnect.apply(this, [req, res]);
this.request.connection.addListener('end', function(){ self._onClose(); });
this._closeTimeout = setTimeout(function(){
self._write('');
}, this.options.duration);
this._payload();
break;
case 'POST':
req.addListener('data', function(message){
body += message;
});
req.addListener('end', function(){
try {
// optimization: just strip first 5 characters here?
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){}
res.writeHead(200);
res.write('ok');
res.end();
});
break;
}
};
});
Polling.prototype._write = function(message){
if (this._closeTimeout) clearTimeout(this._closeTimeout);
var headers = {'Content-Type': 'text/plain', 'Content-Length': message.length};
// https://developer.mozilla.org/En/HTTP_Access_Control
if (this.request.headers.origin && this._verifyOrigin(this.request.headers.origin)){
headers['Access-Control-Allow-Origin'] = this.request.headersorigin;
if (this.request.headers.cookie) headers['Access-Control-Allow-Credentials'] = 'true';
}
this.response.writeHead(200, headers);
this.response.write(message);
this.response.end();
this._onClose();
};

View File

@@ -1,20 +0,0 @@
// Based on Mixin.js from MooTools (MIT)
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
exports.flatten = function(arr){
var array = [];
for (var i = 0, l = arr.length; i < l; i++){
var item = arr[i];
if (item !== null) {
array = array.concat(item instanceof Array ? array.flatten(item) : item);
}
}
return array;
};
exports.include = function(arr, item){
if (arr.indexOf(item) === -1) {
arr.push(item);
}
return arr;
};

View File

@@ -1,51 +0,0 @@
// Based on Mixin.js from MooTools (MIT)
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
exports.clone = function(item) {
var cloned;
if (item instanceof Array){
cloned = [];
for (var i = 0; i < item.length; i++) {
cloned[i] = exports.clone(item[i]);
}
return cloned;
}
if (typeof item === 'object') {
cloned = {};
for (var key in item) {
cloned[key] = exports.clone(item[key]);
}
return cloned;
}
return item;
};
var mergeOne = function(source, key, current){
if (current instanceof Array){
source[key] = exports.clone(current);
} else if (typeof current === 'object'){
if (typeof source[key] === 'object') {
exports.merge(source[key], current);
} else {
source[key] = exports.clone(current);
}
} else {
source[key] = current;
}
return source;
};
exports.merge = function(source, k, v){
if (typeof k === 'string') {
return mergeOne(source, k, v);
}
for (var i = 1, l = arguments.length; i < l; i++){
var object = arguments[i];
for (var key in object) {
mergeOne(source, key, object[key]);
}
}
return source;
};

View File

@@ -1,35 +0,0 @@
// Based on Mixin.js from MooTools (MIT)
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
var object = require('./object'), sys = require('sys');
exports.options = {
options: {},
setOption: function(key, value){
object.merge(this.options, key, value);
return this;
},
setOptions: function(options){
for (var key in options) {
this.setOption(key, options[key]);
}
if (this.addListener){
var first_lower = function(full, first){
return first.toLowerCase();
};
// Automagically register callbacks if the varname starts with on
for (var i in this.options){
if (!(/^on[A-Z]/).test(i) || typeof this.options[i] !== 'function') {
continue;
}
this.addListener(i.replace(/^on([A-Z])/, first_lower), this.options[i]);
this.options[i] = null;
}
}
return this;
}
};

10
lib/socket.io/utils.js Normal file
View File

@@ -0,0 +1,10 @@
exports.options = {
options: function(options, merge){
this.options = exports.merge(options || {}, merge || {});
}
};
exports.merge = function(source, merge){
for (var i in merge) source[i] = merge[i];
return source;
};

1
lib/vendor/js-oo vendored

Submodule lib/vendor/js-oo deleted from 1f94bd8979

Submodule test/client deleted from fec3a888e0

28
tests/client.test.js Normal file
View File

@@ -0,0 +1,28 @@
var listener = require('socket.io/listener'),
Client = require('socket.io/client');
module.exports = {
'test decoding': function(assert){
var client = new Client(listener, {}, {}),
decoded = client._decode('~m~5~m~abcde' + '~m~9~m~123456789');
assert.equal(decoded.length, 2);
assert.equal(decoded[0], 'abcde');
assert.equal(decoded[1], '123456789');
},
'test decoding of bad framed messages': function(assert){
var client = new Client(listener, {}, {}),
decoded = client._decode('~m~5~m~abcde' + '~m\uffsdaasdfd9~m~1aaa23456789');
assert.equal(decoded.length, 1);
assert.equal(decoded[0], 'abcde');
assert.equal(decoded[1], undefined);
},
'test encoding': function(assert){
var client = new Client(listener, {}, {});
assert.equal(client._encode(['abcde', '123456789']), '~m~5~m~abcde' + '~m~9~m~123456789');
assert.equal(client._encode('asdasdsad'), '~m~9~m~asdasdsad');
assert.equal(client._encode(''), '~m~0~m~');
assert.equal(client._encode(null), '~m~0~m~');
}
};

52
tests/io.test.js Normal file
View File

@@ -0,0 +1,52 @@
var io = require('./../'),
Listener = io.Listener,
Client = require('./../lib/socket.io/client'),
WebSocket = require('./support/node-websocket-client/lib/websocket').WebSocket,
empty = new Function,
port = 8080,
create = function(fn){
var server = require('http').createServer(empty), client;
server.listen(port, function(){
client = new WebSocket('ws://localhost:'+ port++ +'/socket.io/websocket', 'borf');
});
return {server: server, client: client, close: function(){
client.close();
server.close();
}};
};
module.exports = {
'test server initialization': function(assert){
var http = create(),
sio = io.listen(http.server);
assert.ok(sio instanceof Listener);
http.close();
},
'test connection and handshake': function(assert){
var server = require('http').createServer(empty),
sio = io.listen(server),
client,
clientCount = 0,
close = function(){
client.close();
server.close();
assert.ok(clientCount, 1);
};
server.listen(port, function(){
client = new WebSocket('ws://localhost:'+ port++ +'/socket.io/websocket', 'borf');
client.onmessage = function(){
console.log('test');
};
});
sio.on('connection', function(client){
console.log('test');
clientCount++;
assert.ok(client instanceof Client);
});
}
};