Compare commits

...

81 Commits
0.2.2 ... 0.5.3

Author SHA1 Message Date
Guillermo Rauch
c173141f8b Updated client 2010-08-01 14:05:55 -07:00
Guillermo Rauch
91cdddb32c Updated client to 0.5.2 2010-08-01 11:27:43 -07:00
Guillermo Rauch
4acf98db9d Fixed require path in README (thanks Tobsn) 2010-07-29 11:09:21 -07:00
Guillermo Rauch
73cc5e2c66 Updated client 2010-07-29 10:37:44 -07:00
Guillermo Rauch
58ac323423 Extended how to use section 2010-07-29 02:04:02 -07:00
Guillermo Rauch
cd2c5140f1 Updated client 2010-07-29 01:59:02 -07:00
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
Guillermo Rauch
7f5228abc3 Fixed syntax error -.- 2010-05-28 06:12:31 -03:00
Guillermo Rauch
0ad237ddf5 Fix for stream not writable problems 2010-05-28 05:28:25 -03:00
Guillermo Rauch
3134940ed6 Updated io.Socket initialization to work on hosts other than localhost
Updated client
2010-05-27 21:45:01 -03:00
Guillermo Rauch
0f783bc06a Fixed htmlfile
- end > close for detecting closed connections
 - Added hearbeats
 - added flush() in write
2010-05-27 21:33:07 -03:00
Virtuo
dd3d829173 Correct bug due to variable name clash.
IMO: this kind of code should be put in an external lib and 100% tested.
2010-05-21 13:09:14 +02:00
Guillermo Rauch
fbce6379a5 Note about submodules 2010-05-13 22:05:31 -03:00
Guillermo Rauch
0459c95848 Added support for Node 0.1.94
Close timeout for transports with keep-alive / websocket set to 0
Close timeout for xhr-polling reduced from 8 seconds to 5 seconds
Now you can pass options to specific transports from io.listen()
Some cleanup
2010-05-12 12:16:44 -03:00
Guillermo Rauch
67815ec15d http > git in gitmodules 2010-05-11 19:34:25 -03:00
Guillermo Rauch
87f62373c5 Client updated 2010-04-30 09:02:11 -03:00
Guillermo Rauch
a557bd6acb Updated client to 0.2.0 2010-04-30 08:55:09 -03:00
Guillermo Rauch
ae9e550c26 More support for disconnections 2010-04-30 08:48:17 -03:00
Guillermo Rauch
b19bac2d33 Updated Readme 2010-04-30 08:36:15 -03:00
Guillermo Rauch
a977ecd65b Fixes for htmlfile, multipart, polling
Removed patches
Removed server events
2010-04-30 08:35:34 -03:00
Guillermo Rauch
c4cf1b28f2 Updated JSON decoding 2010-04-30 08:12:08 -03:00
Guillermo Rauch
f5796c630c OCD-related work 2010-04-30 08:10:25 -03:00
Guillermo Rauch
6ce8c52c4d Updated README
Updated copyright
Updated project locations
2010-04-30 08:09:23 -03:00
Guillermo Rauch
408b2c73f8 Fixed encoding issues for WebSocket transport 2010-04-30 08:07:57 -03:00
Guillermo Rauch
16b5de7b4f All instances of 'close' now 'end' 2010-04-30 07:04:15 -03:00
Guillermo Rauch
b65e6fa611 'tcp' is now 'net' 2010-04-30 07:01:08 -03:00
Tobias Schneider
ddfc39826f Repository for submodule RosePad/Socket.IO doesn't exist anymore, use LearnBoost/Socket.IO instead. 2010-04-27 20:13:47 +02:00
Guillermo Rauch
1745e89936 Client update 2010-04-03 18:50:50 -07:00
29 changed files with 772 additions and 777 deletions

15
.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
url = git://github.com/RosePad/Socket.IO.git
[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

@@ -4,26 +4,23 @@ Socket.IO Server: Sockets for the rest of us
The `Socket.IO` server provides seamless supports for a variety of transports intended for realtime communication
- WebSocket (with Flash policy support)
- Server-Sent Events
- XHR Polling
- XHR Multipart Streaming
- Forever Iframe
Requirements
------------
## Requirements
- Node v0.1.32+ (tested with v0.1.32, v0.1.33)
- [Socket.IO client](http://github.com/RosePad/Socket.IO) to connect from the browser
- Node v0.1.102+
- [Socket.IO client](http://github.com/LearnBoost/Socket.IO) to connect from the browser
How to use
----------
## How to use
`Socket.IO` is designed not to take over an entire port or Node `http.Server` instance. This means that if you choose your HTTP server to listen on the port 80, `socket.io` can intercept requests directed to it and the normal requests will still be served.
By default, the server will intercept requests that contain `socket.io` in the path / resource part of the URI. You can change this (look at the available options below).
var http = require('http'),
io = require('./socket.io/socket.io.js'),
io = require('./path/to/socket.io'),
server = http.createServer(function(req, res){
// your normal server code
@@ -33,29 +30,29 @@ By default, the server will intercept requests that contain `socket.io` in the p
});
// socket.io, I choose you
io.listen(server);
var socket = io.listen(server);
Due to a lack of flexibility in the current Node HTTP server implementation, you'll have to patch Node before using `socket.io`.
In the node directory run:
socket.on('connection', function(client){
// new client is here!
client.on('message', function(){ … })
client.on('disconnect', function(){ … })
});
patch -p1 < {../directory/to/socket.io-node}/patch/{node version}.patch
./configure
make
make test
sudo make install
On the client side, you should use the [Socket.IO client](https://github.com/RosePad/Socket.IO) to connect.
On the client side, you should use the [Socket.IO client](https://github.com/LearnBoost/Socket.IO) to connect.
## Checking out
## Notes
After cloning the repository, remember to run
IMPORTANT! When checking out the git repo, make sure to include the submodules. One way to do it is:
git submodule init
git submodule update
git clone [repo] --recursive
Another, once cloned
git submodule update --init --recursive
## 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
@@ -115,11 +112,9 @@ Options:
A list of the accepted transports.
- *timeout*
8000
Time it has to pass without a reconnection to consider a client disconnected. Applies to all transports.
- *transportOptions*
An object of options to pass to each transport. For example `{ websocket: { closeTimeout: 8000 }}`
- *log*
@@ -181,25 +176,23 @@ One of the design goals is that you should be able to implement whatever protoco
The concept of session also benefits naturally full-duplex WebSocket, in the event of an accidental disconnection and a quick reconnection. Messages that the server intends to deliver to the client are cached temporarily until the reconnection.
The implementation of reconnection logic (potentially with retries) is left for the user.
The implementation of reconnection logic (potentially with retries) is left for the user. By default, transports that are keep-alive or open all the time (like WebSocket) have a timeout of 0 if a disconnection is detected.
* 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.
## Credits
Guillermo Rauch &lt;guillermo@rosepad.com&gt;
Special thanks to [Jonas Pfenniger](http://github.com/zimbatm) for his workaround patch to keep the HTTPConnection open after the request is successful.
Guillermo Rauch &lt;guillermo@learnboost.com&gt;
## License
(The MIT License)
Copyright (c) 2009 RosePad &lt;dev@rosepad.com&gt;
Copyright (c) 2010 LearnBoost &lt;dev@learnboost.com&gt;
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

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,11 +25,15 @@
message({ message: ['you', val] });
document.getElementById('text').value = '';
}
function esc(msg){
return msg.replace(/</g, '&lt;').replace(/>/g, '&gt;');
};
var socket = new io.Socket('localhost', {rememberTransport: false, port: 8080});
var socket = new io.Socket(null, {rememberTransport: false, port: 8080});
socket.connect();
socket.addEvent('message', function(data){
var obj = io.util.JSON.decode(data);
var obj = JSON.parse(data);
if ('buffer' in obj){
document.getElementById('form').style.display='block';

1
example/client Submodule

Submodule example/client added at a8efecaa56

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,13 +1,13 @@
var http = require('http'),
url = require('url'),
fs = require('fs'),
io = require('../lib/socket.io'),
io = require('../'),
sys = require('sys'),
send404 = function(res){
res.writeHead(404);
res.write('404');
res.close();
res.end();
},
server = http.createServer(function(req, res){
@@ -17,19 +17,19 @@ server = http.createServer(function(req, res){
case '/':
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Welcome. Try the <a href="/chat.html">chat</a> example.</h1>');
res.close();
res.end();
break;
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.close();
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,93 +1,172 @@
var sys = require('sys');
var urlparse = require('url').parse,
options = require('./utils').options,
frame = '~m~',
this.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);
};
init: function(listener, req, res){
this.listener = listener;
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.listener.options.timeout);
},
_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;
}, this.options.closeTimeout);
}
};
});
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,117 +1,124 @@
var url = require('url'),
sys = require('sys'),
Options = require('./util/options').Options,
Client = require('./client').Client,
Transports = {},
Listener = this.Listener = Class({
include: [process.EventEmitter.prototype, Options],
options: {
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')
},
Listener = module.exports = function(server, options){
process.EventEmitter.call(this);
var self = this;
this.server = server;
this.options({
origins: '*:*',
resource: 'socket.io',
transports: ['websocket', 'server-events', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling'],
timeout: 8000,
transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling'],
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){
process.EventEmitter.call(this);
}, options);
this.clients = [];
this.clientsIndex = {};
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){
var self = this;
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.');
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.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);
}
});
this.server.addListener('upgrade', function(req, socket, head){
if (!self.check(req, socket, true, head)){
socket.destroy();
}
return this;
},
check: function(req, res){
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.close();
sys.log('Couldnt find client with session id "' + parts[2] + '"');
}
} else {
this._onConnection(parts[1], req, res);
}
return true;
}
return false;
},
});
_lookupClient: function(sid){
return this.clientsIndex[sid];
},
_onClientConnect: function(client){
if (!(client instanceof Client) || !client.sessionId){
return sys.log('Invalid client');
}
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){
if (this.options.transports.indexOf(transport) === -1){
req.connection.close();
return sys.log('Illegal transport "'+ transport +'"');
}
sys.log('Initializing client with transport "'+ transport +'"');
new Transports[transport](this, req, res);
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,
tcp = require('tcp'),
listeners = [];
var net = require('net'),
WebSocket = require('./websocket'),
listeners = [],
netserver,
this.flashsocket = websocket.extend({});
this.flashsocket.init = function(listener){
listeners.push(listener);
Flashsocket = module.exports = function(){
WebSocket.apply(this, arguments);
};
tcp.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.close();
}).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,46 +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){
switch (req.method){
case 'GET':
var self = this;
this.__super__(req, res);
this.request.addListener('end', function(){
if (!('hijack' in self.connection)){
throw new Error('You have to patch Node! Please refer to the README');
}
self.connection.addListener('end', function(){ self._onClose(); });
self.connection.hijack();
self.connection.setTimeout(0);
});
this.response.writeHead(200, { 'Content-type': 'text/html' });
this.response.flush();
this._payload();
break;
case 'POST':
req.addListener('data', function(message){
try {
var msg = qs.parse(message);
self._onMessage(msg.data);
} catch(e){}
res.writeHead(200);
res.write('ok');
res.close();
});
break;
}
},
_write: function(message){
// not sure if this is enough escaping. looks lousy
this.response.write("<script>parent.callback('"+ message.replace(/'/, "\'") +"')</script>");
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,46 +0,0 @@
var Client = require('../client').Client,
qs = require('querystring');
this['server-events'] = Client.extend({
_onConnect: function(req, res){
switch (req.method){
case 'GET':
var self = this;
this.__super__(req, res);
this.request.addListener('end', function(){
if (!('hijack' in self.connection)){
throw new Error('You have to patch Node! Please refer to the README');
}
self.connection.addListener('end', function(){ self._onClose(); });
self.connection.hijack();
self.connection.setTimeout(0);
});
this.response.writeHead(200, { 'Content-type': 'application/x-dom-event-stream' });
this.response.flush();
this._payload();
break;
case 'POST':
req.addListener('data', function(message){
try {
var msg = qs.parse(message);
self._onMessage(msg.data);
} catch(e){}
res.writeHead(200);
res.write('ok');
res.close();
});
break;
}
},
_write: function(message){
this.response.write("Event: socket.io");
this.response.write("data: " + message);
}
});

View File

@@ -1,73 +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, res){
var self = this;
this.__super__(req, res);
this.data = '';
if (this.request.headers['connection'] !== 'Upgrade'
|| this.request.headers['upgrade'] !== 'WebSocket'
|| !this._verifyOrigin(this.request.headers['origin'])){
this.listener.options.log('WebSocket connection invalid');
this.connection.close();
return;
}
this.request.addListener('end', function(){
if (!('hijack' in self.connection)){
throw new Error('You have to patch Node! Please refer to the README');
}
self.connection.hijack();
self.connection.setTimeout(0);
self.connection.setEncoding('utf8');
self.connection.setNoDelay(true);
self.connection.addListener('end', function(){ self._onClose(); });
self.connection.addListener('data', function(data){ self._handle(data); });
});
this.response.use_chunked_encoding_by_default = false;
this.response.writeHeader(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
});
this.response.flush();
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.close();
return false;
}
WebSocket = module.exports = function(){
Client.apply(this, arguments);
};
this._onMessage(chunk.slice(1));
}
require('sys').inherits(WebSocket, Client);
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){
this.connection.write('\u0000' + message + '\uffff');
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;
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();
}
}
this.connection.addListener('end', function(){
self._onClose();
});
this.connection.addListener('data', function(data){
self._handle(data);
});
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,54 +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;
switch (req.method){
case 'GET':
var self = this;
this.__super__(req, res);
this.request.addListener('end', function(){
if (!('hijack' in self.connection)){
throw new Error('You have to patch Node! Please refer to the README');
}
Multipart = module.exports = function(){
Client.apply(this, arguments);
};
self.connection.addListener('end', function(){ self._onClose(); });
self.connection.hijack();
self.connection.setTimeout(0);
});
this.response.use_chunked_encoding_by_default = false;
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){
try {
var msg = qs.parse(message);
self._onMessage(msg.data);
} catch(e){}
res.writeHead(200);
res.write('ok');
res.close();
});
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();
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,44 +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: {
duration: 20000
},
_onConnect: function(req, res){
var self = this;
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){
try {
var msg = qs.parse(message);
self._onMessage(msg.data);
} catch(e){}
res.writeHead(200);
res.write('ok');
res.close();
});
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.close();
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 clone;
if (item instanceof Array){
clone = [];
for (var i = 0; i < item.length; i++) clone[i] = clone(item[i]);
return clone;
} else if (typeof item == 'object') {
clone = {};
for (var key in object) clone[key] = clone(object[key]);
return clone;
} 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

View File

@@ -1,83 +0,0 @@
diff -rup node-v0.1.32-orig/src/node_http.cc node-v0.1.32/src/node_http.cc
--- node-v0.1.32-orig/src/node_http.cc 2010-03-13 13:14:00.000000000 -0800
+++ node-v0.1.32/src/node_http.cc 2010-03-13 13:23:48.000000000 -0800
@@ -57,6 +57,7 @@ HTTPConnection::Initialize (Handle<Objec
client_constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
client_constructor_template->SetClassName(String::NewSymbol("Client"));
NODE_SET_PROTOTYPE_METHOD(client_constructor_template, "resetParser", ResetParser);
+ NODE_SET_PROTOTYPE_METHOD(client_constructor_template, "hijack", Hijack);
target->Set(String::NewSymbol("Client"), client_constructor_template->GetFunction());
t = FunctionTemplate::New(NewServer);
@@ -64,6 +65,7 @@ HTTPConnection::Initialize (Handle<Objec
server_constructor_template->Inherit(Connection::constructor_template);
server_constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
NODE_SET_PROTOTYPE_METHOD(server_constructor_template, "resetParser", ResetParser);
+ NODE_SET_PROTOTYPE_METHOD(server_constructor_template, "hijack", Hijack);
server_constructor_template->SetClassName(String::NewSymbol("ServerSideConnection"));
end_symbol = NODE_PSYMBOL("end");
@@ -101,6 +103,14 @@ Handle<Value> HTTPConnection::ResetParse
}
+Handle<Value> HTTPConnection::Hijack(const Arguments& args) {
+ HandleScope scope;
+ HTTPConnection *connection = ObjectWrap::Unwrap<HTTPConnection>(args.Holder());
+ connection->Hijack();
+ return Undefined();
+}
+
+
void
HTTPConnection::OnReceive (const void *buf, size_t len)
{
@@ -109,6 +119,11 @@ HTTPConnection::OnReceive (const void *b
assert(refs_);
size_t nparsed;
+ if (hijacked) {
+ Connection::OnReceive(buf, len);
+ return;
+ }
+
nparsed = http_parser_execute(&parser_, static_cast<const char*>(buf), len);
if (nparsed != len) {
diff -rup node-v0.1.32-orig/src/node_http.h node-v0.1.32/src/node_http.h
--- node-v0.1.32-orig/src/node_http.h 2010-03-13 13:14:00.000000000 -0800
+++ node-v0.1.32/src/node_http.h 2010-03-13 13:25:05.000000000 -0800
@@ -12,17 +12,21 @@ public:
static void Initialize (v8::Handle<v8::Object> target);
static v8::Persistent<v8::FunctionTemplate> client_constructor_template;
- static v8::Persistent<v8::FunctionTemplate> server_constructor_template;
+ static v8::Persistent<v8::FunctionTemplate> server_constructor_template;
protected:
static v8::Handle<v8::Value> NewClient (const v8::Arguments& args);
static v8::Handle<v8::Value> NewServer (const v8::Arguments& args);
static v8::Handle<v8::Value> ResetParser(const v8::Arguments& args);
+ static v8::Handle<v8::Value> Hijack(const v8::Arguments& args);
+
+ bool hijacked;
HTTPConnection (enum http_parser_type t)
: Connection()
{
type_ = t;
+ hijacked = false;
ResetParser();
}
@@ -41,6 +45,10 @@ protected:
parser_.data = this;
}
+ void Hijack() {
+ hijacked = true;
+ }
+
void OnReceive (const void *buf, size_t len);
void OnEOF ();

View File

@@ -1,83 +0,0 @@
diff -rup node-v0.1.32-orig/src/node_http.cc node-v0.1.32/src/node_http.cc
--- node-v0.1.32-orig/src/node_http.cc 2010-03-13 13:14:00.000000000 -0800
+++ node-v0.1.32/src/node_http.cc 2010-03-13 13:23:48.000000000 -0800
@@ -57,6 +57,7 @@ HTTPConnection::Initialize (Handle<Objec
client_constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
client_constructor_template->SetClassName(String::NewSymbol("Client"));
NODE_SET_PROTOTYPE_METHOD(client_constructor_template, "resetParser", ResetParser);
+ NODE_SET_PROTOTYPE_METHOD(client_constructor_template, "hijack", Hijack);
target->Set(String::NewSymbol("Client"), client_constructor_template->GetFunction());
t = FunctionTemplate::New(NewServer);
@@ -64,6 +65,7 @@ HTTPConnection::Initialize (Handle<Objec
server_constructor_template->Inherit(Connection::constructor_template);
server_constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
NODE_SET_PROTOTYPE_METHOD(server_constructor_template, "resetParser", ResetParser);
+ NODE_SET_PROTOTYPE_METHOD(server_constructor_template, "hijack", Hijack);
server_constructor_template->SetClassName(String::NewSymbol("ServerSideConnection"));
end_symbol = NODE_PSYMBOL("end");
@@ -101,6 +103,14 @@ Handle<Value> HTTPConnection::ResetParse
}
+Handle<Value> HTTPConnection::Hijack(const Arguments& args) {
+ HandleScope scope;
+ HTTPConnection *connection = ObjectWrap::Unwrap<HTTPConnection>(args.Holder());
+ connection->Hijack();
+ return Undefined();
+}
+
+
void
HTTPConnection::OnReceive (const void *buf, size_t len)
{
@@ -109,6 +119,11 @@ HTTPConnection::OnReceive (const void *b
assert(refs_);
size_t nparsed;
+ if (hijacked) {
+ Connection::OnReceive(buf, len);
+ return;
+ }
+
nparsed = http_parser_execute(&parser_, static_cast<const char*>(buf), len);
if (nparsed != len) {
diff -rup node-v0.1.32-orig/src/node_http.h node-v0.1.32/src/node_http.h
--- node-v0.1.32-orig/src/node_http.h 2010-03-13 13:14:00.000000000 -0800
+++ node-v0.1.32/src/node_http.h 2010-03-13 13:25:05.000000000 -0800
@@ -12,17 +12,21 @@ public:
static void Initialize (v8::Handle<v8::Object> target);
static v8::Persistent<v8::FunctionTemplate> client_constructor_template;
- static v8::Persistent<v8::FunctionTemplate> server_constructor_template;
+ static v8::Persistent<v8::FunctionTemplate> server_constructor_template;
protected:
static v8::Handle<v8::Value> NewClient (const v8::Arguments& args);
static v8::Handle<v8::Value> NewServer (const v8::Arguments& args);
static v8::Handle<v8::Value> ResetParser(const v8::Arguments& args);
+ static v8::Handle<v8::Value> Hijack(const v8::Arguments& args);
+
+ bool hijacked;
HTTPConnection (enum http_parser_type t)
: Connection()
{
type_ = t;
+ hijacked = false;
ResetParser();
}
@@ -41,6 +45,10 @@ protected:
parser_.data = this;
}
+ void Hijack() {
+ hijacked = true;
+ }
+
void OnReceive (const void *buf, size_t len);
void OnEOF ();

Submodule test/client deleted from ca21b52818

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