Compare commits

..

54 Commits
0.3.6 ... 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
Guillermo Rauch
8bf30afda2 Proper support for draft 76 2010-06-29 21:23:59 -07:00
Oliver Morgan
b971a18e5b Fixed WebSockets to follow latest 76 draft.
TODO: Implement the closing handshake, specified in draft-76
2010-06-27 13:57:44 -07:00
Guillermo Rauch
57c8d37d47 Implemented heartbeats 2010-06-26 15:04:55 -07:00
Guillermo Rauch
5e329229b4 Fix for flush in htmlfile 2010-06-25 14:57:57 -07:00
Guillermo Rauch
ea36d24d9e Updated Socket.IO client 2010-06-18 03:02:51 -03:00
Guillermo Rauch
d79de5aa56 Multipart fix for Node 0.1.98 2010-06-18 02:38:08 -03:00
Guillermo Rauch
06769c4ca7 Object fixes 2010-06-01 16:19:29 -03:00
Christiaan
df876e37df New changes passing JSlint as much as possible again 2010-06-01 21:05:20 +02:00
Christiaan
31d119740c Merge branch 'master' of http://github.com/LearnBoost/Socket.IO-node
Conflicts:
	lib/socket.io/client.js
	lib/socket.io/listener.js
	lib/socket.io/transports/websocket.js
	lib/socket.io/transports/xhr-multipart.js
	lib/socket.io/transports/xhr-polling.js

Conflicts Resolved
2010-06-01 20:54:13 +02:00
Guillermo Rauch
f3c28e298a Cross domain support:
XHR-Polling
 XHR-Multipart
WebSocket support for Webkit nightly / Chromium nightly
Added heartbeats to multipart
2010-06-01 08:01:11 -03:00
Brian
71d80181fa Fixed multipart streaming so that it works properly again (especially for cross-domain requests). Tested on Firefox 3.6.4. 2010-05-30 22:52:28 -07:00
Brian
0e483a05a5 Moved the cross-site origin checking to the client class, added cross-domain support to xhr-* transports, added ping functionality for the xhr-multipart transport, and fixed the listener class where the log function that can be overridden was not being referenced everywhere. 2010-05-30 22:52:16 -07:00
Christiaan
c1fca0c520 Almost fully passing JSlint 2010-05-29 15:21:46 +02:00
Visnu Pitiyanuvath
1918b75360 implement the sec-websocket-key handshake for latest chrome and webkit builds
http://www.whatwg.org/specs/web-apps/current-work/complete/network.html#opening-handshake
2010-05-28 11:15:55 -07:00
Christiaan
5e255ccdf9 Use exports instead of this as exports is the official way according to the commonjs spec 2010-05-28 19:37:46 +02:00
Christiaan
9a306fddab variables which are no constructor shouldn't start with a uppercase letter 2010-05-28 19:24:38 +02:00
Christiaan
dc593e9972 Use EventEmitter public API instead of the private _events array 2010-05-28 19:22:54 +02:00
26 changed files with 730 additions and 527 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){
@@ -23,13 +23,13 @@ server = http.createServer(function(req, res){
default:
if (/\.(js|html|swf)$/.test(path)){
try {
var swf = path.substr(-4) == '.swf';
res.writeHead(200, {'Content-Type': swf ? 'application/x-shockwave-flash' : ('text/' + (path.substr(-3) == '.js' ? 'javascript' : 'html'))});
var swf = path.substr(-4) === '.swf';
res.writeHead(200, {'Content-Type': swf ? 'application/x-shockwave-flash' : ('text/' + (path.substr(-3) === '.js' ? 'javascript' : 'html'))});
res.write(fs.readFileSync(__dirname + path, swf ? 'binary' : 'utf8'), swf ? 'binary' : 'utf8');
res.end();
} catch(e){
send404(res);
}
}
break;
}
@@ -42,24 +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();
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,100 +1,172 @@
var Options = require('./util/options').Options;
var urlparse = require('url').parse,
options = require('./utils').options,
frame = '~m~',
this.Client = Class({
include: [Options],
options: {
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);
};
init: function(listener, req, res, options){
this.listener = listener;
this.setOptions(options);
this.connections = 0;
this.connected = false;
this._onConnect(req, res);
},
require('sys').inherits(Client, process.EventEmitter);
send: function(message){
if (!this.connected || !(this.connection.readyState == 'open' || this.connection.readyState == 'writeOnly')) return this._queue(message);
this._write(JSON.stringify({messages: [message]}));
return this;
},
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;
};
broadcast: function(message){
if (!('sessionId' in this)) return this;
this.listener.broadcast(message, this.sessionId);
return this;
},
Client.prototype.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){
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;
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);
}
};
payload = payload.concat(this._writeQueue || []);
this._writeQueue = [];
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);
};
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;
};
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;
}
}
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();
}
};
if (payload.length) this._write(JSON.stringify({messages: payload}));
if (this.connections == 1) this.listener._onClientConnect(this);
},
_onClose: function(){
Client.prototype._onClose = function(){
if (this.connected){
var self = this;
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;
}
};
});
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,123 +1,124 @@
var url = require('url'),
sys = require('sys'),
Options = require('./util/options').Options,
Client = require('./client').Client,
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')
},
Transports = {};
Listener = this.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){
sys.log(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 listener = (this.server._events['request'] instanceof Array)
? this.server._events['request'][0]
: this.server._events['request'];
if (listener){
this.server._events['request'] = function(req, res){
if (self.check(req, res)) return;
listener(req, res);
};
} else {
throw new Error('Couldn\'t find the `request` event in the HTTP server.');
}
this.server.addListener('upgrade', function(req, socket, head){
if (!self.check(req, socket, true)){
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);
}
}
return this;
},
check: function(req, res, httpUpgrade){
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();
sys.log('Couldnt find client with session id "' + parts[2] + '"');
}
} else {
this._onConnection(parts[1], req, res, httpUpgrade);
}
return true;
}
return false;
},
}, options);
this.clients = [];
this.clientsIndex = {};
_lookupClient: function(sid){
return this.clientsIndex[sid];
},
var listeners = this.server.listeners('request');
this.server.removeAllListeners('request');
_onClientConnect: function(client){
if (!(client instanceof Client) || !client.sessionId){
return sys.log('Invalid client');
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);
}
client.i = this.clients.length;
this.clients.push(client);
this.clientsIndex[client.sessionId] = client;
sys.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;
sys.log('Client '+ client.sessionId +' disconnected');
this.emit('clientDisconnect', client);
},
// new connections (no session id)
_onConnection: function(transport, req, res, httpUpgrade){
if (this.options.transports.indexOf(transport) === -1 || (httpUpgrade && !Transports[transport].httpUpgrade)){
httpUpgrade ? res.destroy() : req.connection.destroy();
return sys.log('Illegal transport "'+ transport +'"');
this.server.addListener('upgrade', function(req, socket, head){
if (!self.check(req, socket, true, head)){
socket.destroy();
}
sys.log('Initializing client with transport "'+ transport +'"');
new Transports[transport](this, req, res, this.options.transportOptions[transport]);
});
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,
this.flashsocket = websocket.extend({});
this.flashsocket.httpUpgrade = true;
this.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'),
this['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(' '));
this.response.flush();
this._payload();
setInterval(function(){
self.response.write('<!-- heartbeat -->');
self.response.flush();
}, 1000);
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>');
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,69 +1,139 @@
var Client = require('../client').Client,
url = require('url');
var Client = require('../client'),
url = require('url'),
Buffer = require('buffer').Buffer,
crypto = require('crypto'),
this.websocket = Client.extend({
_onConnect: function(req, socket){
var self = this;
this.request = req;
this.connection = socket;
this.data = '';
WebSocket = module.exports = function(){
Client.apply(this, arguments);
};
require('sys').inherits(WebSocket, Client);
WebSocket.prototype._onConnect = function(req, socket){
var self = this, headers = [];
this.request = req;
this.connection = socket;
this.data = '';
if (this.request.headers.upgrade !== 'WebSocket' || !this._verifyOrigin(this.request.headers.origin)){
this.listener.options.log('WebSocket connection invalid');
this.connection.end();
}
this.connection.setTimeout(0);
this.connection.setEncoding('utf8');
this.connection.setNoDelay(true);
if ('sec-websocket-key1' in this.request.headers){
this.draft = 76;
}
if (this.draft == 76){
var origin = this.request.headers.origin;
if (this.request.headers['upgrade'] !== 'WebSocket' || !this._verifyOrigin(this.request.headers['origin'])){
this.listener.options.log('WebSocket connection invalid');
this.connection.end();
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 {
this.connection.setTimeout(0);
this.connection.setEncoding('utf8');
this.connection.setNoDelay(true);
this.connection.write([
'HTTP/1.1 101 Web Socket Protocol Handshake',
'Upgrade: WebSocket',
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,
'', ''
].join('\r\n'));
this.connection.addListener('end', function(){ self._onClose(); });
this.connection.addListener('data', function(data){ self._handle(data); });
this._payload();
},
_handle: function(data){
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];
},
_verifyOrigin: function(origin){
var parts = url.parse(origin);
return this.listener.options.origins.indexOf('*:*') !== -1
|| this.listener.options.origins.indexOf(parts.host + ':' + parts.port) !== -1
|| this.listener.options.origins.indexOf(parts.host + ':*') !== -1
|| this.listener.options.origins.indexOf('*:' + parts.port) !== -1;
},
_write: function(message){
'WebSocket-Location: ws://' + this.request.headers.host + this.request.url
];
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);
});
this.websocket.httpUpgrade = true;
if (this._proveReception(headers)) this._payload();
};
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,48 +1,62 @@
var Client = require('../client').Client,
qs = require('querystring');
var Client = require('../client'),
qs = require('querystring'),
this['xhr-multipart'] = Client.extend({
_onConnect: function(req, res){
var self = this, body = '';
switch (req.method){
case 'GET':
var self = this;
this.__super__(req, res);
this.request.connection.addListener('end', function(){ self._onClose(); });
this.response.useChunkedEncodingByDefault = false;
this.response.shouldKeepAlive = true;
this.response.writeHead(200, {
'Content-Type': 'multipart/x-mixed-replace;boundary=socketio',
'Connection': 'keep-alive'
});
this.response.write("--socketio\n");
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;
}
},
_write: function(message){
this.response.write("Content-Type: text/plain\n\n");
this.response.write(message + "\n");
this.response.write("--socketio\n");
this.response.flush();
Multipart = module.exports = function(){
Client.apply(this, arguments);
};
require('sys').inherits(Multipart, Client);
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,48 +1,52 @@
var Client = require('../client').Client,
qs = require('querystring'),
sys = require('sys');
var Client = require('../client'),
qs = require('querystring'),
this['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);
this.response.writeHead(200, {'Content-Type': 'text/plain', 'Content-Length': message.length});
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,16 +0,0 @@
// Based on Mixin.js from MooTools (MIT)
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
this.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;
};
this.include = function(arr, item){
if (arr.indexOf(item) == -1) arr.push(item);
return arr;
};

View File

@@ -1,38 +0,0 @@
// Based on Mixin.js from MooTools (MIT)
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
var clone = this.clone = function(item){
var cloned;
if (item instanceof Array){
cloned = [];
for (var i = 0; i < item.length; i++) cloned[i] = clone(item[i]);
return cloned;
} else if (typeof item == 'object') {
cloned = {};
for (var key in object) cloned[key] = clone(object[key]);
return cloned;
} else {
return item;
}
},
mergeOne = function(source, key, current){
if (current instanceof Array){
source[key] = clone(current);
} else if (typeof current == 'object'){
if (typeof source[key] == 'object') object.merge(source[key], current);
else source[key] = clone(current);
} else {
source[key] = current;
}
return source;
};
this.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,28 +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');
this.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){
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])/, function(full, first){
return first.toLowerCase();
}), 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 d643e6029c

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);
});
}
};