Compare commits

...

133 Commits
0.7.6 ... 0.8.3

Author SHA1 Message Date
Guillermo Rauch
cc275813b5 Updated changelog 2011-09-03 14:06:05 -07:00
Guillermo Rauch
9d57245d65 Release 0.8.3 2011-09-03 12:42:45 -07:00
Guillermo Rauch
9a05b3597e Fixed test for default flash port. 2011-09-03 12:34:36 -07:00
Guillermo Rauch
41f38b60e8 Added parser test for decoding newline. 2011-09-03 12:23:44 -07:00
Guillermo Rauch
a9bbc38919 Fixed \n parsing for non-JSON packets (fixes #479). 2011-09-03 12:14:07 -07:00
Guillermo Rauch
e282ab0e63 Fixed transport message packet logging.
Fixed style.
2011-09-03 12:13:50 -07:00
Guillermo Rauch
546d5203d4 Added test case for #476 to prevent regressions. 2011-09-03 10:49:08 -07:00
Guillermo Rauch
69941e602b Fixed emission of error event resulting in an uncaught exception if unhandled (fixes #476). 2011-09-03 10:48:31 -07:00
Guillermo Rauch
7ac9c2e888 Fixed style 2011-09-03 10:48:23 -07:00
Guillermo Rauch
fa1f50d173 Fixed; allow for falsy values as the configuration value of log level (fixes #491). 2011-09-03 10:29:51 -07:00
Guillermo Rauch
20ddd5f11a Merge branch 'master' of github.com:LearnBoost/socket.io 2011-09-03 10:19:17 -07:00
Guillermo Rauch
e1891fd615 Fixed repository URI in package.json. Fixes #504. 2011-09-03 10:18:52 -07:00
Guillermo Rauch
4d66f78ca2 Merge pull request #502 from einaros/master
Correction on previous pull req, which left out status code
2011-09-01 12:12:32 -07:00
einaros
6db6db41a2 corrected passing of status code for handshake error 2011-08-31 19:55:28 +02:00
Guillermo Rauch
713baa40e1 Merge pull request #455 from 3rd-Eden/update/package
Added socket.io-flashsocket default port
2011-08-31 09:32:16 -07:00
Guillermo Rauch
7c196f5b32 Merge pull request #501 from einaros/master
Fixes issue commented on in #377
2011-08-31 08:50:44 -07:00
einaros
bd360a15ef added text/plain content-type to handshake responses 2011-08-31 14:48:43 +02:00
einaros
ae7f25332a switched \u**** to \x** for single byte writes, as the former doesn't really make any sense 2011-08-30 11:17:32 +02:00
einaros
63fc15d276 style changes 2011-08-30 11:03:50 +02:00
Guillermo Rauch
2c5fa40c0d Release 0.8.2 2011-08-29 10:36:23 -07:00
Guillermo Rauch
0b61eda84c Release 0.8.1 2011-08-29 09:42:16 -07:00
Guillermo Rauch
23ba929f3f Merge branch 'master' of github.com:LearnBoost/socket.io 2011-08-29 09:37:54 -07:00
einaros
13647075f2 fixed utf8 bug in send framing 2011-08-29 09:43:12 +02:00
Guillermo Rauch
4c9414c4c1 Merge pull request #486 from Znarkus/patch-1
Fixed typo.
2011-08-28 23:57:28 -07:00
Markus Hedlund
ec88f95722 Fixed typo. 2011-08-29 20:23:38 +03:00
einaros
f377cd631e fixed bug in send framing for over 64kB of data 2011-08-29 08:33:30 +02:00
einaros
e41aab84f8 corrected ping handling from websocket transport, and added warning output on parser error 2011-08-29 08:00:03 +02:00
einaros
0a6c78cbb8 joined compatible hybi protocol handlers and updated test reference 2011-08-29 07:56:40 +02:00
einaros
004130cb11 fixed Parser library path and did some code cleanup 2011-08-29 07:30:29 +02:00
Guillermo Rauch
a300223122 Release 0.8.0 2011-08-28 15:42:19 -07:00
Guillermo Rauch
0769c40368 Renamed wsver/ -> websocket/ 2011-08-28 15:41:55 -07:00
Guillermo Rauch
355203afdb Changed protocols require path. 2011-08-28 15:41:13 -07:00
einaros
46bfcd0d83 Merge remote branch 'upstream/master' 2011-08-28 11:20:01 +02:00
einaros
46b2f86372 added hybi07 tests 2011-08-28 11:19:22 +02:00
einaros
e5c86178f5 added initial hybi07 protocol parser 2011-08-28 10:44:07 +02:00
Guillermo Rauch
0c31d6eabf Release 0.7.11 2011-08-27 15:29:33 -07:00
einaros
0e08d67e48 cleanups and comments 2011-08-27 23:43:16 +02:00
Guillermo Rauch
42904cb3d7 Release 0.7.10 2011-08-27 11:42:55 -07:00
einaros
8efb1bc6e2 added test and support for really long messages, but capped by 32 bit 2011-08-27 19:21:01 +02:00
einaros
8bdf221935 minor cleanups 2011-08-27 18:42:17 +02:00
einaros
899fb7faa1 updated to work with two-level websocket versioning 2011-08-27 18:27:52 +02:00
einaros
34622b74ef added hybi10 close operation 2011-08-27 17:54:13 +02:00
einaros
d327976064 added hybi10 parser tests 2011-08-27 17:45:39 +02:00
einaros
07b9c4696d added hybi10 support 2011-08-27 17:41:49 +02:00
einaros
7a5913b8a6 Added http referrer verification to manager.js verifyOrigin + tests for origins setting 2011-08-16 07:46:57 +02:00
Guillermo Rauch
fbb268fbce Release 0.7.9 2011-08-12 10:19:18 -07:00
Guillermo Rauch
a31c267e83 Bumped version. 2011-08-12 06:56:26 -07:00
Guillermo Rauch
b82fd79f57 Updated package.json socket.io-client and bumped version. 2011-08-12 06:55:37 -07:00
Arnout Kazemier
00b75759f1 Added socket.io-flashsocket default port 2011-08-09 19:33:27 +02:00
Guillermo Rauch
f85ce74a1f Merge pull request #446 from 3rd-Eden/testsuite
Testsuite fixes
2011-08-08 12:07:07 -07:00
Guillermo Rauch
65f1399a44 Release 0.7.8 2011-08-08 08:06:30 -07:00
Arnout Kazemier
894ec9f84e Make sure we only do garbage collection when the server we receive
is actually ran.
2011-08-04 22:03:55 +02:00
Guillermo Rauch
d86ffcf06d Merge pull request #408 from 3rd-Eden/gc
Garbage collection
2011-08-04 11:50:39 -07:00
Arnout Kazemier
ab5beaff63 Fix 2011-08-04 20:32:49 +02:00
Arnout Kazemier
f0ef33b45f Merge branch 'master' of github.com:LearnBoost/socket.io into gc
Conflicts:
	lib/manager.js
2011-08-04 20:32:43 +02:00
Guillermo Rauch
a4ec5aafa6 Merge pull request #363 from dvv/489bc860d2f050a7925e602a6d16c428f2784b40
small cleanup
2011-07-30 20:32:39 -07:00
Guillermo Rauch
984639ba67 Merge pull request #381 from 3rd-Eden/alias
Added alias for to to in and in to to
2011-07-30 20:27:30 -07:00
Guillermo Rauch
9923c1dee9 Merge branch 'master' of github.com:LearnBoost/socket.io 2011-07-30 20:23:35 -07:00
Guillermo Rauch
1b0a4849df Added test (fixes #380). 2011-07-30 20:21:13 -07:00
Guillermo Rauch
4fc43f322f Changed; make sure sio#listen passes options to both HTTP server and socket.io manager.
Added docs for sio#listen.
2011-07-30 20:20:31 -07:00
Guillermo Rauch
5c0f78ab02 Added options parameter support for Manager constructor. 2011-07-30 20:19:38 -07:00
Guillermo Rauch
d8c7060cc8 Merge pull request #396 from 3rd-Eden/xxs
Potential fix for #372
2011-07-30 18:49:47 -07:00
Guillermo Rauch
b0335b0a61 Merge pull request #339 from 3rd-Eden/333
Don't require redis by default
2011-07-30 18:47:41 -07:00
Guillermo Rauch
a1797ccd4b Merge pull request #337 from 3rd-Eden/handshakeData
Expose path and querystring in handshakeData
2011-07-30 18:47:12 -07:00
Guillermo Rauch
a1c997bc58 Merge pull request #401 from Pita/master
Send response once we got all POST data, not immediately
2011-07-30 18:36:08 -07:00
Guillermo Rauch
0b9b28d251 Merge pull request #409 from 3rd-Eden/bug/407
Fix for #407
2011-07-30 17:59:06 -07:00
Guillermo Rauch
a79b2fa761 Added memory leaks tests. 2011-07-30 17:53:35 -07:00
Guillermo Rauch
195eba74de Added assertvanish dev dependency. 2011-07-30 17:53:22 -07:00
Guillermo Rauch
3edebe5d61 Added test-leaks Makefile task. 2011-07-30 17:53:14 -07:00
Guillermo Rauch
b56389fbc8 Removed auto npm-linking from make test. 2011-07-30 14:46:54 -07:00
Guillermo Rauch
203293db0b Merge pull request #413 from 3rd-Eden/update/package
updated package.json with new build of policyfile and redis
2011-07-30 14:33:09 -07:00
Guillermo Rauch
2b7ea448c4 Merge pull request #416 from 3rd-Eden/test/374
Added testcase to prevent #374 from happening again
2011-07-30 14:32:51 -07:00
Guillermo Rauch
c627f1b7d0 Merge pull request #421 from 3rd-Eden/bug/heartbeat
Make sure that you can disable heart beats
2011-07-30 14:32:34 -07:00
Guillermo Rauch
4c20afd4b7 Merge pull request #346 from ericz/patch-1
Change `;` typo to `,` in first example
2011-07-26 01:48:22 -07:00
Arnout Kazemier
f689434f61 Make sure that you can disable heart beats 2011-07-25 22:12:48 +02:00
Arnout Kazemier
b694ee68c9 Added testcase to prevent #374 from happening again 2011-07-24 13:21:30 +02:00
Guillermo Rauch
8b22ca2ffd Merge pull request #415 from 3rd-Eden/bug/rooms
Bug/rooms
2011-07-24 02:30:36 -07:00
Arnout Kazemier
5c50c4844f fixed missing done() that cuased the suite to timeout 2011-07-24 11:27:43 +02:00
Arnout Kazemier
4fcad6e4bc updated package.json with new build of policyfile and redis 2011-07-23 22:59:32 +02:00
Arnout Kazemier
3b2316e0d8 Fixed rooms memory leak 2011-07-23 22:55:29 +02:00
Arnout Kazemier
a821cce390 Fix for #407 2011-07-22 22:52:20 +02:00
Arnout Kazemier
abe142ac66 Merge branch 'master' of github.com:LearnBoost/socket.io into gc 2011-07-22 21:42:09 +02:00
Arnout Kazemier
5eff0e5ca7 Small clean up, so the the code makes a bit more sense 2011-07-22 21:37:13 +02:00
Guillermo Rauch
c06242efd3 Merge pull request #383 from AD7six/feature/no-duplicate-clients-in-rooms
prevent duplicate references in rooms - probable fix for #379
2011-07-22 07:51:54 -07:00
Peter 'Pita' Martischka
f69f387e1d Send response once we got all POST data, not immediately 2011-07-20 17:29:26 +01:00
Guillermo Rauch
f5c10aec7f Merge pull request #395 from 3rd-Eden/bug/376
Fix for bug #376
2011-07-18 14:12:15 -07:00
Arnout Kazemier
34bd9d9092 Fix for bug #376
Also added unit test to prevent it from happening in the future
2011-07-18 23:06:48 +02:00
Andy Dawson
c30151d03a Remove unused Cl (client) variable.
This test doesn't use any real clients.
2011-07-18 13:22:08 -07:00
Arnout Kazemier
f784c477f0 Potential fix for #372 2011-07-18 22:02:03 +02:00
AD7six
c899c98f31 prevent duplicate references in rooms 2011-07-15 01:13:41 +02:00
Arnout Kazemier
53f0f4d66d Added alias for to to in and in to to 2011-07-14 21:31:55 +02:00
Guillermo Rauch
c826fadb9f Fixed roomClients definition (#378). 2011-07-13 18:30:24 -03:00
Guillermo Rauch
097094cd7a Updated dep [thanks @3rd-Eden] 2011-07-12 05:11:03 -03:00
Guillermo Rauch
c3fa1bf5af Release 0.7.7 2011-07-12 05:05:49 -03:00
Guillermo Rauch
8798cfbced Merge pull request #340 from 3rd-Eden/291
It was emitting uknown room, so the messages where never send
2011-07-12 00:55:49 -07:00
Arnout Kazemier
81d71ebb08 Fixed port number 2011-07-12 09:52:53 +02:00
Guillermo Rauch
074e74c6c5 Added test for emitting to closed clients to prevent regression. 2011-07-12 04:35:07 -03:00
Guillermo Rauch
65b8272724 Merge branch 'master' of github.com:LearnBoost/socket.io 2011-07-12 04:29:56 -03:00
Guillermo Rauch
ca3f3379cb Fixed race condition in redis test. 2011-07-12 04:29:30 -03:00
Guillermo Rauch
faad10baee Changed Transport#end instrumentation. 2011-07-12 04:29:11 -03:00
Guillermo Rauch
d3eac92eaa Leveraged $emit instead of emit internally. 2011-07-12 04:28:55 -03:00
Guillermo Rauch
fb5b9bc0b1 Made tests faster. 2011-07-12 03:42:05 -03:00
Guillermo Rauch
34505071f4 Fixed double disconnect events. 2011-07-12 03:41:43 -03:00
Guillermo Rauch
0a2d0b9d0b Fixed disconnect logic 2011-07-12 03:41:15 -03:00
Guillermo Rauch
4495f5987a Simplified remote events handling in Socket. 2011-07-11 23:51:42 -03:00
Guillermo Rauch
aad29d5d92 Fixed double dispatch handling. 2011-07-11 23:51:24 -03:00
Guillermo Rauch
ad8452035d Fixed; increased timeout. 2011-07-11 23:51:08 -03:00
Arnout Kazemier
b2ffed891b Fixed typo 2011-07-11 00:12:27 -07:00
ericz
27ab98dca4 Fixed typo 2011-07-05 14:26:46 -07:00
Eric Zhang
a8ca11cb47 Change ; typo to , in first example 2011-07-05 14:22:55 -07:00
Arnout Kazemier
d39d1401c4 Fixed broken test suite 2011-07-05 00:59:31 +02:00
Arnout Kazemier
159c75096d Removed console statement 2011-07-05 00:38:24 +02:00
Arnout Kazemier
a70347b15f Fixed #285 2011-07-05 00:37:19 +02:00
Arnout Kazemier
1a2c8aa31f It was emitting uknown room, so the messages where never send fixes #291 2011-07-05 00:17:02 +02:00
Arnout Kazemier
15e1e68cfd Merge branch 'master' of github.com:LearnBoost/socket.io into 333 2011-07-04 22:53:20 +02:00
Arnout Kazemier
167da44211 Fixes #333 2011-07-04 22:51:00 +02:00
Arnout Kazemier
1372838092 Expose path and querystring in handshakeData 2011-07-04 19:26:22 +02:00
Guillermo Rauch
7257e1ec36 Merge pull request #329 from 3rd-Eden/remoteAddress
.address() returned server ip >_<, solved with this patch and adjusted th
2011-07-03 05:43:48 -07:00
Arnout Kazemier
af5960bc28 address.remoteAddress -> address.address just like node's 2011-07-02 19:50:18 +02:00
Arnout Kazemier
206cfdf9c6 Merge branch 'master' of github.com:LearnBoost/socket.io into remoteAddress
Conflicts:
	lib/manager.js
2011-07-02 17:52:09 +02:00
Arnout Kazemier
65d7229079 .address() returned server ip >_<, solved with this patch and adjusted the test
suite
2011-07-02 17:49:32 +02:00
Guillermo Rauch
c18aa40ba6 Merge pull request #328 from coolbloke1324/master
Client IP instead of server IP for connectionAddress.
2011-07-02 08:41:33 -07:00
coolbloke1324
249f33da16 Updated Manager.prototype.handshakeData to provide connection.remoteAddress instead of .address() because .address() was returning server IP and .remoteAddress returns client IP. 2011-07-02 08:30:18 -07:00
Guillermo Rauch
b9a7c8be90 Merge pull request #325 from 3rd-Eden/ssl
See https://github.com/joyent/node/issues/1055
2011-07-01 12:41:02 -07:00
Arnout Kazemier
e4ac72a316 Unfucked space 2011-07-01 21:26:43 +02:00
Arnout Kazemier
dff9cbfe1b See https://github.com/joyent/node/issues/1055 2011-07-01 21:24:51 +02:00
Vladimir Dronnikov
489bc860d2 cleanup 2011-07-01 09:50:37 -07:00
Guillermo Rauch
29a8fff576 Removed transports definition in chat example. 2011-06-30 18:59:52 -03:00
Guillermo Rauch
e66a68f0fa Fixed room cleanup 2011-06-30 18:59:35 -03:00
Guillermo Rauch
4058eacbd4 Fixed; make sure the client is cleaned up after booting. 2011-06-30 18:58:55 -03:00
Guillermo Rauch
8cbd1544b9 Make sure to mark the client as non-open if the connection is closed. 2011-06-30 18:58:29 -03:00
Guillermo Rauch
f302744fec Removed unneeded buffer declarations. 2011-06-30 18:58:06 -03:00
Guillermo Rauch
a3ba4e2c10 Fixed; make sure to clear socket handlers and subscriptions upon transport close. 2011-06-30 18:19:41 -03:00
30 changed files with 1996 additions and 439 deletions

View File

@@ -1,4 +1,95 @@
0.8.3 / 2011-09-03
==================
* Fixed `\n` parsing for non-JSON packets (fixes #479).
* Fixed parsing of certain unicode characters (fixes #451).
* Fixed transport message packet logging.
* Fixed emission of `error` event resulting in an uncaught exception if unhandled (fixes #476).
* Fixed; allow for falsy values as the configuration value of `log level` (fixes #491).
* Fixed repository URI in `package.json`. Fixes #504.
* Added text/plain content-type to handshake responses [einaros]
* Improved single byte writes [einaros]
* Updated socket.io-flashsocket default port from 843 to 10843 [3rd-Eden]
* Updated client.
0.8.2 / 2011-08-29
==================
* Updated client.
0.8.1 / 2011-08-29
==================
* Fixed utf8 bug in send framing in websocket [einaros]
* Fixed typo in docs [Znarkus]
* Fixed bug in send framing for over 64kB of data in websocket [einaros]
* Corrected ping handling in websocket transport [einaros]
0.8.0 / 2011-08-28
==================
* Updated to work with two-level websocket versioning. [einaros]
* Added hybi07 support. [einaros]
* Added hybi10 support. [einaros]
* Added http referrer verification to manager.js verifyOrigin. [einaors]
0.7.11 / 2011-08-27
===================
* Updated socket.io-client.
0.7.10 / 2011-08-27
===================
* Updated socket.io-client.
0.7.9 / 2011-08-12
==================
* Updated socket.io-client.
* Make sure we only do garbage collection when the server we receive is actually run.
0.7.8 / 2011-08-08
==================
* Changed; make sure sio#listen passes options to both HTTP server and socket.io manager.
* Added docs for sio#listen.
* Added options parameter support for Manager constructor.
* Added memory leaks tests and test-leaks Makefile task.
* Removed auto npm-linking from make test.
* Make sure that you can disable heartbeats. [3rd-Eden]
* Fixed rooms memory leak [3rd-Eden]
* Send response once we got all POST data, not immediately [Pita]
* Fixed onLeave behavior with missing clientsk [3rd-Eden]
* Prevent duplicate references in rooms.
* Added alias for `to` to `in` and `in` to `to`.
* Fixed roomClients definition.
* Removed dependency on redis for installation without npm [3rd-Eden]
* Expose path and querystring in handshakeData [3rd-Eden]
0.7.7 / 2011-07-12
==================
* Fixed double dispatch handling with emit to closed clients.
* Added test for emitting to closed clients to prevent regression.
* Fixed race condition in redis test.
* Changed Transport#end instrumentation.
* Leveraged $emit instead of emit internally.
* Made tests faster.
* Fixed double disconnect events.
* Fixed disconnect logic
* Simplified remote events handling in Socket.
* Increased testcase timeout.
* Fixed unknown room emitting (GH-291). [3rd-Eden]
* Fixed `address` in handshakeData. [3rd-Eden]
* Removed transports definition in chat example.
* Fixed room cleanup
* Fixed; make sure the client is cleaned up after booting.
* Make sure to mark the client as non-open if the connection is closed.
* Removed unneeded `buffer` declarations.
* Fixed; make sure to clear socket handlers and subscriptions upon transport close.
0.7.6 / 2011-06-30
==================

View File

@@ -2,8 +2,8 @@
ALL_TESTS = $(shell find test/ -name '*.test.js')
run-tests:
@npm link > /dev/null --local
@./node_modules/.bin/expresso \
-t 3000 \
-I support \
-I lib \
--serial \
@@ -16,4 +16,7 @@ test:
test-cov:
@TESTFLAGS=--cov $(MAKE) test
test-leaks:
@ls test/leaks/* | xargs node --expose_debug_as=debug --expose_gc
.PHONY: test

View File

@@ -20,7 +20,7 @@ Next, attach it to a HTTP/HTTPS server. If you're using the fantastic `express`
web framework:
```js
var app = express.createServer();
var app = express.createServer()
, io = io.listen(app);
app.listen(80);
@@ -60,7 +60,7 @@ Besides `connect`, `message` and `disconnect`, you can emit custom events:
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
io.sockets.emit('this', { will: 'be received by everyone');
io.sockets.emit('this', { will: 'be received by everyone' });
socket.on('private message', function (from, msg) {
console.log('I received a private message by ', from, ' saying ', msg);

View File

@@ -61,10 +61,6 @@ app.listen(3000, function () {
var io = sio.listen(app)
, nicknames = {};
io.set('transports', [
, 'xhr-polling'
]);
io.sockets.on('connection', function (socket) {
socket.on('user message', function (msg) {
socket.broadcast.emit('user message', socket.nickname, msg);

View File

@@ -1,4 +1,3 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
@@ -9,9 +8,7 @@
* Module dependencies.
*/
var http = require('http')
, https = require('https')
, fs = require('fs')
var fs = require('fs')
, url = require('url')
, util = require('./util')
, store = require('./store')
@@ -55,7 +52,7 @@ var parent = module.parent.exports
* @api public
*/
function Manager (server) {
function Manager (server, options) {
this.server = server;
this.namespaces = {};
this.sockets = this.of('');
@@ -74,7 +71,7 @@ function Manager (server) {
, 'heartbeat interval': 20
, 'polling duration': 20
, 'flash policy server': true
, 'flash policy port': 843
, 'flash policy port': 10843
, 'destroy upgrade': true
, 'browser client': true
, 'browser client minification': false
@@ -83,6 +80,10 @@ function Manager (server) {
, 'client store expiration': 15
};
for (var i in options) {
this.settings[i] = options[i];
}
this.initStore();
// reset listeners
@@ -99,6 +100,14 @@ function Manager (server) {
self.handleUpgrade(req, socket, head);
});
server.on('close', function () {
clearInterval(self.gc);
});
server.once('listening', function () {
self.gc = setInterval(self.garbageCollection.bind(self), 10000);
});
for (var i in transports) {
if (transports[i].init) {
transports[i].init(this);
@@ -132,7 +141,7 @@ Manager.prototype.__defineGetter__('log', function () {
if (this.disabled('log')) return;
var logger = this.get('logger');
logger.level = this.get('log level');
logger.level = this.get('log level') || -1;
return logger;
});
@@ -324,18 +333,6 @@ Manager.prototype.onOpen = function (id) {
*/
Manager.prototype.onDispatch = function (room, packet, volatile, exceptions) {
// go through the users who have pending buffers
for (var i = 0, l = this.closedA.length; i < l; i++) {
if (!this.roomClients[this.closedA[i]]) continue;
if (this.roomClients[this.closedA[i]][room]) {
if (!~exceptions.indexOf(this.closedA[i])) {
this.closed[this.closedA[i]].push(packet);
}
}
}
// go through room clients
if (this.rooms[room]) {
for (var i = 0, l = this.rooms[room].length; i < l; i++) {
var id = this.rooms[room][i];
@@ -359,15 +356,17 @@ Manager.prototype.onDispatch = function (room, packet, volatile, exceptions) {
Manager.prototype.onJoin = function (id, name) {
if (!this.roomClients[id]) {
this.roomClients[id] = [];
this.roomClients[id] = {};
}
if (!this.rooms[name]) {
this.rooms[name] = [];
}
this.rooms[name].push(id);
this.roomClients[id][name] = true;
if (!~this.rooms[name].indexOf(id)) {
this.rooms[name].push(id);
this.roomClients[id][name] = true;
}
};
/**
@@ -378,7 +377,15 @@ Manager.prototype.onJoin = function (id, name) {
Manager.prototype.onLeave = function (id, room) {
if (this.rooms[room]) {
this.rooms[room].splice(this.rooms[room].indexOf(id), 1);
var index = this.rooms[room].indexOf(id);
if (index >= 0) {
this.rooms[room].splice(index, 1);
}
if (!this.rooms[room].length) {
delete this.rooms[room];
}
delete this.roomClients[id][room];
}
};
@@ -390,6 +397,10 @@ Manager.prototype.onLeave = function (id, room) {
*/
Manager.prototype.onClose = function (id) {
if (this.open[id]) {
delete this.open[id];
}
this.closed[id] = [];
this.closedA.push(id);
@@ -438,6 +449,8 @@ Manager.prototype.onClientDisconnect = function (id, reason) {
this.namespaces[name].handleDisconnect(id, reason);
}
}
this.onDisconnect(id);
};
/**
@@ -469,8 +482,9 @@ Manager.prototype.onDisconnect = function (id, local) {
if (this.roomClients[id]) {
for (var room in this.roomClients[id]) {
this.rooms[room].splice(this.rooms.indexOf(id), 1);
this.onLeave(id, room);
}
delete this.roomClients[id]
}
this.store.destroyClient(id, this.get('client store expiration'));
@@ -587,9 +601,10 @@ Manager.prototype.handleClient = function (data, req) {
return;
}
var transport = new transports[data.transport](this, data, req);
var transport = new transports[data.transport](this, data, req)
, handshaken = this.handshaken[data.id];
if (this.handshaken[data.id]) {
if (handshaken) {
if (transport.open) {
if (this.closed[data.id] && this.closed[data.id].length) {
transport.payload(this.closed[data.id]);
@@ -605,6 +620,11 @@ Manager.prototype.handleClient = function (data, req) {
this.onConnect(data.id);
this.store.publish('connect', data.id);
// flag as used
delete handshaken.issued;
this.onHandshake(data.id, handshaken);
this.store.publish('handshake', data.id, handshaken);
// initialize the socket for all namespaces
for (var i in this.namespaces) {
var socket = this.namespaces[i].socket(data.id, true);
@@ -755,7 +775,7 @@ Manager.prototype.handleHandshake = function (data, req, res) {
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end('io.j[' + data.query.jsonp + '](new Error("' + message + '"));');
} else {
res.writeHead(status);
res.writeHead(status, { 'Content-Type': 'text/plain' });
res.end(message);
}
};
@@ -779,7 +799,7 @@ Manager.prototype.handleHandshake = function (data, req, res) {
var id = self.generateId()
, hs = [
id
, self.get('heartbeat timeout') || ''
, self.enabled('heartbeats') ? self.get('heartbeat timeout') || '' : ''
, self.get('close timeout') || ''
, self.transports(data).join(',')
].join(':');
@@ -788,7 +808,7 @@ Manager.prototype.handleHandshake = function (data, req, res) {
hs = 'io.j[' + data.query.jsonp + '](' + JSON.stringify(hs) + ');';
res.writeHead(200, { 'Content-Type': 'application/javascript' });
} else {
res.writeHead(200);
res.writeHead(200, { 'Content-Type': 'text/plain' });
}
res.end(hs);
@@ -811,16 +831,31 @@ Manager.prototype.handleHandshake = function (data, req, res) {
*/
Manager.prototype.handshakeData = function (data) {
var connectionAddress = null;
if (data.request.connection.address) {
connectionAddress = data.request.connection.address();
var connection = data.request.connection
, connectionAddress
, date = new Date;
if (connection.remoteAddress) {
connectionAddress = {
address: connection.remoteAddress
, port: connection.remotePort
};
} else if (connection.socket && connection.socket.remoteAddress) {
connectionAddress = {
address: connection.socket.remoteAddress
, port: connection.socket.remotePort
};
}
return {
headers: data.headers
, address: connectionAddress
, time: (new Date).toString()
, time: date.toString()
, query: data.query
, url: data.request.url
, xdomain: !!data.request.headers.origin
, secure: data.request.connection.secure
, issued: +date
};
};
@@ -831,7 +866,7 @@ Manager.prototype.handshakeData = function (data) {
*/
Manager.prototype.verifyOrigin = function (request) {
var origin = request.headers.origin
var origin = request.headers.origin || request.headers.referer
, origins = this.get('origins');
if (origin === 'null') origin = '*';
@@ -843,14 +878,19 @@ Manager.prototype.verifyOrigin = function (request) {
if (origin) {
try {
var parts = url.parse(origin);
return
~origins.indexOf(parts.host + ':' + parts.port) ||
~origins.indexOf(parts.host + ':*') ||
var ok =
~origins.indexOf(parts.hostname + ':' + parts.port) ||
~origins.indexOf(parts.hostname + ':*') ||
~origins.indexOf('*:' + parts.port);
} catch (ex) {}
if (!ok) this.log.warn('illegal origin: ' + origin);
return ok;
} catch (ex) {
this.log.warn('error parsing origin');
}
}
else {
this.log.warn('origin missing from handshake, yet required by config');
}
return false;
};
@@ -950,6 +990,8 @@ Manager.prototype.checkRequest = function (req) {
/**
* Declares a socket namespace
*
* @api public
*/
Manager.prototype.of = function (nsp) {
@@ -959,3 +1001,26 @@ Manager.prototype.of = function (nsp) {
return this.namespaces[nsp] = new SocketNamespace(this, nsp);
};
/**
* Perform garbage collection on long living objects and properties that cannot
* be removed automatically.
*
* @api private
*/
Manager.prototype.garbageCollection = function () {
// clean up unused handshakes
var ids = Object.keys(this.handshaken)
, i = ids.length
, now = Date.now()
, handshake;
while (i--) {
handshake = this.handshaken[ids[i]];
if ('issued' in handshake && (now - handshake.issued) >= 3E4) {
this.onDisconnect(ids[i]);
}
}
};

View File

@@ -49,7 +49,7 @@ SocketNamespace.prototype.$emit = EventEmitter.prototype.emit;
SocketNamespace.prototype.clients = function (room) {
var room = this.name + (room !== undefined ?
(this.name !== '' ? '/' : '') + room : '');
'/' + room : '');
if (!this.manager.rooms[room]) {
return [];
@@ -108,8 +108,8 @@ SocketNamespace.prototype.__defineGetter__('volatile', function () {
* @api public
*/
SocketNamespace.prototype.in = function (room) {
this.flags.endpoint = (this.name === '' ? '' : (this.name + '/')) + room;
SocketNamespace.prototype.in = SocketNamespace.prototype.to = function (room) {
this.flags.endpoint = this.name + (room ? '/' + room : '');
return this;
};
@@ -181,7 +181,7 @@ SocketNamespace.prototype.send = function (data) {
*/
SocketNamespace.prototype.emit = function (name) {
if (name == 'connection' || name == 'newListener') {
if (name == 'newListener') {
return this.$emit.apply(this, arguments);
}
@@ -227,6 +227,7 @@ SocketNamespace.prototype.authorization = function (fn) {
SocketNamespace.prototype.handleDisconnect = function (sid, reason) {
if (this.sockets[sid] && this.sockets[sid].readable) {
this.sockets[sid].onDisconnect(reason);
delete this.sockets[sid];
}
};
@@ -287,7 +288,7 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
socket.packet({ type: 'connect' });
// emit connection event
self.emit('connection', socket);
self.$emit('connection', socket);
};
switch (packet.type) {
@@ -323,8 +324,9 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
case 'event':
var params = [packet.name].concat(packet.args);
if (dataAck)
if (dataAck) {
params.push(ack);
}
socket.$emit.apply(socket, params);
break;
@@ -333,7 +335,7 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
this.manager.onLeave(sessid, this.name);
this.store.publish('leave', sessid, this.name);
socket.emit('disconnect', packet.reason || 'packet');
socket.$emit('disconnect', packet.reason || 'packet');
break;
case 'json':
@@ -343,6 +345,6 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
if (dataAck)
params.push(ack);
socket.emit.apply(socket, params);
socket.$emit.apply(socket, params);
};
};

View File

@@ -138,7 +138,7 @@ exports.encodePayload = function (packets) {
* @api private
*/
var regexp = /^([^:]+):([0-9]+)?(\+)?:([^:]+)?:?(.*)?$/;
var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;
exports.decodePacket = function (data) {
var pieces = data.match(regexp);

View File

@@ -15,7 +15,7 @@ var client = require('socket.io-client');
* Version.
*/
exports.version = '0.7.6';
exports.version = '0.8.3';
/**
* Supported protocol version.
@@ -32,6 +32,9 @@ exports.clientVersion = client.version;
/**
* Attaches a manager
*
* @param {HTTPServer/Number} a HTTP/S server or a port number to listen on.
* @param {Object} opts to be passed to Manager and/or http server
* @param {Function} callback if a port is supplied
* @api public
*/
@@ -65,7 +68,7 @@ exports.listen = function (server, options, fn) {
}
// otherwise assume a http/s server
return new exports.Manager(server);
return new exports.Manager(server, options);
};
/**

View File

@@ -11,7 +11,7 @@
var parser = require('./parser')
, util = require('./util')
, EventEmitter = process.EventEmitter;
, EventEmitter = process.EventEmitter
/**
* Export the constructor.
@@ -20,20 +20,10 @@ var parser = require('./parser')
exports = module.exports = Socket;
/**
* Reserved event names.
* Default error event listener to prevent uncaught exceptions.
*/
var events = {
message: 1
, connect: 1
, disconnect: 1
, open: 1
, close: 1
, error: 1
, retry: 1
, reconnect: 1
, newListener: 1
};
var defaultError = function () {};
/**
* Socket constructor.
@@ -55,6 +45,7 @@ function Socket (manager, id, nsp, readable) {
this.setFlags();
this.readable = readable;
this.store = this.manager.store.client(this.id);
this.on('error', defaultError);
};
/**
@@ -122,7 +113,7 @@ Socket.prototype.__defineGetter__('broadcast', function () {
* @api public
*/
Socket.prototype.to = function (room) {
Socket.prototype.to = Socket.prototype.in = function (room) {
this.flags.room = room;
return this;
};
@@ -149,7 +140,7 @@ Socket.prototype.setFlags = function () {
Socket.prototype.onDisconnect = function (reason) {
if (!this.disconnected) {
this.emit('disconnect', reason);
this.$emit('disconnect', reason);
this.disconnected = true;
}
};
@@ -162,7 +153,7 @@ Socket.prototype.onDisconnect = function (reason) {
Socket.prototype.join = function (name, fn) {
var nsp = this.namespace.name
, name = (nsp === '' ? '' : (nsp + '/')) + name;
, name = (nsp + '/') + name;
this.manager.onJoin(this.id, name);
this.manager.store.publish('join', this.id, name);
@@ -183,7 +174,7 @@ Socket.prototype.join = function (name, fn) {
Socket.prototype.leave = function (name, fn) {
var nsp = this.namespace.name
, name = (nsp === '' ? '' : (nsp + '/')) + name;
, name = (nsp + '/') + name;
this.manager.onLeave(this.id, name);
this.manager.store.publish('leave', this.id, name);
@@ -293,13 +284,8 @@ Socket.prototype.disconnect = function () {
if (this.manager.transports[this.id] && this.manager.transports[this.id].open) {
this.manager.transports[this.id].onForcedDisconnect();
} else {
if (this.manager.open[this.id]) {
// if the connection is open in a different node
this.manager.store.publish('disconnect-force:' + this.id);
} else {
this.manager.onClientDisconnect(this.id);
this.manager.store.publish('disconnect:' + this.id);
}
this.manager.onClientDisconnect(this.id);
this.manager.store.publish('disconnect:' + this.id);
}
}
@@ -342,7 +328,7 @@ Socket.prototype.$emit = EventEmitter.prototype.emit;
*/
Socket.prototype.emit = function (ev) {
if (events[ev]) {
if (ev == 'newListener') {
return this.$emit.apply(this, arguments);
}

View File

@@ -11,8 +11,7 @@
var crypto = require('crypto')
, Store = require('../store')
, assert = require('assert')
, redis = require('redis');
, assert = require('assert');
/**
* Exports the constructor.
@@ -25,6 +24,7 @@ Redis.Client = Client;
* Redis store.
* Options:
* - nodeId (fn) gets an id that uniquely identifies this node
* - redis (fn) redis constructor, defaults to redis
* - redisPub (object) options to pass to the pub redis client
* - redisSub (object) options to pass to the sub redis client
* - redisClient (object) options to pass to the general redis client
@@ -60,6 +60,8 @@ function Redis (opts) {
}
}
var redis = opts.redis || require('redis');
// initialize a pubsub client and a regular client
this.pub = redis.createClient(opts.redisPub);
this.sub = redis.createClient(opts.redisSub);

View File

@@ -28,7 +28,6 @@ function Transport (mng, data, req) {
this.id = data.id;
this.disconnected = false;
this.drained = true;
this.buffer = [];
this.handleRequest(req);
};
@@ -260,7 +259,7 @@ Transport.prototype.clearCloseTimeout = function () {
*/
Transport.prototype.setHeartbeatTimeout = function () {
if (!this.heartbeatTimeout) {
if (!this.heartbeatTimeout && this.manager.enabled('heartbeats')) {
var self = this;
this.heartbeatTimeout = setTimeout(function () {
@@ -280,7 +279,7 @@ Transport.prototype.setHeartbeatTimeout = function () {
*/
Transport.prototype.clearHeartbeatTimeout = function () {
if (this.heartbeatTimeout) {
if (this.heartbeatTimeout && this.manager.enabled('heartbeats')) {
clearTimeout(this.heartbeatTimeout);
this.heartbeatTimeout = null;
this.log.debug('cleared heartbeat timeout for client', this.id);
@@ -295,7 +294,7 @@ Transport.prototype.clearHeartbeatTimeout = function () {
*/
Transport.prototype.setHeartbeatInterval = function () {
if (!this.heartbeatInterval) {
if (!this.heartbeatInterval && this.manager.enabled('heartbeats')) {
var self = this;
this.heartbeatInterval = setTimeout(function () {
@@ -357,11 +356,13 @@ Transport.prototype.onMessage = function (packet) {
if ('disconnect' == packet.type && packet.endpoint == '') {
this.log.debug('got disconnection packet');
if (current && current.open) {
if (current) {
current.onForcedDisconnect();
} else {
this.store.publish('disconnect-force:' + this.id);
}
return;
}
if (packet.id && packet.ack != 'data') {
@@ -397,7 +398,7 @@ Transport.prototype.onMessage = function (packet) {
*/
Transport.prototype.clearHeartbeatInterval = function () {
if (this.heartbeatInterval) {
if (this.heartbeatInterval && this.manager.enabled('heartbeats')) {
clearTimeout(this.heartbeatInterval);
this.heartbeatInterval = null;
this.log.debug('cleared heartbeat interval for client', this.id);
@@ -439,6 +440,7 @@ Transport.prototype.close = function () {
Transport.prototype.onClose = function () {
if (this.open) {
this.setCloseTimeout();
this.clearHandlers();
this.open = false;
this.manager.onClose(this.id);
this.store.publish('close', this.id);
@@ -453,7 +455,7 @@ Transport.prototype.onClose = function () {
Transport.prototype.end = function (reason) {
if (!this.disconnected) {
this.log.info('ending socket');
this.log.info('transport end');
var local = this.manager.transports[this.id];
@@ -480,7 +482,6 @@ Transport.prototype.discard = function () {
this.discarded = true;
this.clearTimeouts();
this.clearHandlers();
this.buffer = [];
return this;
};

View File

@@ -24,7 +24,7 @@ exports = module.exports = FlashSocket;
*/
function FlashSocket (mng, data, req) {
WebSocket.call(this, mng, data, req);
return WebSocket.call(this, mng, data, req);
}
/**

View File

@@ -54,6 +54,9 @@ HTTPTransport.prototype.handleRequest = function (req) {
});
req.on('end', function () {
res.writeHead(200, headers);
res.end('1');
self.onData(self.postEncoded ? qs.parse(buffer).d : buffer);
});
@@ -65,9 +68,6 @@ HTTPTransport.prototype.handleRequest = function (req) {
headers['Access-Control-Allow-Credentials'] = 'true';
}
}
res.writeHead(200, headers);
res.end('1');
} else {
this.response = req.res;
@@ -83,9 +83,9 @@ HTTPTransport.prototype.handleRequest = function (req) {
HTTPTransport.prototype.onData = function (data) {
var messages = parser.decodePayload(data);
this.log.debug(this.name + ' received data packet', data);
for (var i = 0, l = messages.length; i < l; i++) {
this.log.debug(this.name + ' received data packet', data);
this.onMessage(messages[i]);
}
};

View File

@@ -70,6 +70,7 @@ JSONPPolling.prototype.doWrite = function (data) {
'Content-Type': 'text/javascript; charset=UTF-8'
, 'Content-Length': Buffer.byteLength(data)
, 'Connection': 'Keep-Alive'
, 'X-XSS-Protection': '0'
});
this.response.write(data);

View File

@@ -9,10 +9,7 @@
* Module requirements.
*/
var Transport = require('../transport')
, EventEmitter = process.EventEmitter
, crypto = require('crypto')
, parser = require('../parser');
var protocolVersions = require('./websocket/');
/**
* Export the constructor.
@@ -28,323 +25,9 @@ exports = module.exports = WebSocket;
*/
function WebSocket (mng, data, req) {
// parser
var self = this;
this.parser = new Parser();
this.parser.on('data', function (packet) {
self.log.debug(self.name + ' received data packet', packet);
self.onMessage(parser.decodePacket(packet));
});
this.parser.on('close', function () {
self.end();
});
this.parser.on('error', function () {
self.end();
});
Transport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
WebSocket.prototype.__proto__ = Transport.prototype;
/**
* Transport name
*
* @api public
*/
WebSocket.prototype.name = 'websocket';
/**
* Called when the socket connects.
*
* @api private
*/
WebSocket.prototype.onSocketConnect = function () {
var self = this;
this.socket.setNoDelay(true);
this.buffer = true;
this.buffered = [];
if (this.req.headers.upgrade !== 'WebSocket') {
this.log.warn(this.name + ' connection invalid');
this.end();
return;
var version = req.headers['sec-websocket-version'];
if (typeof version !== 'undefined' && typeof protocolVersions[version] !== 'undefined') {
return new protocolVersions[version](mng, data, req);
}
var origin = this.req.headers.origin
, location = (this.socket.encrypted ? 'wss' : 'ws')
+ '://' + this.req.headers.host + this.req.url
, waitingForNonce = false;
if (this.req.headers['sec-websocket-key1']) {
// If we don't have the nonce yet, wait for it (HAProxy compatibility).
if (! (this.req.head && this.req.head.length >= 8)) {
waitingForNonce = true;
}
var headers = [
'HTTP/1.1 101 WebSocket Protocol Handshake'
, 'Upgrade: WebSocket'
, 'Connection: Upgrade'
, 'Sec-WebSocket-Origin: ' + origin
, 'Sec-WebSocket-Location: ' + location
];
if (this.req.headers['sec-websocket-protocol']){
headers.push('Sec-WebSocket-Protocol: '
+ this.req.headers['sec-websocket-protocol']);
}
} else {
var headers = [
'HTTP/1.1 101 Web Socket Protocol Handshake'
, 'Upgrade: WebSocket'
, 'Connection: Upgrade'
, 'WebSocket-Origin: ' + origin
, 'WebSocket-Location: ' + location
];
}
try {
this.socket.write(headers.concat('', '').join('\r\n'));
this.socket.setTimeout(0);
this.socket.setNoDelay(true);
this.socket.setEncoding('utf8');
} catch (e) {
this.end();
return;
}
if (waitingForNonce) {
this.socket.setEncoding('binary');
} else if (this.proveReception(headers)) {
self.flush();
}
var headBuffer = '';
this.socket.on('data', function (data) {
if (waitingForNonce) {
headBuffer += data;
if (headBuffer.length < 8) {
return;
}
// Restore the connection to utf8 encoding after receiving the nonce
self.socket.setEncoding('utf8');
waitingForNonce = false;
// Stuff the nonce into the location where it's expected to be
self.req.head = headBuffer.substr(0, 8);
headBuffer = '';
if (self.proveReception(headers)) {
self.flush();
}
return;
}
self.parser.add(data);
});
};
/**
* Writes to the socket.
*
* @api private
*/
WebSocket.prototype.write = function (data) {
if (this.open) {
this.drained = false;
if (this.buffer) {
this.buffered.push(data);
return this;
}
var length = Buffer.byteLength(data)
, buffer = new Buffer(2 + length);
buffer.write('\u0000', 'binary');
buffer.write(data, 1, 'utf8');
buffer.write('\uffff', 1 + length, 'binary');
try {
if (this.socket.write(buffer)) {
this.drained = true;
}
} catch (e) {
this.end();
}
this.log.debug(this.name + ' writing', data);
}
};
/**
* Flushes the internal buffer
*
* @api private
*/
WebSocket.prototype.flush = function () {
this.buffer = false;
for (var i = 0, l = this.buffered.length; i < l; i++) {
this.write(this.buffered.splice(0, 1)[0]);
}
};
/**
* Finishes the handshake.
*
* @api private
*/
WebSocket.prototype.proveReception = function (headers) {
var self = this
, k1 = this.req.headers['sec-websocket-key1']
, k2 = this.req.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){
self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
self.end();
return false;
}
n /= spaces;
md5.update(String.fromCharCode(
n >> 24 & 0xFF,
n >> 16 & 0xFF,
n >> 8 & 0xFF,
n & 0xFF));
});
md5.update(this.req.head.toString('binary'));
try {
this.socket.write(md5.digest('binary'), 'binary');
} catch (e) {
this.end();
}
}
return true;
};
/**
* Writes a payload.
*
* @api private
*/
WebSocket.prototype.payload = function (msgs) {
for (var i = 0, l = msgs.length; i < l; i++) {
this.write(msgs[i]);
}
return this;
};
/**
* Closes the connection.
*
* @api private
*/
WebSocket.prototype.doClose = function () {
this.socket.end();
};
/**
* WebSocket parser
*
* @api public
*/
function Parser () {
this.buffer = '';
this.i = 0;
};
/**
* Inherits from EventEmitter.
*/
Parser.prototype.__proto__ = EventEmitter.prototype;
/**
* Adds data to the buffer.
*
* @api public
*/
Parser.prototype.add = function (data) {
this.buffer += data;
this.parse();
};
/**
* Parses the buffer.
*
* @api private
*/
Parser.prototype.parse = function () {
for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
chr = this.buffer[i];
if (this.buffer.length == 2 && this.buffer[1] == '\u0000') {
this.emit('close');
this.buffer = '';
this.i = 0;
return;
}
if (i === 0){
if (chr != '\u0000')
this.error('Bad framing. Expected null byte as first frame');
else
continue;
}
if (chr == '\ufffd'){
this.emit('data', this.buffer.substr(1, i - 1));
this.buffer = this.buffer.substr(i + 1);
this.i = 0;
return this.parse();
}
}
};
/**
* Handles an error
*
* @api private
*/
Parser.prototype.error = function (reason) {
this.buffer = '';
this.i = 0;
this.emit('error', reason);
return this;
return new protocolVersions['default'](mng, data, req);
};

View File

@@ -0,0 +1,350 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var Transport = require('../../transport')
, EventEmitter = process.EventEmitter
, crypto = require('crypto')
, parser = require('../../parser');
/**
* Export the constructor.
*/
exports = module.exports = WebSocket;
/**
* HTTP interface constructor. Interface compatible with all transports that
* depend on request-response cycles.
*
* @api public
*/
function WebSocket (mng, data, req) {
// parser
var self = this;
this.parser = new Parser();
this.parser.on('data', function (packet) {
self.log.debug(self.name + ' received data packet', packet);
self.onMessage(parser.decodePacket(packet));
});
this.parser.on('close', function () {
self.end();
});
this.parser.on('error', function () {
self.end();
});
Transport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
WebSocket.prototype.__proto__ = Transport.prototype;
/**
* Transport name
*
* @api public
*/
WebSocket.prototype.name = 'websocket';
/**
* Called when the socket connects.
*
* @api private
*/
WebSocket.prototype.onSocketConnect = function () {
var self = this;
this.socket.setNoDelay(true);
this.buffer = true;
this.buffered = [];
if (this.req.headers.upgrade !== 'WebSocket') {
this.log.warn(this.name + ' connection invalid');
this.end();
return;
}
var origin = this.req.headers.origin
, location = (this.socket.encrypted ? 'wss' : 'ws')
+ '://' + this.req.headers.host + this.req.url
, waitingForNonce = false;
if (this.req.headers['sec-websocket-key1']) {
// If we don't have the nonce yet, wait for it (HAProxy compatibility).
if (! (this.req.head && this.req.head.length >= 8)) {
waitingForNonce = true;
}
var headers = [
'HTTP/1.1 101 WebSocket Protocol Handshake'
, 'Upgrade: WebSocket'
, 'Connection: Upgrade'
, 'Sec-WebSocket-Origin: ' + origin
, 'Sec-WebSocket-Location: ' + location
];
if (this.req.headers['sec-websocket-protocol']){
headers.push('Sec-WebSocket-Protocol: '
+ this.req.headers['sec-websocket-protocol']);
}
} else {
var headers = [
'HTTP/1.1 101 Web Socket Protocol Handshake'
, 'Upgrade: WebSocket'
, 'Connection: Upgrade'
, 'WebSocket-Origin: ' + origin
, 'WebSocket-Location: ' + location
];
}
try {
this.socket.write(headers.concat('', '').join('\r\n'));
this.socket.setTimeout(0);
this.socket.setNoDelay(true);
this.socket.setEncoding('utf8');
} catch (e) {
this.end();
return;
}
if (waitingForNonce) {
this.socket.setEncoding('binary');
} else if (this.proveReception(headers)) {
self.flush();
}
var headBuffer = '';
this.socket.on('data', function (data) {
if (waitingForNonce) {
headBuffer += data;
if (headBuffer.length < 8) {
return;
}
// Restore the connection to utf8 encoding after receiving the nonce
self.socket.setEncoding('utf8');
waitingForNonce = false;
// Stuff the nonce into the location where it's expected to be
self.req.head = headBuffer.substr(0, 8);
headBuffer = '';
if (self.proveReception(headers)) {
self.flush();
}
return;
}
self.parser.add(data);
});
};
/**
* Writes to the socket.
*
* @api private
*/
WebSocket.prototype.write = function (data) {
if (this.open) {
this.drained = false;
if (this.buffer) {
this.buffered.push(data);
return this;
}
var length = Buffer.byteLength(data)
, buffer = new Buffer(2 + length);
buffer.write('\x00', 'binary');
buffer.write(data, 1, 'utf8');
buffer.write('\xff', 1 + length, 'binary');
try {
if (this.socket.write(buffer)) {
this.drained = true;
}
} catch (e) {
this.end();
}
this.log.debug(this.name + ' writing', data);
}
};
/**
* Flushes the internal buffer
*
* @api private
*/
WebSocket.prototype.flush = function () {
this.buffer = false;
for (var i = 0, l = this.buffered.length; i < l; i++) {
this.write(this.buffered.splice(0, 1)[0]);
}
};
/**
* Finishes the handshake.
*
* @api private
*/
WebSocket.prototype.proveReception = function (headers) {
var self = this
, k1 = this.req.headers['sec-websocket-key1']
, k2 = this.req.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){
self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
self.end();
return false;
}
n /= spaces;
md5.update(String.fromCharCode(
n >> 24 & 0xFF,
n >> 16 & 0xFF,
n >> 8 & 0xFF,
n & 0xFF));
});
md5.update(this.req.head.toString('binary'));
try {
this.socket.write(md5.digest('binary'), 'binary');
} catch (e) {
this.end();
}
}
return true;
};
/**
* Writes a payload.
*
* @api private
*/
WebSocket.prototype.payload = function (msgs) {
for (var i = 0, l = msgs.length; i < l; i++) {
this.write(msgs[i]);
}
return this;
};
/**
* Closes the connection.
*
* @api private
*/
WebSocket.prototype.doClose = function () {
this.socket.end();
};
/**
* WebSocket parser
*
* @api public
*/
function Parser () {
this.buffer = '';
this.i = 0;
};
/**
* Inherits from EventEmitter.
*/
Parser.prototype.__proto__ = EventEmitter.prototype;
/**
* Adds data to the buffer.
*
* @api public
*/
Parser.prototype.add = function (data) {
this.buffer += data;
this.parse();
};
/**
* Parses the buffer.
*
* @api private
*/
Parser.prototype.parse = function () {
for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
chr = this.buffer[i];
if (this.buffer.length == 2 && this.buffer[1] == '\u0000') {
this.emit('close');
this.buffer = '';
this.i = 0;
return;
}
if (i === 0){
if (chr != '\u0000')
this.error('Bad framing. Expected null byte as first frame');
else
continue;
}
if (chr == '\ufffd'){
this.emit('data', this.buffer.substr(1, i - 1));
this.buffer = this.buffer.substr(i + 1);
this.i = 0;
return this.parse();
}
}
};
/**
* Handles an error
*
* @api private
*/
Parser.prototype.error = function (reason) {
this.buffer = '';
this.i = 0;
this.emit('error', reason);
return this;
};

View File

@@ -0,0 +1,465 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var Transport = require('../../transport')
, EventEmitter = process.EventEmitter
, crypto = require('crypto')
, parser = require('../../parser')
, util = require('../../util');
/**
* Export the constructor.
*/
exports = module.exports = WebSocket;
exports.Parser = Parser;
/**
* HTTP interface constructor. Interface compatible with all transports that
* depend on request-response cycles.
*
* @api public
*/
function WebSocket (mng, data, req) {
// parser
var self = this;
this.parser = new Parser();
this.parser.on('data', function (packet) {
self.onMessage(parser.decodePacket(packet));
});
this.parser.on('ping', function () {
// version 8 ping => pong
self.socket.write('\u008a\u0000');
});
this.parser.on('close', function () {
self.end();
});
this.parser.on('error', function (reason) {
self.log.warn(self.name + ' parser error: ' + reason);
self.end();
});
Transport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
WebSocket.prototype.__proto__ = Transport.prototype;
/**
* Transport name
*
* @api public
*/
WebSocket.prototype.name = 'websocket';
/**
* Called when the socket connects.
*
* @api private
*/
WebSocket.prototype.onSocketConnect = function () {
var self = this;
if (this.req.headers.upgrade !== 'websocket') {
this.log.warn(this.name + ' connection invalid');
this.end();
return;
}
var origin = this.req.headers.origin
, location = (this.socket.encrypted ? 'wss' : 'ws')
+ '://' + this.req.headers.host + this.req.url;
if (!this.req.headers['sec-websocket-key']) {
this.log.warn(this.name + ' connection invalid: received no key');
this.end();
return;
}
// calc key
var key = this.req.headers['sec-websocket-key'];
var shasum = crypto.createHash('sha1');
shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
key = shasum.digest('base64');
var headers = [
'HTTP/1.1 101 Switching Protocols'
, 'Upgrade: websocket'
, 'Connection: Upgrade'
, 'Sec-WebSocket-Accept: ' + key
];
try {
this.socket.write(headers.concat('', '').join('\r\n'));
this.socket.setTimeout(0);
this.socket.setNoDelay(true);
} catch (e) {
this.end();
return;
}
this.socket.on('data', function (data) {
self.parser.add(data);
});
};
/**
* Writes to the socket.
*
* @api private
*/
WebSocket.prototype.write = function (data) {
if (this.open) {
var buf = this.frame(0x81, data);
this.socket.write(buf, 'binary');
this.log.debug(this.name + ' writing', data);
}
};
/**
* Frame server-to-client output as a text packet.
*
* @api private
*/
WebSocket.prototype.frame = function (opcode, str) {
var dataBuffer = new Buffer(str)
, dataLength = dataBuffer.length
, startOffset = 2
, secondByte = dataLength;
if (dataLength > 65536) {
startOffset = 10;
secondByte = 127;
}
else if (dataLength > 125) {
startOffset = 4;
secondByte = 126;
}
var outputBuffer = new Buffer(dataLength + startOffset);
outputBuffer[0] = opcode;
outputBuffer[1] = secondByte;
dataBuffer.copy(outputBuffer, startOffset);
switch (secondByte) {
case 126:
outputBuffer[2] = dataLength >>> 8;
outputBuffer[3] = dataLength % 256;
break;
case 127:
var l = dataLength;
for (var i = 1; i <= 8; ++i) {
outputBuffer[startOffset - i] = l & 0xff;
l >>>= 8;
}
}
return outputBuffer;
};
/**
* Closes the connection.
*
* @api private
*/
WebSocket.prototype.doClose = function () {
this.socket.end();
};
/**
* WebSocket parser
*
* @api public
*/
function Parser () {
this.state = {
activeFragmentedOperation: null,
lastFragment: false,
masked: false,
opcode: 0
};
this.overflow = null;
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
this.currentMessage = '';
var self = this;
this.opcodeHandlers = {
// text
'1': function(data) {
var finish = function(mask, data) {
self.currentMessage += self.unmask(mask, data);
if (self.state.lastFragment) {
self.emit('data', self.currentMessage);
self.currentMessage = '';
}
self.endPacket();
}
var expectData = function(length) {
if (self.state.masked) {
self.expect('Mask', 4, function(data) {
var mask = data;
self.expect('Data', length, function(data) {
finish(mask, data);
});
});
}
else {
self.expect('Data', length, function(data) {
finish(null, data);
});
}
}
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
}
else if (firstLength == 127) {
self.expect('Length', 8, function(data) {
if (util.unpack(data.slice(0, 4)) != 0) {
self.error('packets with length spanning more than 32 bit is currently not supported');
return;
}
var lengthBytes = data.slice(4); // note: cap to 32 bit length
expectData(util.unpack(data));
});
}
},
// close
'8': function(data) {
self.emit('close');
self.reset();
},
// ping
'9': function(data) {
if (self.state.lastFragment == false) {
self.error('fragmented ping is not supported');
return;
}
var finish = function(mask, data) {
self.emit('ping', self.unmask(mask, data));
self.endPacket();
}
var expectData = function(length) {
if (self.state.masked) {
self.expect('Mask', 4, function(data) {
var mask = data;
self.expect('Data', length, function(data) {
finish(mask, data);
});
});
}
else {
self.expect('Data', length, function(data) {
finish(null, data);
});
}
}
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength == 0) {
finish(null, null);
}
else if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
}
else if (firstLength == 127) {
self.expect('Length', 8, function(data) {
expectData(util.unpack(data));
});
}
}
}
this.expect('Opcode', 2, this.processPacket);
};
/**
* Inherits from EventEmitter.
*/
Parser.prototype.__proto__ = EventEmitter.prototype;
/**
* Add new data to the parser.
*
* @api public
*/
Parser.prototype.add = function(data) {
if (this.expectBuffer == null) {
this.addToOverflow(data);
return;
}
var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset);
data.copy(this.expectBuffer, this.expectOffset, 0, toRead);
this.expectOffset += toRead;
if (toRead < data.length) {
// at this point the overflow buffer shouldn't at all exist
this.overflow = new Buffer(data.length - toRead);
data.copy(this.overflow, 0, toRead, toRead + this.overflow.length);
}
if (this.expectOffset == this.expectBuffer.length) {
var bufferForHandler = this.expectBuffer;
this.expectBuffer = null;
this.expectOffset = 0;
this.expectHandler.call(this, bufferForHandler);
}
}
/**
* Adds a piece of data to the overflow.
*
* @api private
*/
Parser.prototype.addToOverflow = function(data) {
if (this.overflow == null) this.overflow = data;
else {
var prevOverflow = this.overflow;
this.overflow = new Buffer(this.overflow.length + data.length);
prevOverflow.copy(this.overflow, 0);
data.copy(this.overflow, prevOverflow.length);
}
}
/**
* Waits for a certain amount of bytes to be available, then fires a callback.
*
* @api private
*/
Parser.prototype.expect = function(what, length, handler) {
this.expectBuffer = new Buffer(length);
this.expectOffset = 0;
this.expectHandler = handler;
if (this.overflow != null) {
var toOverflow = this.overflow;
this.overflow = null;
this.add(toOverflow);
}
}
/**
* Start processing a new packet.
*
* @api private
*/
Parser.prototype.processPacket = function (data) {
if ((data[0] & 0x70) != 0) this.error('reserved fields not empty');
this.state.lastFragment = (data[0] & 0x80) == 0x80;
this.state.masked = (data[1] & 0x80) == 0x80;
var opcode = data[0] & 0xf;
if (opcode == 0) {
// continuation frame
if (this.state.opcode != 1 || this.state.opcode != 2) {
this.error('continuation frame cannot follow current opcode')
return;
}
}
else this.state.opcode = opcode;
this.state.opcode = data[0] & 0xf;
var handler = this.opcodeHandlers[this.state.opcode];
if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode);
else handler(data);
}
/**
* Endprocessing a packet.
*
* @api private
*/
Parser.prototype.endPacket = function() {
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) {
// end current fragmented operation
this.state.activeFragmentedOperation = null;
}
this.state.lastFragment = false;
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
this.state.masked = false;
this.expect('Opcode', 2, this.processPacket);
}
/**
* Reset the parser state.
*
* @api private
*/
Parser.prototype.reset = function() {
this.state = {
activeFragmentedOperation: null,
lastFragment: false,
masked: false,
opcode: 0
};
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
this.overflow = null;
this.currentMessage = '';
}
/**
* Unmask received data.
*
* @api private
*/
Parser.prototype.unmask = function (mask, buf) {
if (mask != null) {
for (var i = 0, ll = buf.length; i < ll; i++) {
buf[i] ^= mask[i % 4];
}
}
return buf != null ? buf.toString('utf8') : '';
}
/**
* Handles an error
*
* @api private
*/
Parser.prototype.error = function (reason) {
this.reset();
this.emit('error', reason);
return this;
};

View File

@@ -0,0 +1,10 @@
/**
* Export websocket versions.
*/
module.exports = {
7: require('./hybi-07-12'),
8: require('./hybi-07-12'),
default: require('./default')
};

View File

@@ -23,3 +23,28 @@ exports.toArray = function (enu) {
return arr;
};
/**
* Unpacks a buffer to a number.
*
* @api public
*/
exports.unpack = function (buffer) {
var n = 0;
for (var i = 0; i < buffer.length; ++i) {
n = (i == 0) ? buffer[i] : (n * 256) + buffer[i];
}
return n;
}
/**
* Left pads a string.
*
* @api public
*/
exports.padl = function (s,n,c) {
return new Array(1 + n - s.length).join(c) + s;
}

View File

@@ -1,7 +1,7 @@
{
"name": "socket.io"
, "version": "0.7.6"
, "description": "Realtime apps made cross-browser & easy with a WebSocket-like API"
, "version": "0.8.3"
, "description": "Real-time apps made cross-browser & easy with a WebSocket-like API"
, "homepage": "http://socket.io"
, "keywords": ["websocket", "socket", "realtime", "socket.io", "comet", "ajax"]
, "author": "Guillermo Rauch <guillermo@learnboost.com>"
@@ -9,19 +9,21 @@
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
, { "name": "Arnout Kazemier", "email": "info@3rd-eden.com" }
, { "name": "Vladimir Dronnikov", "email": "dronnikov@gmail.com" }
, { "name": "Einar Otto Stangvik", "email": "einaros@gmail.com" }
]
, "repository":{
"type": "git"
, "url": "https://github.com/LearnBoost/Socket.IO-node.git"
, "url": "https://github.com/LearnBoost/socket.io.git"
}
, "dependencies": {
"socket.io-client": "0.7.3"
, "policyfile": "0.0.3"
, "redis": "0.6.0"
"socket.io-client": "0.8.3"
, "policyfile": "0.0.4"
, "redis": "0.6.6"
}
, "devDependencies": {
"expresso": "0.7.7"
, "should": "0.0.4"
, "assertvanish": "0.0.3-1"
}
, "main": "index"
, "engines": { "node": ">= 0.4.0" }

View File

@@ -180,7 +180,9 @@ client = function (port) {
create = function (cl) {
console.log('');
return io.listen(cl.port);
var manager = io.listen(cl.port);
manager.set('client store expiration', 0);
return manager;
};
/**

View File

@@ -0,0 +1,54 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
require.paths.unshift(__dirname + '/../../lib');
var assertvanish = require('assertvanish')
, common = require('../common')
, ports = 15800;
function resultCallback (leaks, leakedSocket) {
if (leaks) {
console.error('Leak detected');
process.exit(1);
} else {
console.error('No leaks');
process.exit(0);
}
};
/**
* Test.
*/
var cl = client(++ports);
var io = create(cl);
io.sockets.on('connection', function (socket) {
console.log('connected');
socket.on('disconnect', function() {
console.log("client gone");
setTimeout(gc, 1000);
assertvanish(socket, 2000, {silent: true, callback: resultCallback});
});
});
setTimeout(function() {
cl.handshake(function (sid) {
var ws = websocket(cl, sid);
ws.on('open', function () {
console.log('open!');
setTimeout(function() {
ws.close();
}, 500);
});
});
}, 100);

View File

@@ -1,4 +1,3 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
@@ -21,6 +20,22 @@ var sio = require('socket.io')
module.exports = {
'test setting and getting a configuration flag': function (done) {
var port = ++ports
, io = sio.listen(http.createServer());
io.set('a', 'b');
io.get('a').should.eql('b');
var port = ++ports
, io = sio.listen(http.createServer());
io.configure(function () {
io.set('a', 'b');
io.enable('tobi');
});
io.get('a').should.eql('b');
done();
},
@@ -37,11 +52,9 @@ module.exports = {
, io = sio.listen(http.createServer());
io.configure(function () {
io.set('a', 'b');
io.enable('tobi');
});
io.get('a').should.eql('b');
io.enabled('tobi').should.be.true;
done();
@@ -459,6 +472,91 @@ module.exports = {
});
},
'test that a referer is accepted for *:* origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', '*:*');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com:82/something' } }, function (res, data) {
res.statusCode.should.eql(200);
cl.end();
io.server.close();
done();
});
},
'test that valid referer is accepted for addr:* origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:*');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
res.statusCode.should.eql(200);
cl.end();
io.server.close();
done();
});
},
'test that erroneous referer is denied for addr:* origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:*');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://baz.bar.com/something' } }, function (res, data) {
res.statusCode.should.eql(403);
cl.end();
io.server.close();
done();
});
},
'test that valid referer port is accepted for addr:port origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:81');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com:81/something' } }, function (res, data) {
res.statusCode.should.eql(200);
cl.end();
io.server.close();
done();
});
},
'test that erroneous referer port is denied for addr:port origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:81');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
res.statusCode.should.eql(403);
cl.end();
io.server.close();
done();
});
},
'test limiting the supported transports for a manager': function (done) {
var port = ++ports
, io = sio.listen(port)
@@ -534,6 +632,100 @@ module.exports = {
io.server.close();
done();
});
}
},
'test disabling heartbeats': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port)
, messages = 0
, beat = false
, ws;
io.configure(function () {
io.disable('heartbeats');
io.set('heartbeat interval', .05);
io.set('heartbeat timeout', .05);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
setTimeout(function () {
socket.disconnect();
}, io.get('heartbeat timeout') * 1000 + 100);
socket.on('disconnect', function (reason) {
beat.should.be.false;
cl.end();
ws.finishClose();
io.server.close();
done();
});
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(200);
data.should.match(/([^:]+)::[\.0-9]+:(.*)/);
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('message', function (packet) {
if (++messages == 1) {
packet.type.should.eql('connect');
} else if (packet.type == 'heartbeat'){
beat = true;
}
});
});
});
},
'no duplicate room members': function (done) {
var port = ++ports
, io = sio.listen(port);
Object.keys(io.rooms).length.should.equal(0);
io.onJoin(123, 'foo');
io.rooms.foo.length.should.equal(1);
io.onJoin(123, 'foo');
io.rooms.foo.length.should.equal(1);
io.onJoin(124, 'foo');
io.rooms.foo.length.should.equal(2);
io.onJoin(124, 'foo');
io.rooms.foo.length.should.equal(2);
io.onJoin(123, 'bar');
io.rooms.foo.length.should.equal(2);
io.rooms.bar.length.should.equal(1);
io.onJoin(123, 'bar');
io.rooms.foo.length.should.equal(2);
io.rooms.bar.length.should.equal(1);
io.onJoin(124, 'bar');
io.rooms.foo.length.should.equal(2);
io.rooms.bar.length.should.equal(2);
io.onJoin(124, 'bar');
io.rooms.foo.length.should.equal(2);
io.rooms.bar.length.should.equal(2);
io.server.close();
done();
},
'test passing options directly to the Manager through listen': function (done) {
var port = ++ports
, io = sio.listen(port, { resource: '/my resource', custom: 'opt' });
io.get('resource').should.equal('/my resource');
io.get('custom').should.equal('opt');
io.server.close();
done();
}
};

View File

@@ -80,8 +80,8 @@ module.exports = {
fn(null, true);
})
.on('connection', function (socket) {
socket.handshake.address.address.should.equal('127.0.0.1');
socket.handshake.address.port.should.equal(ports);
(!!socket.handshake.address.address).should.be.true;
(!!socket.handshake.address.port).should.be.true;
socket.handshake.headers.host.should.equal('localhost');
socket.handshake.headers.connection.should.equal('keep-alive');
socket.handshake.time.should.match(/GMT/);
@@ -139,5 +139,109 @@ module.exports = {
}
})
});
},
'broadcasting sends and emits on a namespace': function (done) {
var cl = client(++ports)
, io = create(cl)
, calls = 0
, connect = 0
, message = 0
, events = 0
, expected = 5
, ws1
, ws2;
io.of('a')
.on('connection', function (socket){
socket.broadcast.emit('b', 'test');
socket.broadcast.json.emit('json', {foo:'bar'});
socket.broadcast.send('foo');
});
function finish () {
connect.should.equal(2);
message.should.equal(1);
events.should.equal(2);
cl.end();
ws1.finishClose();
ws2.finishClose();
io.server.close();
done();
}
cl.handshake(function (sid) {
ws1 = websocket(cl, sid);
ws1.on('open', function() {
ws1.packet({
type: 'connect'
, endpoint: 'a'
});
});
ws1.on('message', function (data) {
if (data.type === 'connect') {
++connect;
if (++calls === expected) finish();
}
if (data.type === 'message') {
++message;
if (++calls === expected) finish();
}
if (data.type === 'event') {
if (data.name === 'b' || data.name === 'json') ++events;
if (++calls === expected) finish();
}
});
cl.handshake(function (sid) {
ws2 = websocket(cl, sid);
ws2.on('open', function () {
ws2.packet({
type: 'connect'
, endpoint: 'a'
});
});
})
})
},
'joining rooms inside a namespace': function (done) {
var cl = client(++ports)
, io = create(cl)
, calls = 0
, ws;
io.of('/foo').on('connection', function (socket) {
socket.join('foo.bar');
this.in('foo.bar').emit('baz', 'pewpew');
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function (){
ws.packet({
type: 'connect'
, endpoint: '/foo'
});
});
ws.on('message', function (data) {
if (data.type === 'event') {
data.name.should.equal('baz');
cl.end();
ws.finishClose();
io.server.close();
done();
}
});
})
}
};

View File

@@ -343,6 +343,14 @@ module.exports = {
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
]).should.eql('\ufffd5\ufffd3:::5\ufffd7\ufffd3:::53d')
},
'test decoding newline': function () {
parser.decodePacket('3:::\n').should.eql({
type: 'message'
, endpoint: ''
, data: '\n'
});
}
};

View File

@@ -233,7 +233,7 @@ module.exports = {
store.destroy();
done();
});
}, 1900);
}, 2000);
});
}

View File

@@ -77,7 +77,7 @@ module.exports = {
var io = sio.listen(http.createServer())
, port = ++ports;
io.get('flash policy port').should.eql(843);
io.get('flash policy port').should.eql(10843);
io.set('flash policy port', port);
io.get('flash policy port').should.eql(port);

View File

@@ -0,0 +1,271 @@
/**
* Test dependencies.
*/
var assert = require('assert');
var Parser = require('../lib/transports/websocket/hybi-07-12.js').Parser;
/**
* Returns a Buffer from a "ff 00 ff"-type hex string.
*/
function makeBufferFromHexString(byteStr) {
var bytes = byteStr.split(' ');
var buf = new Buffer(bytes.length);
for (var i = 0; i < bytes.length; ++i) {
buf[i] = parseInt(bytes[i], 16);
}
return buf;
}
/**
* Splits a buffer in two parts.
*/
function splitBuffer(buffer) {
var b1 = new Buffer(Math.ceil(buffer.length / 2));
buffer.copy(b1, 0, 0, b1.length);
var b2 = new Buffer(Math.floor(buffer.length / 2));
buffer.copy(b2, 0, b1.length, b1.length + b2.length);
return [b1, b2];
}
/**
* Performs hybi07+ type masking on a hex string.
*/
function mask(str, maskString) {
var buf = new Buffer(str);
var mask = makeBufferFromHexString(maskString || '34 83 a8 68');
for (var i = 0; i < buf.length; ++i) {
buf[i] ^= mask[i % 4];
}
return buf;
}
/**
* Unpacks a Buffer into a number.
*/
function unpack(buffer) {
var n = 0;
for (var i = 0; i < buffer.length; ++i) {
n = (i == 0) ? buffer[i] : (n * 256) + buffer[i];
}
return n;
}
/**
* Returns a hex string, representing a specific byte count 'length', from a number.
*/
function pack(length, number) {
return padl(number.toString(16), length, '0').replace(/(\d\d)/g, '$1 ').trim();
}
/**
* Left pads the string 's' to a total length of 'n' with char 'c'.
*/
function padl(s, n, c) {
return new Array(1 + n - s.length).join(c) + s;
}
/**
* Returns a hex string from a Buffer.
*/
function dump(data) {
var s = '';
for (var i = 0; i < data.length; ++i) {
s += padl(data[i].toString(16), 2, '0') + ' ';
}
return s.trim();
}
/**
* Tests.
*/
module.exports = {
'can parse unmasked text message': function() {
var p = new Parser();
var packet = '81 05 48 65 6c 6c 6f';
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal('Hello', data);
});
p.add(makeBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse close message': function() {
var p = new Parser();
var packet = '88 00';
var gotClose = false;
p.on('close', function(data) {
gotClose = true;
});
p.add(makeBufferFromHexString(packet));
assert.ok(gotClose);
},
'can parse masked text message': function() {
var p = new Parser();
var packet = '81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5';
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal('5:::{"name":"echo"}', data);
});
p.add(makeBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a masked text message longer than 125 bytes': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var packet = '81 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(makeBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a really long masked text message': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 64*1024; ++i) message += (i % 5).toString();
var packet = '81 FF ' + pack(16, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(makeBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a fragmented masked text message of 300 bytes': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var msgpiece2 = message.substr(150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece1, '34 83 a8 68'));
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(makeBufferFromHexString(packet1));
p.add(makeBufferFromHexString(packet2));
assert.ok(gotData);
},
'can parse a ping message': function() {
var p = new Parser();
var message = 'Hello';
var packet = '89 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(message, data);
});
p.add(makeBufferFromHexString(packet));
assert.ok(gotPing);
},
'can parse a ping with no data': function() {
var p = new Parser();
var packet = '89 00';
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
});
p.add(makeBufferFromHexString(packet));
assert.ok(gotPing);
},
'can parse a fragmented masked text message of 300 bytes with a ping in the middle': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + dump(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(pingMessage, data);
});
p.add(makeBufferFromHexString(packet1));
p.add(makeBufferFromHexString(pingPacket));
p.add(makeBufferFromHexString(packet2));
assert.ok(gotData);
assert.ok(gotPing);
},
'can parse a fragmented masked text message of 300 bytes with a ping in the middle, which is delievered over sevaral tcp packets': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + dump(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(pingMessage, data);
});
var buffers = [];
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(packet1)));
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(pingPacket)));
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(packet2)));
for (var i = 0; i < buffers.length; ++i) {
p.add(buffers[i]);
}
assert.ok(gotData);
assert.ok(gotPing);
},
};

View File

@@ -12,7 +12,7 @@
var sio = require('socket.io')
, should = require('./common')
, parser = sio.parser
, ports = 15400;
, ports = 15800;
/**
* Tests.
@@ -899,6 +899,59 @@ module.exports = {
});
},
'test leaving a room': function (done) {
var port = ++ports
, cl1 = client(port)
, cl2 = client(port)
, io = create(cl1)
, joins = 0
, disconnects = 0;
io.set('close timeout', 0);
io.sockets.on('connection', function (socket) {
socket.join('foo');
io.sockets.clients('foo').should.have.length(++joins);
socket.on('disconnect', function () {
socket.leave('foo');
socket.leave('foo');
socket.leave('foo');
io.sockets.clients('foo').should.have.length(--joins);
if (++disconnects == 2) {
io.server.close();
cl1.end();
cl2.end();
done();
}
})
});
cl1.handshake(function (sid) {
var ws1 = websocket(cl1, sid);
ws1.on('message', function (msg) {
if (!ws1.connected) {
msg.type.should.eql('connect');
ws1.connected = true;
ws1.finishClose();
}
});
});
cl2.handshake(function (sid) {
var ws2 = websocket(cl2, sid);
ws2.on('message', function (msg) {
if (!ws2.connected) {
msg.type.should.eql('connect');
ws2.connected = true;
ws2.finishClose();
}
});
});
},
'test message with broadcast flag': function (done) {
var port = ++ports
, cl1 = client(port)
@@ -1493,8 +1546,8 @@ module.exports = {
, ws;
io.sockets.on('connection', function (socket) {
socket.handshake.address.address.should.equal('127.0.0.1');
socket.handshake.address.port.should.equal(ports);
(!!socket.handshake.address.address).should.be.true;
(!!socket.handshake.address.port).should.be.true;
socket.handshake.headers.host.should.equal('localhost');
socket.handshake.headers.connection.should.equal('keep-alive');
socket.handshake.time.should.match(/GMT/);
@@ -1584,6 +1637,42 @@ module.exports = {
});
},
'test accessing handshake data from sockets on disconnect': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function () {
(!!socket.handshake.address.address).should.be.true;
(!!socket.handshake.address.port).should.be.true;
socket.handshake.headers.host.should.equal('localhost');
socket.handshake.headers.connection.should.equal('keep-alive');
socket.handshake.time.should.match(/GMT/);
setTimeout(function () {
ws.finishClose();
cl.end();
io.server.close();
done();
}, 10);
});
socket.disconnect();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('message', function (msg) {
if (!ws.connected) {
msg.type.should.eql('connect');
ws.connected = true;
}
});
});
},
'test for intentional and unintentional disconnects': function (done) {
var cl = client(++ports)
, io = create(cl)
@@ -1642,5 +1731,41 @@ module.exports = {
}
});
});
}
},
'test socket clean up': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.sockets.on('connection', function (socket) {
var self = this
, id = socket.id;
socket.on('disconnect', function () {
setTimeout(function () {
var available = !!self.sockets[id];
available.should.be.false;
ws.finishClose();
cl.end();
io.server.close();
done();
}, 10);
});
socket.disconnect();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('message', function (msg) {
if (!ws.connected) {
msg.type.should.eql('connect');
ws.connected = true;
}
});
});
},
};

View File

@@ -950,6 +950,44 @@ module.exports = {
});
},
'test that emitting an error event doesnt throw': function (done) {
var cl = client(++ports)
, io = create(cl)
io.configure(function () {
io.set('polling duration', .05);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function () {
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
, parser.encodePacket({
type: 'event'
, name: 'error'
})
, function (res, data) {
res.statusCode.should.eql(200);
data.should.equal('1');
}
);
});
});
},
'test emitting an event to the server with data': function (done) {
var cl = client(++ports)
, io = create(cl)
@@ -2635,4 +2673,82 @@ module.exports = {
});
},
'test emitting to closed clients': function (done) {
var cl = client(++ports)
, cl2 = client(ports)
, io = create(cl)
, connections = 0;
io.configure(function () {
io.set('close timeout', .1);
});
io.sockets.on('connection', function (socket) {
socket.send('a');
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, packs) {
res.statusCode.should.equal(200);
packs.should.have.length(1);
packs[0].should.eql({ type: 'message', endpoint: '', data: 'a' });
cl2.handshake(function (sid2) {
cl2.get(
'/socket.io/{protocol}/xhr-polling/' + sid2
, function (res, packs) {
res.statusCode.should.equal(200);
packs.should.have.length(1);
packs[0].should.eql({ type: 'message', endpoint: '', data: 'a' });
io.sockets.emit('woot', 'b');
var total = 2;
cl.get(
'/socket.io/{protocol}/xhr-polling/' + sid
, function (res, packs) {
res.statusCode.should.equal(200);
packs.should.have.length(1);
packs[0].should.eql({
type: 'event'
, endpoint: ''
, name: 'woot'
, args: ['b']
});
--total || finish();
}
);
cl2.get(
'/socket.io/{protocol}/xhr-polling/' + sid2
, function (res, packs) {
res.statusCode.should.equal(200);
packs.should.have.length(1);
packs[0].should.eql({
type: 'event'
, endpoint: ''
, name: 'woot'
, args: ['b']
});
--total || finish();
}
);
function finish () {
cl.end();
cl2.end();
io.server.close();
done();
};
}
);
});
});
});
}
};