Compare commits

...

614 Commits

Author SHA1 Message Date
Damien Arrachequesne
41b9a7e45d Release 0.9.19 2017-05-16 07:14:45 +02:00
Damien Arrachequesne
e4d61b1be6 Properly require EventEmitter
One must use `require('events').EventEmitter` in node.js 0.10.x
2017-05-16 07:09:38 +02:00
Damien Arrachequesne
ed74dee3b0 Release 0.9.18 2017-05-07 07:54:20 +02:00
John Chadwick
9ad1fd2771 Remove process.EventEmitter usage for Node 7.x
process.EventEmitter has been deprecated and removed from Node.
2017-05-07 07:50:00 +02:00
Guillermo Rauch
d25c5484c3 Release 0.9.17 2014-05-22 11:03:55 -07:00
Guillermo Rauch
fea676b90e Merge pull request #1371 from surespot/channelfix
Use static channels for node syncing.
2014-05-22 10:49:53 -07:00
Adam Patacchiola
386d2a9c0c don't call non existant transport onDisconnect 2013-12-13 10:02:20 -07:00
Adam Patacchiola
8b47789414 use static channels for remote syncing instead of subscribing/unsubscribing 5 channels for every connection 2013-12-13 07:22:43 -07:00
Guillermo Rauch
47b06c0fcf Merge pull request #1333 from yujiosaka/0.9
http-polling : adding 'X-XSS-Protection : 0;' to headers necessary not o...
2013-12-09 08:17:42 -08:00
Guillermo Rauch
9823325a1f Merge pull request #1346 from DeadAlready/0.9-security-fix
Use destroy buffer size on websocket transport method as well
2013-11-15 04:01:16 -08:00
Karl Düüna
a47d76b990 Use destroy buffer size on websocket transport method as well 2013-11-15 10:31:00 +02:00
yujiosaka
db3ac4b415 http-polling : adding 'X-XSS-Protection : 0;' to headers necessary not only to jsonp-polling but http-polling 2013-10-24 14:53:19 +09:00
Guillermo Rauch
b9a2804b1a Merge pull request #1323 from tico8/0.9-bug
Memory leak : If a lot of connections continue being disconnected, a memory will leak.
2013-10-09 08:32:44 -07:00
tico8
b4182a5d42 Memory leak : If a lot of connections continue being disconnected, a memory will leak. 2013-10-09 01:06:02 +09:00
Guillermo Rauch
5120a706f2 Release 0.9.16 2013-06-06 08:39:48 -07:00
Guillermo Rauch
ee078cb124 transports: added tests for htmlfile escaping/unescaping 2013-06-06 08:38:57 -07:00
Guillermo Rauch
64d8f572aa Release 0.9.15 2013-06-06 08:22:29 -07:00
Guillermo Rauch
4e1ba9f872 transports: escaping (fixes #1251) 2013-06-06 08:17:40 -07:00
Guillermo Rauch
5d93af994a Release 0.9.14 2013-03-29 14:15:52 -07:00
Guillermo Rauch
6e25c802cc manager: fix memory leak with SSL 2013-03-29 14:14:42 -07:00
Guillermo Rauch
37690f78d7 Release 0.9.13 2012-12-13 15:15:38 -03:00
Guillermo Rauch
3cbd00ca70 package: fixed base64id requirement 2012-12-13 15:15:15 -03:00
Guillermo Rauch
b2a8ed1421 Release 0.9.12 2012-12-13 08:19:16 -03:00
Guillermo Rauch
0d3313f536 manager: fix for latest node which is returning a clone with listeners [viirya] 2012-12-13 08:18:42 -03:00
Guillermo Rauch
3b7224c7e0 Release 0.9.11 2012-11-02 08:03:15 -07:00
Guillermo Rauch
2030cf1432 package: move redis to optionalDependenices [3rd-Eden] 2012-11-02 08:01:24 -07:00
Guillermo Rauch
de9e8dffe1 Release 0.9.10 2012-08-10 13:34:50 -07:00
Guillermo Rauch
3a3044ebba Merge pull request #972 from Coreh/express-3.x-readme
Add express 3.0 instructions on Readme.md
2012-08-10 10:17:55 -07:00
Guillermo Rauch
d10b4dd1bd Merge pull request #985 from GICodeWarrior/log-case-fix
Don't lowercase log messages
2012-08-08 18:03:57 -07:00
Rusty Burchfield
12beee2d63 Don't lowercase log messages
Lowercasing log messages is unnecessary.  It makes some messages difficult to
read, and others difficult to search for.
2012-08-08 11:32:57 -07:00
Guillermo Rauch
875f14d16b Revert "Fix infinite recursion in Websocket parsers."
This reverts commit c218468f67.
2012-08-07 13:18:41 -07:00
Guillermo Rauch
8ca8990a0c Merge pull request #983 from GICodeWarrior/parser-recursion
Fix infinite recursion in Websocket parsers.
2012-08-06 13:16:37 -07:00
Rusty Burchfield
c218468f67 Fix infinite recursion in Websocket parsers.
If a client is feeding messages faster than server can handle them, infinite
recursion occurs.  Basically, the "overflow" data gets added to the parser and
it immediately parses a new message.

The fix pushes the processing of the next message (in this edge case) onto the
event queue.  This prevents the stack from recursing indefinitely.  This also
prevents a fast client from starving other clients.
2012-08-06 13:03:51 -07:00
Guillermo Rauch
4164e3bd7e Merge pull request #981 from doozr/honour-flash-settings
Honour flash settings
2012-08-06 08:59:17 -07:00
Guillermo Rauch
46227e7ac9 Merge pull request #980 from doozr/jsonp-error-crash
Always set the HTTP response in case an error should be returned to the client
2012-08-06 08:58:57 -07:00
Craig Andrews
d723d363b2 Always set the HTTP response in case an error should be returned to the client 2012-08-06 14:16:06 +01:00
Craig Andrews
fa1c1b2ada Create or destroy the flash policy server on configuration change 2012-08-06 14:14:15 +01:00
Craig Andrews
d32a848c3f Honour configuration to disable flash policy server 2012-08-06 14:14:14 +01:00
Guillermo Rauch
48ad0d3d1d Release 0.9.9 2012-08-01 15:14:02 -07:00
Guillermo Rauch
281a467960 manager: added response to sync disconnect xhrs 2012-08-01 15:08:06 -07:00
Guillermo Rauch
1fa74a46a3 Revert "Fix disconnectSync getting ignored"
This reverts commit f48b40e134.
2012-08-01 11:58:28 -07:00
Guillermo Rauch
ca4e3f32a3 Merge pull request #975 from huancz/master
fix issue #961 - restore compatibility with earlier node releases (up to 0.4.x)
2012-07-31 08:34:22 -07:00
Guillermo Rauch
9dd8134e6a Merge pull request #970 from renier/master
Codebase needs a license file (#965)
2012-07-31 08:32:07 -07:00
Renier Morales
180f1c91b9 Put license text in its own file (#965) 2012-07-31 11:07:21 -04:00
Marco Aurélio
bddf652c25 Add express 3.0 instructions on Readme.md 2012-07-30 15:53:47 -03:00
Guillermo Rauch
c795e4cf1a Merge pull request #971 from Coreh/express-3.x-warn
Add warning to .listen() to ease the migration to Express 3.x
2012-07-30 11:49:53 -07:00
Marco Aurélio
6afbb34581 Add warning to .listen() to ease the migration to Express 3.x 2012-07-30 15:43:00 -03:00
Guillermo Rauch
ac39dbc721 Merge pull request #964 from crickeys/xhr_polling_disconnectSync
Fix disconnectSync getting ignored
2012-07-27 10:06:03 -07:00
Petr Běhan
a5c5c20438 restore compatibility with node 0.4.x 2012-07-27 15:38:57 +02:00
Brian Gruber
f48b40e134 Fix disconnectSync getting ignored
If using xhr-polling and a browser closes a tab or window, the
disconnectSync in the socket.io-client method is called which sends an
XHR request to the server indicating a disconnect. This line would cause
that to be ignored and so the server would have to wait for a timeout to
mark them as disconnect. This was possibly because it was sent from a
different tcp socket than the current connection.
2012-07-26 21:19:00 -05:00
Guillermo Rauch
1679fd564c Release 0.9.8 2012-07-24 17:36:30 -07:00
Guillermo Rauch
bb900d445a Release 0.9.7 2012-07-24 11:16:20 -07:00
Guillermo Rauch
c6fed55f53 tests: fixed tests for 0.8 2012-07-24 10:39:23 -07:00
Guillermo Rauch
6adebc85fc Merge pull request #958 from xaroth8088/master
Prevent crash when socket leaves a room twice.
2012-07-22 11:26:21 -07:00
xaroth8088
7a087bcc94 Prevent crash when socket leaves a room twice. 2012-07-22 11:09:06 -07:00
Guillermo Rauch
18422183c8 Merge pull request #957 from xaroth8088/master
Corrects unsafe usage of for..in, permitting socket.io to be used in environments that extend Object, etc.
2012-07-21 11:44:28 -07:00
xaroth8088
aeb904f58b Corrects unsafe usage of for..in, permitting socket.io to be used in environments where Object, Function, etc. have been extended.
http://yuiblog.com/blog/2006/09/26/for-in-intrigue/
2012-07-21 11:30:15 -07:00
xaroth8088
9c0b9de7f0 Revert "Corrects unsafe usage of for..in, permitting socket.io to be used in environments that extend Object, etc."
This reverts commit 81552c11ca.
2012-07-21 11:21:11 -07:00
xaroth8088
81552c11ca Corrects unsafe usage of for..in, permitting socket.io to be used in environments that extend Object, etc.
http://yuiblog.com/blog/2006/09/26/for-in-intrigue/
2012-07-21 10:29:20 -07:00
Guillermo Rauch
e1fe76aebe Fix for node 0.8 with gzip compression. Thanks @vadimi 2012-07-09 16:58:02 -07:00
Guillermo Rauch
8197a0c854 Merge pull request #929 from sjonnet19/patch-1
Update redis to support Node 0.8.x
2012-06-26 06:29:08 -07:00
Shawn Jonnet
2b91f1407f Update redis to support Node 0.8.x 2012-06-25 23:30:24 -03:00
Guillermo Rauch
3b9715e8e7 Merge pull request #869 from MrSwitch/master
Small change to demo copy
2012-05-03 06:24:15 -07:00
Andrew Dodson
4e13cfb03e Update copy 2012-05-03 12:16:01 +10:00
Guillermo Rauch
39671e81a5 Merge pull request #868 from bodash/patch-1
I continued to have websocket connection issues in Safari when using SSL...
2012-05-02 12:21:24 -07:00
bodash
ffa8994a23 I continued to have websocket connection issues in Safari when using SSL that terminated at a load balancer. The shorthand logic that was here was nice and compact but didn't seem to work. Took the "intent" of the short hand and made it a bit more verbose and now it works. 2012-05-02 13:18:46 -06:00
Guillermo Rauch
de1afe1317 Merge pull request #857 from martinthomson/bug/856
Fix for ID generation vulnerability #856
2012-04-26 15:49:24 -07:00
Martin Thomson
aaad106b90 Adding node 0.4 backward compat for id gen 2012-04-26 15:08:19 -07:00
Martin Thomson
f850ddccd0 Removing more fixes for other bug 2012-04-26 14:35:17 -07:00
Martin Thomson
8d269aae4c Removing fixes for other bug 2012-04-26 14:33:37 -07:00
Martin Thomson
67b4eb9abd Making ID generation securely random 2012-04-26 14:28:00 -07:00
Guillermo Rauch
fe6dd87443 Merge pull request #848 from mbrevoort/redisStoreRaceCondition
Fix Redis Store race condition in manager onOpen unsubscribe callback
2012-04-23 15:30:30 -07:00
Mike Brevoort
d9aeaa494f Fix Redis Store race condition in manager onOpen unsubscribe callback 2012-04-23 16:06:31 -06:00
Guillermo Rauch
2024d45383 Merge pull request #841 from TooTallNate/master
fix for EventEmitters always reusing the same Array instance for listeners
2012-04-19 13:30:13 -07:00
Nathan Rajlich
e1884859bc fix for EventEmitters always reusing the same Array instance for listeners
This fixes node v0.7.x.

The node commits that broke this old behavior is here:
  78dc13fbf9%5E...928ea564d16da47e615ddac627e0b4d4a40d8196
2012-04-19 13:18:22 -07:00
Guillermo Rauch
0242a2ddf3 Merge branch 'master' of github.com:LearnBoost/socket.io 2012-04-17 19:51:49 -03:00
Guillermo Rauch
dbe6d5f740 Release 0.9.6 2012-04-17 19:51:37 -03:00
Guillermo Rauch
e98fc7bc86 Fixed XSS in jsonp-polling. 2012-04-17 19:48:32 -03:00
Guillermo Rauch
9bbf17f31e Merge pull request #827 from crickeys/patch-4
Fixes when browser doesn't send origin header, defaults to empty string ...
2012-04-11 15:02:50 -07:00
crickeys
1a5a87af13 Fixes when browser doesn't send origin header, defaults to empty string instead of UNDEFINED (which would throw an error on the origin.match(/^https/) below 2012-04-11 14:41:07 -05:00
Guillermo Rauch
a4e53a642b Release 0.9.5 2012-04-05 14:37:18 -03:00
Guillermo Rauch
6f36d8c2ff Added test for polling with connection close. 2012-04-05 14:32:10 -03:00
Guillermo Rauch
09fb16b443 Ensure close upon request close. 2012-04-05 14:31:50 -03:00
Guillermo Rauch
330407cc9d Fix disconnection reason being lost for polling transports. 2012-04-05 14:31:32 -03:00
Guillermo Rauch
2075307f23 Ensure that polling transports work with Connection: close 2012-04-05 14:31:13 -03:00
Guillermo Rauch
d7b06edaca Log disconnection reason 2012-04-05 14:31:01 -03:00
Guillermo Rauch
46fdcf00b3 Release 0.9.4 2012-04-01 01:50:50 -03:00
Guillermo Rauch
147b9bb941 Release 0.9.4 2012-04-01 01:49:51 -03:00
Guillermo Rauch
02a3da487c Merge branch 'master' of github.com:LearnBoost/socket.io 2012-04-01 01:48:25 -03:00
Guillermo Rauch
087c686ad0 Release 0.9.4 2012-04-01 01:48:13 -03:00
Guillermo Rauch
16205fc522 Merge pull request #809 from DanielBaulig/issue795-fix
Issue795 fix
2012-03-28 12:58:45 -07:00
Guillermo Rauch
a232159ce8 Release 0.9.3 2012-03-28 09:28:05 -03:00
Guillermo Rauch
b9c3255b7c Merge pull request #806 from mixu/upstream/fix-ff-xhr-post-syntax
Fix "syntax error" message on FF, resulting from FF trying to parse the POST result as XML due to it not having a response content-type
2012-03-27 16:54:29 -07:00
Mikito Takada
d80010dcf0 Firefox will try to parse the response from POST requests, causing a syntax error message in the Web Console. Basically an addition to https://github.com/LearnBoost/socket.io/pull/501 2012-03-27 16:43:17 -07:00
Daniel Baulig
00694a8a98 Fix issue #795 2012-03-19 22:03:31 +01:00
Daniel Baulig
da95094998 Add disconnect from namespace test-case for issue #795 2012-03-19 21:54:20 +01:00
Guillermo Rauch
e7d7582f84 Release 0.9.2 2012-03-13 11:00:50 -03:00
Guillermo Rauch
df5f23d309 More sensible close timeout default (fixes disconnect issue) 2012-03-13 10:49:06 -03:00
Guillermo Rauch
e018ba91eb Merge branch 'master' of github.com:LearnBoost/socket.io 2012-03-09 08:47:01 -03:00
Guillermo Rauch
c59aa6ff2c Getting ready for 1.0 2012-03-09 08:42:24 -03:00
Guillermo Rauch
9431709298 Merge pull request #787 from MatthewMueller/master
io.configure('development', function() {...}) will trigger if NODE_ENV is not defined.
2012-03-09 02:17:15 -08:00
Matt Mueller
a29525e043 NODE_ENV in configure now defaults to development, which is consistent with express 2012-03-09 02:10:58 -08:00
Guillermo Rauch
5312e154b3 Release 0.9.1-1 2012-03-02 08:41:17 -03:00
Guillermo Rauch
480b86f382 Bumped client with NPM dependency fix. 2012-03-02 08:40:41 -03:00
Guillermo Rauch
c0e2c3012f Release 0.9.1 2012-03-02 08:20:38 -03:00
Guillermo Rauch
97b04c4152 Merge branch 'master' of github.com:LearnBoost/socket.io 2012-03-02 07:50:59 -03:00
Guillermo Rauch
c8306e207d Temporarily removing node 0.4 from travis-ci 2012-02-29 11:01:11 -03:00
Guillermo Rauch
de5c0b3554 Merge pull request #771 from felixge/heartbeat
Make heartbeat timeout > heartbeat interval
2012-02-29 05:43:24 -08:00
Felix Geisendörfer
57a0b24060 Make heartbeat timeout > heartbeat interval
Otherwise clients would detect timeouts before a heartbeat has a chance
of reaching them. The new values themselves were suggested by @rauchg. I
myself think that the heartbeat timeout should probably only be ~10s larger
than the interval.
2012-02-29 14:15:37 +01:00
Guillermo Rauch
204576c006 Make these 2 tests work both on 0.4 and 0.6 2012-02-26 21:51:22 -03:00
Guillermo Rauch
66ac425bf7 Release 0.9.0 2012-02-26 21:46:39 -03:00
Guillermo Rauch
a01e7e2256 Make tests pass on 0.4 2012-02-26 18:21:25 -03:00
Guillermo Rauch
47cfa5aadf Fixed tests. All 250 passing. 2012-02-26 17:14:06 -03:00
Guillermo Rauch
ddd7f804af Fixed cross-domain xhr tests. 2012-02-26 17:11:52 -03:00
Guillermo Rauch
8c1c7a24ef Merge pull request #744 from ajaxorg/regexp-as-resource
make it possible to use a regexp to match the socket.io resource URL
2012-02-09 09:38:03 -08:00
Fabian Jakobs
09b130f4cf make it possible to use a regexp to match the
socket.io resource URL. We need this because we
have to prefix the socket.io URL with a variable
ID.
2012-02-09 17:17:55 +01:00
Guillermo Rauch
b662704b0b Merge pull request #737 from mixu/mixu/authfix2
Supplemental fix to gavinuhma/authfix, it looks like the same Access-Control-Origin logic is needed in the http and xhr-polling transports
2012-02-03 14:24:57 -08:00
Mikito Takada
304a4285ff Supplemental fix to gavinuhma/authfix, it looks like the same Access-Control-Origin logic is needed in the http and xhr-polling transports 2012-02-03 12:20:40 -08:00
Guillermo Rauch
6074795b19 Updated express dep for windows compatibility. 2012-01-11 07:41:09 -08:00
Guillermo Rauch
a139809a97 Updated express dep for windows compatibility. 2012-01-11 07:40:44 -08:00
Guillermo Rauch
8ff2edd79c Merge pull request #689 from bwillard/master
Improve performance of parser.decodePayload
2011-12-16 14:04:12 -08:00
Brian Willard
ebd25676ee combine two substr calls into one in decodePayload to improve performance 2011-12-16 15:31:48 -06:00
Guillermo Rauch
d9d529cb17 Merge pull request #683 from jherdman/documentation-fix
Minor documentation fix
2011-12-12 11:02:24 -08:00
James Herdman
b37666a8e8 Minor documentation fix 2011-12-12 11:10:25 -05:00
Guillermo Rauch
cc2270bb90 Merge pull request #622 from mattrobenolt/master
Location mismatch in Safari behind proxy
2011-12-05 14:04:57 -08:00
Matt Robenolt
36fc7b07ea Minor. Conform to style of other files. 2011-12-05 16:59:15 -05:00
Matt Robenolt
8eab3a87e7 Switching setting to 'match origin protocol' 2011-12-05 16:58:00 -05:00
Guillermo Rauch
94d513c85a Revert "Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect()."
This reverts commit d5ab46d662.
2011-11-26 15:14:26 -08:00
Guillermo Rauch
70abe7aada Revert "Handle leaked dispatch:[id] subscription."
This reverts commit c110036f75.
2011-11-26 15:14:13 -08:00
Guillermo Rauch
9a8c1c4ae7 Merge pull request #667 from dshaw/patch/redis-disconnect
Patch/redis disconnect
2011-11-26 14:03:01 -08:00
Daniel Shaw
c110036f75 Handle leaked dispatch:[id] subscription. 2011-11-26 12:14:39 -08:00
Daniel Shaw
d5ab46d662 Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect(). 2011-11-26 11:22:43 -08:00
Guillermo Rauch
eeaca6d9ac Merge pull request #662 from 3rd-Eden/leak
prevent memory leaking on uncompleted requests & add max post size limit...
2011-11-23 14:04:42 -08:00
Arnout Kazemier
a7f45fe6c0 prevent memory leaking on uncompleted requests & add max post size limitation 2011-11-23 22:28:38 +01:00
Guillermo Rauch
b59fd61d56 Merge pull request #661 from 3rd-Eden/travis
Fix for testcase
2011-11-22 14:52:36 -08:00
Arnout Kazemier
7948619609 Fix for testcase 2011-11-22 23:49:58 +01:00
Guillermo Rauch
6e8166d039 Merge pull request #660 from 3rd-Eden/travis
Assertvarnish
2011-11-22 14:43:21 -08:00
Arnout Kazemier
8fc3e37ca1 Merge branch 'master' of github.com:LearnBoost/socket.io into travis 2011-11-22 23:41:54 +01:00
Guillermo Rauch
17d0f4d489 Merge pull request #630 from gavinuhma/auth-fix
set Access-Control-Allow-Origin header to origin to enable withCredentials
2011-11-22 14:38:30 -08:00
Gavin Uhma
61e7e8955a Merge branch 'master' of git://github.com/LearnBoost/socket.io into auth-fix 2011-11-22 18:03:48 -04:00
Gavin Uhma
4c17f7f83b Set Access-Control-Allow-Credentials true, regardless of cookie 2011-11-22 18:03:25 -04:00
Arnout Kazemier
0f29d786b2 Remove assertvarnish from package as it breaks on 0.6 2011-11-22 21:18:15 +01:00
Guillermo Rauch
5ee6b43921 Merge pull request #659 from 3rd-Eden/travis
Added travis
2011-11-22 11:47:49 -08:00
Arnout Kazemier
f211f78019 Correct irc channel 2011-11-22 20:46:01 +01:00
Arnout Kazemier
eeb2a73f16 Added travis 2011-11-22 20:44:19 +01:00
Guillermo Rauch
3887633e35 Merge pull request #440 from dylang/master
Updated package.json to reflect new repo location
2011-11-16 13:05:31 -08:00
Guillermo Rauch
db8cf7673b Merge pull request #634 from christopherobin/master
Fixing issue #432
2011-11-16 13:05:18 -08:00
Guillermo Rauch
dfb852151b Merge pull request #570 from 3rd-Eden/logger
Use tty to detect if we should add colors or not by default.
2011-11-16 13:04:51 -08:00
Guillermo Rauch
7b6c85030e Merge pull request #643 from einaros/master
Bug fixes, and for some reason a heap of whitespace cleanups
2011-11-14 09:09:58 -08:00
einaros
2d5dcc1a8a whitespace cleanup 2011-11-14 17:57:34 +01:00
einaros
2b28c46400 added proper return after reserved field error 2011-11-14 17:50:57 +01:00
einaros
27714d7286 fixes manager.js failure to close connection after transport error has happened 2011-11-14 17:40:16 +01:00
einaros
ffef944dd5 added implicit port 80 for origin checks. fixes #638 2011-11-14 09:45:44 +01:00
Christophe Robin
f4b434a6a5 Fixed bug #432 in 0.8.7 2011-11-11 11:06:56 +09:00
Gavin Uhma
e4a9342e8b set Access-Control-Allow-Origin header to origin to enable withCredentials 2011-11-08 18:22:30 -04:00
Guillermo Rauch
3ed6b79781 Release 0.8.7 2011-11-05 13:50:53 -07:00
Matt Robenolt
6f2270add6 Adding configuration variable matchOriginProtocol
matchOriginProtocol is meant to be used when running socket.io behind a
proxy. matchOriginProtocol should be set to true when you want the
location handshake to match the protocol of the origin. This fixes
issues with terminating the SSL in front of Node and forcing location
to think it's wss instead of ws.
2011-11-03 15:34:24 -04:00
Matt Robenolt
220f8d5bf5 Fixes location mismatch error in Safari. 2011-11-03 01:35:18 -03:00
Guillermo Rauch
946418e70e Merge pull request #621 from einaros/master
Deals with a memleak in namespaces, and silently drops malformed websocket connections
2011-11-02 15:11:21 -07:00
Guillermo Rauch
2bb60ac40b Merge pull request #616 from thekiur/master
This patch will prevent some random memory leaking.
2011-11-02 11:25:19 -07:00
TJ Holowaychuk
e20777d21d Merge pull request #617 from sutiam/patch-2
add semicolon
2011-11-01 17:33:08 -07:00
sutiam
311ef7e7e7 add semicolon 2011-11-01 17:26:33 -07:00
thekiur
9e92075cbb Removed useless object: closedA, probably fixed a memory leak 2011-11-01 23:31:58 +02:00
einaros
63043b3d5d fixes leak #608 2011-10-31 22:47:25 +01:00
einaros
92a3cce272 circumvents #602 .. although only a horribly malformed websocket connection could potentially cause this 2011-10-28 14:04:55 +02:00
Guillermo Rauch
8339c96e84 Merge pull request #596 from einaros/nodenext
Node v0.5+ compatibility
2011-10-28 01:30:18 -07:00
einaros
a125fcb1a4 further updates to chat example dependencies 2011-10-28 10:18:47 +02:00
einaros
97f634f18f further updates to irc example dependencies 2011-10-28 10:18:08 +02:00
einaros
82266cf202 updated stylus dependency in examples to node 0.5+ compatible version 2011-10-28 10:14:26 +02:00
einaros
be94641651 fixed irc example for nodenext 2011-10-28 10:12:52 +02:00
einaros
703d1d778e fixed chat example for nodenext 2011-10-28 10:10:45 +02:00
einaros
70c61fa84d Merge remote branch 'upstream/master' into nodenext 2011-10-28 10:10:26 +02:00
Guillermo Rauch
abd0326b06 Release 0.8.6 2011-10-27 20:10:18 +09:00
Guillermo Rauch
3e0b4488f8 Merge pull request #599 from 3rd-Eden/jsonptests
The server expects JSON.stringify'd packages
2011-10-27 02:44:50 -07:00
Arnout Kazemier
86908c3b4d The server expects JSON.stringify'd packages 2011-10-27 09:10:27 +02:00
Guillermo Rauch
5491c2798e Merge branch 'master' of github.com:LearnBoost/socket.io 2011-10-27 12:54:03 +08:00
Guillermo Rauch
a9def6e209 Merge pull request #598 from 3rd-Eden/utf8
charset=UTF8
2011-10-26 21:53:46 -07:00
Guillermo Rauch
c7a2dc45c8 Merge pull request #591 from einaros/master
Resubmission of: Minor potential issues corrected in websocket transports
2011-10-26 21:51:52 -07:00
Guillermo Rauch
cf76b13145 Added JSON decoding on jsonp-polling transport.
This is due to browser's buggy handling of outgoing \n
2011-10-27 12:45:19 +08:00
Arnout Kazemier
db2a17f279 charset=UTF8 2011-10-26 11:48:41 +02:00
einaros
3a07cc29bd added return for clarity 2011-10-26 08:21:22 +02:00
einaros
89a5134b66 corrected whitespace 2011-10-25 23:46:36 +02:00
einaros
6ca42fdc8f well thats five minutes of my life ill never get back. thanks. 2011-10-25 22:53:37 +02:00
einaros
a9f81a59c2 ensure backwards compatibility 2011-10-25 22:12:55 +02:00
einaros
8a90bf5234 re-enabled flashsocket tests, after bugfixes in flashsocket transport 2011-10-25 21:26:57 +02:00
einaros
357a9cb870 fixed race condition and scoping bug 2011-10-25 21:25:44 +02:00
einaros
8253ed573a ignore exception on port change 2011-10-25 19:47:39 +02:00
einaros
3f55d82bf7 added node v4 client closing compatibility 2011-10-25 13:30:06 +02:00
einaros
08467d4e12 temporarily disabled flash policy server tests 2011-10-25 13:19:39 +02:00
einaros
cb70f7873f moved default error handler 2011-10-25 13:18:40 +02:00
einaros
57b0ce73c7 resolve race condition 2011-10-25 13:09:04 +02:00
einaros
25f97431d4 set nodelay 2011-10-25 13:08:49 +02:00
einaros
f931af5758 fixed xhr-polling tests, in a way similar to jsonp 2011-10-25 10:45:20 +02:00
einaros
c88ea9ed61 fixed websocket testing, removed seemingly irrelevant tests 2011-10-25 10:42:18 +02:00
einaros
2bdee1b28f use new agent 2011-10-25 10:41:58 +02:00
einaros
a1f0b6c361 manager tests now pass properly 2011-10-24 21:26:15 +02:00
einaros
c1e64b90a4 added warning message from default error event handler 2011-10-23 23:03:23 +02:00
einaros
79c3d84a98 circumvent race condition due to changed http bits in 0.5 2011-10-23 14:21:22 +02:00
einaros
f6c376d087 another stab at proper 0.5 agent socket closing 2011-10-23 14:20:21 +02:00
einaros
bc15077ecc further checks to not close sockets that aren't there 2011-10-23 14:14:48 +02:00
einaros
28bf55e572 patched a few compatibility issues in node-websocket-server 2011-10-23 00:43:07 +02:00
einaros
f213d69e17 check that there's actually a socket to close 2011-10-22 14:39:59 +02:00
einaros
aa6f228ccf updated to use NODE_PATH rather than old expresso include argument 2011-10-22 14:39:33 +02:00
einaros
553b9e9d68 updated test to work with 0.5 2011-10-22 13:30:30 +02:00
einaros
ec6e43d7ee updated expresso dep 2011-10-22 13:30:12 +02:00
einaros
48140cf8a0 node 0.5 way of closing agent sockets 2011-10-22 13:29:57 +02:00
einaros
796bc9e95b capture error emitted by the server, which happens a lot with node 0.5+ 2011-10-22 13:28:54 +02:00
einaros
67495ad8a9 case-insensitive match for websocket upgrade, in all websocket transports 2011-10-22 10:21:10 +02:00
einaros
bee1efb11c fixes #555 2011-10-22 10:16:48 +02:00
Guillermo Rauch
120924f626 Merge pull request #582 from chees/patch-1
Changed sockets.emit to io.sockets.emit to get the example working.
2011-10-16 10:00:20 -07:00
Christiaan
acdbacb25e Changed sockets.emit to io.sockets.emit to get the example working. 2011-10-16 14:59:14 +03:00
TJ Holowaychuk
cde6a38218 Merge pull request #580 from 3rd-Eden/performance
Added a benchmark runner + profile option
2011-10-15 12:12:14 -07:00
Guillermo Rauch
e69c185e17 Merge pull request #581 from dshaw/patch/520
Fixes #520. Updates to latest node_redis.
2011-10-15 11:47:41 -07:00
Daniel Shaw
8cab86af1c Fixes #520. Updates to latest node_redis. 2011-10-15 11:43:48 -07:00
Arnout Kazemier
00557f663a Added error and heartbeat decoding 2011-10-15 15:30:23 +02:00
Arnout Kazemier
54c22aea96 Added a benchmark runner + profile option 2011-10-15 15:10:35 +02:00
Guillermo Rauch
a9929c916f Merge pull request #578 from 3rd-Eden/performance
Use benchmark.js instead, so we can benchmark 0.5.9
2011-10-13 13:42:19 -07:00
Arnout Kazemier
1d66b6b5da Some tiny optimizations 2011-10-13 22:31:52 +02:00
Arnout Kazemier
a75670c1c2 Use benchmark.js instead, so we can benchmark 0.5.9 2011-10-13 21:00:59 +02:00
Guillermo Rauch
373c729e66 Merge pull request #573 from 3rd-Eden/performance
Parser performance boost
2011-10-12 15:10:19 -07:00
Arnout Kazemier
7800003c5e Returned the switch for the decoder, optimized the switch for the encoder 2011-10-12 22:03:46 +02:00
Arnout Kazemier
b662f2e14e Optimized the loop, so the most commen packets are checked first 2011-10-12 21:53:38 +02:00
Arnout Kazemier
709c172444 http://cl.ly/402t2C133B2a1P0g1K0h <--- before http://cl.ly/363W080c2j261l3A1m3y <--- after ;o 2011-10-12 21:44:09 +02:00
Tj Holowaychuk
6d5ffa0d33 remove vbench dev dep
so people dont need node-canvas
2011-10-11 21:38:31 -07:00
Tj Holowaychuk
f8c7ff2782 Added decode/encode benchmarks 2011-10-11 16:00:15 -07:00
Arnout Kazemier
d9049f69c1 Merge branch 'master' of github.com:LearnBoost/socket.io into logger 2011-10-11 20:01:05 +02:00
Arnout Kazemier
10ffbd59e9 Use tty to detect if we should add colors or not by default. 2011-10-11 20:00:30 +02:00
Guillermo Rauch
175fe8573b Merge pull request #563 from 3rd-Eden/logger
Logger
2011-10-11 08:18:16 -07:00
Guillermo Rauch
0224e4ac5f Merge pull request #569 from 3rd-Eden/blacklist
Blacklist events
2011-10-11 08:13:55 -07:00
Arnout Kazemier
ecd20b0e1f Added support for blacklisting events that are emitted from the client side.
Currently it's possible for a client do .emit('disconnect') and this will trigger
the disconnect event on the server.. Which can lead to major issues.

We should black list that by default. You can override or add more events by adding
them to the `blacklist` setting
2011-10-11 10:40:22 +02:00
Arnout Kazemier
b8f6dc7810 Inital stab at blacklisting client side events 2011-10-11 09:53:26 +02:00
Guillermo Rauch
0339e745fd Merge pull request #564 from 3rd-Eden/bug/538
fixes #538
2011-10-10 13:23:51 -07:00
Arnout Kazemier
b3740e9ab6 fixes #538 2011-10-10 21:52:16 +02:00
Arnout Kazemier
0b7ed64082 Fixed logging options, closes #540 2011-10-10 21:32:54 +02:00
Arnout Kazemier
07b84f4400 Added a test to ensure that etags do no mess up far future headers 2011-10-10 21:06:58 +02:00
Guillermo Rauch
59e4c3b46c Merge pull request #562 from 3rd-Eden/static
Hardcore caching for pro's
2011-10-10 11:20:38 -07:00
Arnout Kazemier
0e3bbd0e16 Whoops 2011-10-10 20:02:37 +02:00
Arnout Kazemier
763fdd1c4e Added more hardcore caching fixes #558
Added tests against it
Added vary header for gzip
2011-10-10 20:01:28 +02:00
Guillermo Rauch
61bd23f0f9 Merge pull request #561 from einaros/master
Proper websocket test fixes, after patching node-websocket-client
2011-10-09 01:33:13 -07:00
Arnout Kazemier
8107c1a1e2 Added support for HEAD requests, closes #557 2011-10-08 15:46:23 +02:00
Arnout Kazemier
fa5b518110 Merge branch 'master' of github.com:LearnBoost/socket.io into static 2011-10-08 15:42:21 +02:00
einaros
b3df2836e9 properly cleaned up async websocket / flashsocket tests, after patching node-websocket-client 2011-10-08 12:02:31 +02:00
einaros
08568ee49e patched to properly shut down when a finishClose call is made during connection establishment 2011-10-08 12:01:46 +02:00
einaros
aba2d5e0ef removed empty console.log 2011-10-08 12:00:55 +02:00
Guillermo Rauch
dfebed38ab Release 0.8.5 2011-10-07 11:25:39 -07:00
Guillermo Rauch
51782fc5d7 Fixed websocket tests. 2011-10-07 11:07:33 -07:00
Guillermo Rauch
11f1a7c491 Merge pull request #347 from 3rd-Eden/socket.transport
Expose socket.transport
2011-10-07 10:07:05 -07:00
Arnout Kazemier
5573f7fcdf First stab of adding Expires + cache control headers for #558 2011-10-05 20:16:20 +02:00
Guillermo Rauch
1d743cfc84 Merge branch 'master' of github.com:LearnBoost/socket.io 2011-10-04 21:21:31 -07:00
Guillermo Rauch
21c01558fd Added tip [getify] 2011-10-04 21:21:20 -07:00
Guillermo Rauch
6d57445167 Fixed readme [getify] 2011-10-04 21:18:02 -07:00
Guillermo Rauch
52f6a5b124 Merge pull request #554 from einaros/master
Fixes issue #553: Origin verification brought into the client
2011-10-02 10:59:41 -07:00
einaros
2c3c73f045 corrected origin header name in test 2011-10-02 16:37:14 +02:00
einaros
54fc513fc9 fixes #553 - (re)verify origin in websocket transport implementations 2011-10-02 16:34:09 +02:00
einaros
0b1e43cb87 added binary support to the hybi 07-12 parsers as well 2011-10-02 15:31:36 +02:00
einaros
c8dabb225c added binary support to hybi-16 parser, refactored hybi tests somewhat 2011-10-02 15:19:17 +02:00
einaros
245dc12ade added fixes from hybi-07-12 which also apply to 16 2011-10-02 00:09:21 +02:00
Guillermo Rauch
7a405232a5 Merge pull request #550 from einaros/master
HyBi-16 support, and a few bugfixes
2011-10-01 12:15:16 -07:00
einaros
00f7ca1d02 fixed incorrect space chars 2011-10-01 14:45:20 +02:00
einaros
f1cea7e788 Merge branch 'master' of github.com:einaros/socket.io 2011-10-01 14:42:07 +02:00
einaros
050fcf7a83 websocket draft HyBi-16 support 2011-10-01 14:39:27 +02:00
Guillermo Rauch
dfa350bea7 Revert "Expanded 'How to Use' in readme"
This reverts commit ed7cedd78f.
2011-09-26 13:27:52 -07:00
Guillermo Rauch
e5d5b99f0e Merge pull request #539 from AlexChesser/alex.chesser
Expanded 'How to Use' in readme
2011-09-26 13:26:42 -07:00
Alex Chesser
ed7cedd78f Expanded 'How to Use' in readme 2011-09-26 15:35:37 -04:00
einaros
a22eb70cfb Merge remote branch 'upstream/master' 2011-09-26 10:21:09 +02:00
einaros
2a81b25a5b fixed websocket continuation bugs 2011-09-26 10:14:19 +02:00
Guillermo Rauch
7db146df47 Merge pull request #457 from 3rd-Eden/ACAO
access-control-allow-origin
2011-09-20 15:04:59 -07:00
Guillermo Rauch
6182dfff39 Merge pull request #532 from einaros/master
Fixes issue 523, and (unreported?) issue where flashsocket identifies as websocket
2011-09-20 10:03:42 -07:00
einaros
1ccd8cea6b Merge remote branch 'upstream/master' 2011-09-20 18:56:08 +02:00
einaros
f9ea04eb6b redirect actual transport name (such as flashsocket), if present, to the websocket version being loaded 2011-09-20 18:53:04 +02:00
einaros
ab9a5a1578 added test to ensure that websocket still identifies as .. websocket 2011-09-20 18:51:36 +02:00
einaros
3364a73a97 added done() call to end test 2011-09-20 18:45:01 +02:00
einaros
d02a7f415b added test for flashsocket name, to catch issues with various websocket versions 2011-09-20 18:41:06 +02:00
einaros
1874fd7c30 added transport option to websocket test client (for flashsocket testing) 2011-09-20 18:40:43 +02:00
einaros
3c4a04ea02 fixes #523. private 'payload' method in transport used from manager.js, wasn't present in hybi parser 2011-09-20 17:56:32 +02:00
Guillermo Rauch
1468917743 Merge pull request #529 from dshaw/patch/redisstore
Fix RedisStore
2011-09-19 10:06:52 -07:00
Arnout Kazemier
6df152cc5d Merge branch 'master' of github.com:LearnBoost/socket.io into ACAO
Conflicts:
	lib/manager.js
2011-09-13 21:45:51 +02:00
Daniel Shaw
c6b3549b61 Minimal RedisClient configs. 2011-09-12 00:20:40 -07:00
Daniel Shaw
f80ab2aae8 Merge branch 'master' of https://github.com/LearnBoost/socket.io 2011-09-11 23:06:23 -07:00
Guillermo Rauch
b2f9f19d99 Merge pull request #391 from 3rd-Eden/static
Static
2011-09-07 09:39:30 -07:00
Guillermo Rauch
cb7304837c Ensured parser#decodePayload doesn't choke.
This addresses the situation where malformed data is supplied to the parser.
Fixes #510
2011-09-07 08:31:46 -07:00
Guillermo Rauch
9e6f58fe27 Release 0.8.4 2011-09-06 07:48:14 -07:00
Guillermo Rauch
e3fb39da3d Corrected comment; (fixes #433) 2011-09-03 14:33:00 -07:00
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
Daniel Shaw
cc0a96a8d9 Wow, really. 2011-09-01 14:22:53 -07:00
Daniel Shaw
7b2b302022 merge learnboost/master 2011-09-01 14:00:17 -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
d88575dadf Release 0.8.2 2011-08-29 17:07:29 -07:00
Guillermo Rauch
93c963e30f Release 0.8.1 2011-08-29 17:07:29 -07:00
einaros
72a79e5cec fixed utf8 bug in send framing 2011-08-29 17:07:29 -07:00
Markus Hedlund
140ed41907 Fixed typo. 2011-08-29 17:07:29 -07:00
einaros
12fc168516 fixed bug in send framing for over 64kB of data 2011-08-29 17:07:29 -07:00
einaros
2c3dc42ae8 corrected ping handling from websocket transport, and added warning output on parser error 2011-08-29 17:07:29 -07:00
einaros
48dadd8e10 joined compatible hybi protocol handlers and updated test reference 2011-08-29 17:07:29 -07:00
einaros
ce4c46b37d fixed Parser library path and did some code cleanup 2011-08-29 17:07:29 -07:00
Guillermo Rauch
c8938a99b2 Release 0.8.0 2011-08-29 17:07:29 -07:00
Guillermo Rauch
b7998e815a Renamed wsver/ -> websocket/ 2011-08-29 17:07:29 -07:00
Guillermo Rauch
444229a9dc Changed protocols require path. 2011-08-29 17:07:28 -07:00
einaros
b69dac6f4d added hybi07 tests 2011-08-29 17:07:28 -07:00
einaros
94fdbadaec added initial hybi07 protocol parser 2011-08-29 17:07:28 -07:00
Guillermo Rauch
c4b23246b4 Release 0.7.11 2011-08-29 17:07:28 -07:00
einaros
5186788969 cleanups and comments 2011-08-29 17:07:28 -07:00
Guillermo Rauch
169ad8245f Release 0.7.10 2011-08-29 17:07:28 -07:00
einaros
71e013a197 added test and support for really long messages, but capped by 32 bit 2011-08-29 17:07:28 -07:00
einaros
7e60d37171 minor cleanups 2011-08-29 17:07:28 -07:00
einaros
a9e9e64eab updated to work with two-level websocket versioning 2011-08-29 17:07:28 -07:00
einaros
39ae8d4629 added hybi10 close operation 2011-08-29 17:07:28 -07:00
einaros
a075870308 added hybi10 parser tests 2011-08-29 17:07:28 -07:00
einaros
186649102d added hybi10 support 2011-08-29 17:07:28 -07:00
einaros
bb2e100e7f Added http referrer verification to manager.js verifyOrigin + tests for origins setting 2011-08-29 17:07:28 -07: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
Daniel Shaw
dd30de3c5a Merge remote-tracking branch 'learnboost/master' 2011-08-16 15:26:30 -07: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
e269fcaf0d Fixed semicolon 2011-08-10 22:58:13 +02:00
Arnout Kazemier
a8c61b0001 undo 2011-08-10 22:57:25 +02:00
Arnout Kazemier
4708480e7d Added access control for cross domain xhr handshakes 2011-08-10 22:35:30 +02: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
Daniel Shaw
30284944b1 Merge remote-tracking branch 'learnboost/master' 2011-08-08 11:08:28 -07:00
Guillermo Rauch
65f1399a44 Release 0.7.8 2011-08-08 08:06:30 -07:00
Daniel Shaw
559d36601d Merge remote-tracking branch 'learnboost/master' 2011-08-06 19:07:27 -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
Daniel Shaw
1fa158c663 Merge with learnboost/master 2011-08-03 13:22:36 -07:00
Dylan Greene
a51fe07420 Updated the package location. 2011-08-01 14:44:11 -03:00
Arnout Kazemier
a631f86b3b Merged with upstream master 2011-07-31 10:22:54 +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
Daniel Shaw
831f1baa4a Merge remote-tracking branch 'learnboost/master' 2011-07-28 16:38:48 -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
Arnout Kazemier
0d3441d8b3 Added gzip to the options list and fixed a typo 2011-07-18 00:20:05 +02:00
Arnout Kazemier
23e14223bd Added more test cases and allow a more flexible constructor for adding content 2011-07-17 23:50:06 +02:00
Arnout Kazemier
4b94f2b8bf Passes all 14 tests 2011-07-17 20:21:08 +02:00
Arnout Kazemier
f88eedc3c8 More tests 2011-07-17 11:13:12 +02:00
Arnout Kazemier
5c24bf8c1d Removed old testcases from manager and tiny fix for Static 2011-07-17 01:24:21 +02:00
Arnout Kazemier
9cd51b1f6b Passes test suite 2011-07-17 00:10:43 +02:00
Arnout Kazemier
2f8eb63557 Added support for automatic generation of socket.io files
Added support for custom socket.io files based on path structure

For example socket.io+websocket+xhr-polling.js will create a dedicated build with
only support for websockets and xhr-polling.
2011-07-16 21:55:06 +02:00
Arnout Kazemier
4bdc30734c Fixed small typos 2011-07-15 23:59:10 +02:00
Arnout Kazemier
4743744efc Expose the constructor in socket.io and have the Manager use the new API 2011-07-15 22:31:03 +02:00
Arnout Kazemier
c2d98bde72 Add now stores the request details and has returns the details for static content 2011-07-15 22:10:51 +02:00
AD7six
c899c98f31 prevent duplicate references in rooms 2011-07-15 01:13:41 +02:00
Arnout Kazemier
9e97e6c691 Added more comments 2011-07-15 00:30:25 +02:00
Arnout Kazemier
afdfdb815e Finished the write function setup 2011-07-15 00:24:57 +02:00
Arnout Kazemier
d043c33351 Added the write and answer fn 2011-07-15 00:13:54 +02:00
Arnout Kazemier
53f0f4d66d Added alias for to to in and in to to 2011-07-14 21:31:55 +02:00
Arnout Kazemier
168b207c6d Inital draft of gzip support 2011-07-14 21:24:26 +02:00
Arnout Kazemier
f6ebb7b8d6 API structure 2011-07-14 20:54:40 +02:00
Guillermo Rauch
c826fadb9f Fixed roomClients definition (#378). 2011-07-13 18:30:24 -03:00
Daniel Shaw
dfebed6c2f Fix typo. Also mentioned in @dluxemburg's #341. 2011-07-12 02:56:48 -07:00
Daniel Shaw
3a2545b497 merge 2011-07-12 01:22:54 -07: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
Daniel Shaw
ffa17e1205 Merge remote-tracking branch 'learnboost/master' 2011-07-11 00:16:07 -07:00
Daniel Shaw
59e250b186 Run initStore on store setting change. Fixes #367. 2011-07-11 00:14:37 -07:00
Arnout Kazemier
b2ffed891b Fixed typo 2011-07-11 00:12:27 -07:00
Daniel Shaw
9bf10ed051 Updated to the latest redis release. Lots of important patches. 2011-07-10 17:11:08 -07:00
Arnout Kazemier
9cfdb8ed97 Expose socket.transport 2011-07-06 00:10:25 +02: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
Guillermo Rauch
a483f9cafd Release 0.7.6 2011-06-30 15:32:22 -03:00
Guillermo Rauch
b43c82f3db Fixed general dispatching when a client has closed. 2011-06-30 14:03:43 -03:00
Guillermo Rauch
84f0099c1d Release 0.7.5 2011-06-30 12:57:44 -03:00
Guillermo Rauch
c6a883c8a9 Fixed dispatching to clients that are disconnected. 2011-06-30 12:56:42 -03:00
Guillermo Rauch
af2d9f285b Release 0.7.4 2011-06-30 12:48:00 -03:00
Guillermo Rauch
40c1f8da2b Fixed; only clear handlers if they were set. [level09] 2011-06-30 12:47:02 -03:00
Guillermo Rauch
9b40977616 Release 0.7.3 2011-06-30 10:38:03 -03:00
Guillermo Rauch
964f040b58 Merge pull request #318 from dvv/patch-1
normalize SocketNamespace local eventing
2011-06-30 06:22:26 -07:00
Vladimir Dronnikov
44919189ec normalize SocketNamespace local eventing 2011-06-30 03:12:55 -07:00
Guillermo Rauch
6c4cc5113f Merge pull request #315 from 3rd-Eden/intentional
disconnect
2011-06-29 13:23:45 -07:00
Arnout Kazemier
06e29fef67 Use packet.reason or default to 'packet' 2011-06-29 21:58:13 +02:00
Arnout Kazemier
16518c0715 Added a small indicator + test suite so you can see how a user is disconnected 2011-06-29 21:45:14 +02:00
Guillermo Rauch
09a713da18 Merge pull request #314 from nyxtom/master
Issue #313 TypeError: Object #<CleartextStream> has no method 'address'
2011-06-29 10:52:56 -07:00
Thomas Holloway
3e0239ed07 Quick fix to issues #313 to check if an address exists on a connection 2011-06-29 12:29:00 -05:00
Guillermo Rauch
d3d2a70944 Added MemoryStore expiration tests. 2011-06-29 11:38:28 -03:00
Guillermo Rauch
1bcf87eb14 Added RedisStore expiration tests. 2011-06-29 11:38:18 -03:00
Guillermo Rauch
f9c210c11c Added expiration support to RedisStore.Client#destroy through EXPIRE command. 2011-06-29 11:37:58 -03:00
Guillermo Rauch
06421dd008 Added expiration support to MemoryStore.Client#destroy. 2011-06-29 11:37:26 -03:00
Guillermo Rauch
af7c7141f0 Changed; for consistency across stores, undefined becomes null. 2011-06-29 11:36:14 -03:00
Guillermo Rauch
def5498f61 Added expiration parameters to Store#destroyClient and Store#destroy 2011-06-29 11:35:44 -03:00
Guillermo Rauch
494b2d4487 Added Socket#has
Added `Socket#del`
2011-06-29 11:35:13 -03:00
Guillermo Rauch
ba3b854190 Changed; make sure to cache a reference to the client object so that it can be
referenced after its destroying and before the data expires.
2011-06-29 11:33:54 -03:00
Guillermo Rauch
abc3723b54 Changed; make sure to pass client store expiration to Store#destroyClient. 2011-06-29 11:33:24 -03:00
Guillermo Rauch
ba10280071 Added a client store expiration option with a 15 seconds default. 2011-06-29 11:32:47 -03:00
Guillermo Rauch
fc95d340ef Merge pull request #306 from 3rd-Eden/namespace.auth
Namespace.auth
2011-06-29 04:14:29 -07:00
Arnout Kazemier
d47bcd51ba Merge branch 'master' of github.com:LearnBoost/socket.io into namespace.auth 2011-06-29 13:10:23 +02:00
Guillermo Rauch
be7f56b819 Merge branch 'master' of github.com:LearnBoost/socket.io 2011-06-29 07:55:21 -03:00
Guillermo Rauch
6a83d6879c Added redis store tests 2011-06-29 07:54:26 -03:00
Guillermo Rauch
56f5911b60 Added memory store tests 2011-06-29 07:54:13 -03:00
Guillermo Rauch
2fd38866d2 Finished redis store 2011-06-29 07:53:51 -03:00
Guillermo Rauch
4398271ff5 Added MemoryClient#has, MemoryClient#del
Refactored MemoryClient#destroy
2011-06-29 07:51:54 -03:00
Guillermo Rauch
6ce89b80ef Added Store#destroyClient
Refactored Store#destroy
2011-06-29 07:51:21 -03:00
Guillermo Rauch
1449b18681 Added docs 2011-06-29 07:50:23 -03:00
Guillermo Rauch
f921164bf8 Removed Store instrumentation 2011-06-29 07:50:04 -03:00
Guillermo Rauch
ea1f5822ae Removed Store#log since they're manager-independent. 2011-06-29 07:49:47 -03:00
Guillermo Rauch
2fd406f7b2 Fixed; make sure to clean up store upon client disconnect. 2011-06-29 07:49:22 -03:00
Guillermo Rauch
34d0cbdbf0 Merge pull request #310 from 3rd-Eden/websocket.write
Having a stab at issue #295
2011-06-29 03:20:49 -07:00
Arnout Kazemier
2110bac72d Having a stab at issue #295 2011-06-29 12:17:20 +02:00
Arnout Kazemier
299e097fa7 Added storage for socket.handshake & updated test suite to check for it 2011-06-28 23:50:33 +02:00
Arnout Kazemier
a1963fcf7d Added missing test suite 2011-06-28 21:49:45 +02:00
Arnout Kazemier
8abe712294 Passes test suite 2011-06-28 21:41:46 +02:00
Guillermo Rauch
df21bea4df Fixed store references 2011-06-28 16:32:40 -03:00
Arnout Kazemier
45f2718954 Cleaned up the websocket test 2011-06-28 21:05:53 +02:00
Guillermo Rauch
24aef9e40b Merge branch 'master' of github.com:LearnBoost/Socket.IO-node 2011-06-28 15:50:19 -03:00
Guillermo Rauch
a00ce7eee6 Fix for client side namespaces. 2011-06-28 15:49:48 -03:00
Guillermo Rauch
17dd94e3c9 Merge pull request #300 from 3rd-Eden/static.write
Create a new branch for fixing #287
2011-06-28 11:45:40 -07:00
Guillermo Rauch
81fe9e0117 Merge pull request #302 from 3rd-Eden/readme
Readme fixes
2011-06-28 11:45:05 -07:00
Arnout Kazemier
c9bf814580 Readme fixes 2011-06-28 20:43:24 +02:00
Arnout Kazemier
78b546e185 Incorporated feedback from @guille 2011-06-28 20:37:58 +02:00
Arnout Kazemier
e7deb32a6f Create a new branch for fixing #287 2011-06-28 20:23:13 +02:00
Guillermo Rauch
4e4199c15e Added test for SocketNamespace#clients 2011-06-28 13:33:09 -03:00
Guillermo Rauch
fb79657476 Added SocketNamespace#clients 2011-06-28 13:33:01 -03:00
Guillermo Rauch
f2212f6962 Make handshake data extensible 2011-06-28 09:27:07 -03:00
Guillermo Rauch
c9d21c5ff5 Fixed tests 2011-06-28 08:29:42 -03:00
Guillermo Rauch
6b6f17b098 Added handshakeData#address
Added handshakeData#secure
2011-06-28 08:29:00 -03:00
Guillermo Rauch
9740b42866 Added test for Socket#handshake. 2011-06-28 07:37:25 -03:00
Guillermo Rauch
2a1b2bd37e Added Socket#handshake data getter. 2011-06-28 07:37:15 -03:00
Guillermo Rauch
b58c2e4afe Added handshake data normalizer function.
Changed; Make sure we store the `handshake data` in the hash of connected clients.
2011-06-28 07:36:35 -03:00
Guillermo Rauch
a2f8fb4970 Implemented RedisStore.Client#get and RedisStore.Client#set 2011-06-27 16:21:32 -03:00
Guillermo Rauch
a4ef10d6a2 Added missing redis require. 2011-06-27 16:14:47 -03:00
Guillermo Rauch
91e43064f3 Added Redis store.
- Simply leverages pub/sub for nodes communication
  - Makes sure own node doesn't consume its own messages
  - Optionally uses msgpack to encode data for higher speed than JSON
  - Opens two connections per node (one in pub/sub mode, one for normal redis)
2011-06-27 15:55:33 -03:00
Guillermo Rauch
76e6b9ea6f Removed comments in xhr-polling test. 2011-06-27 15:53:15 -03:00
Guillermo Rauch
a517efcaca Fixed signature of Socket#join in tests
Fixed style
2011-06-27 15:52:35 -03:00
Guillermo Rauch
8f6860b155 Make sure to close websocket clients on session tests 2011-06-27 15:52:17 -03:00
Guillermo Rauch
72283a9078 Removed comments from tests 2011-06-27 15:51:59 -03:00
Guillermo Rauch
c9d87ebb98 Fixed xhr polling transport name 2011-06-27 15:51:44 -03:00
Guillermo Rauch
4a591f191f Fixed xhr polling constructor to take request as parameter 2011-06-27 15:51:26 -03:00
Guillermo Rauch
779816ded7 Added req to websocket transport constructor. 2011-06-27 15:50:46 -03:00
Guillermo Rauch
2b90edb3a2 Added req parameter to jsonp-polling constructor. 2011-06-27 15:50:24 -03:00
Guillermo Rauch
a99016df79 Make sure to take request as parameter in the http constructor 2011-06-27 15:50:08 -03:00
Guillermo Rauch
78c7a55ce4 Make sure to take request as parameter in the polling constructor 2011-06-27 15:49:37 -03:00
Guillermo Rauch
3fbce85315 Make sure to take request as parameter in the htmlfile constructor 2011-06-27 15:49:13 -03:00
Guillermo Rauch
9aa5320730 Fixed Flashsocket constructor 2011-06-27 15:48:20 -03:00
Guillermo Rauch
2fb28c8843 Removed Transport#subscribe and Transport#unsubscribe 2011-06-27 15:47:55 -03:00
Guillermo Rauch
767ca4f100 Fixed .end signature 2011-06-27 15:47:43 -03:00
Guillermo Rauch
733569ae68 Added Transport#discard. Mostly useful for when request/response cycles are load
balanced into different nodes.
2011-06-27 15:47:14 -03:00
Guillermo Rauch
f736302b97 Refactored Transport#end
Removed concept of transport pausing as we no longer have async buffers.
2011-06-27 15:46:43 -03:00
Guillermo Rauch
7bfcf54f90 Publish transport close event. 2011-06-27 15:45:58 -03:00
Guillermo Rauch
fa5ef9ff2d Removed forced parameter from .end call from disconnect 2011-06-27 15:45:37 -03:00
Guillermo Rauch
df457440fd Refactored onMessage to leverage subscriptions 2011-06-27 15:45:18 -03:00
Guillermo Rauch
375cbf49de Important fix for hearbeats 2011-06-27 15:45:09 -03:00
Guillermo Rauch
2c66814b82 Applied new .end signature 2011-06-27 15:44:50 -03:00
Guillermo Rauch
2a4e4e1300 Added handler for dispatch subscription 2011-06-27 15:44:38 -03:00
Guillermo Rauch
6f12de98fc Added handler for heartbeat clear subscription 2011-06-27 15:44:28 -03:00
Guillermo Rauch
73ea4dd13f Removed the .end method to disregard whether it was forced or not 2011-06-27 15:43:46 -03:00
Guillermo Rauch
772afe897d Refactored Transport to leverage subscriptions. 2011-06-27 15:43:24 -03:00
Guillermo Rauch
0b75d09090 Refactored Memory.Client 2011-06-27 15:42:49 -03:00
Guillermo Rauch
95e787dcc5 Removed all transport-specific methods from Memory store
Added publish/subscribe placeholders that basically do nothing.
2011-06-27 15:40:32 -03:00
Guillermo Rauch
5342dd6d76 Refactored Socket
- initialize options in constructor
  - inherit from EventEmitter always
  - added generic Socket#client
2011-06-27 15:37:30 -03:00
Guillermo Rauch
195e227393 Refactored Socket#disconnect to intelligently decide whether the disconnection has
to be forced or just destroy the storage, so that a re-opening of the transport is
not possible.
2011-06-27 15:36:20 -03:00
Guillermo Rauch
c28a85e520 Refacotred Socket#set and Socket#get 2011-06-27 15:36:08 -03:00
Guillermo Rauch
bf8b1e6879 Added Socket#dispatch, in charge of intelligently determining whether the packet
has to be relied to other nodes to handle, handle it internally, or discarding it
if it's volatile from the local buffer provided the transport is not open.
2011-06-27 15:35:09 -03:00
Guillermo Rauch
240cf3fde6 Leverage Socket#dispatch to dispatch a message. 2011-06-27 15:34:46 -03:00
Guillermo Rauch
d40cde8503 Refactored Socket#leave 2011-06-27 15:34:31 -03:00
Guillermo Rauch
db9d81971d Refactored Socket#join 2011-06-27 15:34:20 -03:00
Guillermo Rauch
54d2c0111d Changed; Socket#store now returns the client store. 2011-06-27 15:32:44 -03:00
Guillermo Rauch
cfd5315b1e Removed disconnect subscription per-socket (added unnecessary overhead with multiple
namespaces).
2011-06-27 15:32:19 -03:00
Guillermo Rauch
cbc12ecdcf Make sure to leave rooms upon disconnection of a namespace (fixes some bug). 2011-06-27 15:31:44 -03:00
Guillermo Rauch
9aa650e430 Removed the join callback, since it's not necessary (TCP based pub/sub guarantees
proper total ordering).
2011-06-27 15:30:47 -03:00
Guillermo Rauch
f06fefab14 Added handleDisconnect method to emit the disconnect event when the socket is
disconnected due to network reasons, making sure we don't emit it on non-connected
namespaces.
2011-06-27 15:30:02 -03:00
Guillermo Rauch
5b6efb784f Simplified global message dispatching by leveraging subscriptions. 2011-06-27 15:28:24 -03:00
Guillermo Rauch
c210241379 Refactored handshake 2011-06-27 15:27:58 -03:00
Guillermo Rauch
8594d684ef Fixed style 2011-06-27 15:27:44 -03:00
Guillermo Rauch
970621d5ee Refactored client request handling:
- removed the concept of async handshake, since we now keep track of open/closed
   clients and handshakes across all nodes (replication)
 - when a client closes temporarily, before the disconnection is triggered and before
   the transport is reopened in any node, we buffer messages in all nodes. We therefore
   remove the need for a complex message queue with a memory tradeoff (buffer redundancy).
2011-06-27 15:21:50 -03:00
Guillermo Rauch
c9d9c2e8b3 Added initStore and subscription handlers. 2011-06-27 15:21:01 -03:00
Guillermo Rauch
8f44f026ee Removed usage of Store#disconnect, replaced with a simple subscription. 2011-06-27 15:20:35 -03:00
Guillermo Rauch
c369073a72 Moved id generation method into the manager. 2011-06-27 15:18:37 -03:00
Guillermo Rauch
b700f0546d Added call to initStore to initialize subscriptions. 2011-06-27 15:16:54 -03:00
Guillermo Rauch
0701408061 Added redis client dependency.
Removed >= from policy.
2011-06-27 15:14:53 -03:00
Guillermo Rauch
bafb347e4a Changed console.error to console.log. 2011-06-22 23:56:14 -03:00
Guillermo Rauch
802da70bf7 Fixed; bind both servers at the same time do that the test never times out. 2011-06-22 22:18:51 -03:00
Guillermo Rauch
f2711daa37 Merge pull request #282 from 3rd-Eden/master
304
2011-06-22 15:49:56 -07:00
Arnout Kazemier
7200dfed84 Output the correct name 2011-06-23 00:48:06 +02:00
Arnout Kazemier
b348f4de99 Style 2011-06-23 00:23:18 +02:00
Arnout Kazemier
6452602603 Merge branch 'master' of https://github.com/LearnBoost/Socket.IO-node 2011-06-23 00:20:55 +02:00
Arnout Kazemier
6f93bb2ec7 Added 304 support 2011-06-23 00:20:28 +02:00
Guillermo Rauch
ffd3e8bc96 Removed Transport#name for abstract interface. 2011-06-22 18:40:26 -03:00
Guillermo Rauch
bdea4b11a7 Merge pull request #281 from 3rd-Eden/master
Lazy load http / https modules & fix for crash in memory store
2011-06-22 13:49:54 -07:00
Arnout Kazemier
041b5655f9 Delete they client instead of setting it to null. And make sure the client exists
before we do .disconnect
2011-06-22 22:43:39 +02:00
Arnout Kazemier
080676bf6e Lazy require http and https module only when needed 2011-06-22 21:40:02 +02:00
Guillermo Rauch
2e7076abcc Merge pull request #273 from 3rd-Eden/master
merge fail
2011-06-22 01:27:43 -07:00
Arnout Kazemier
b87e51bae3 Refixed merge fail 2011-06-22 10:26:39 +02:00
Guillermo Rauch
cc5bc44ef0 Merge pull request #265 from 3rd-Eden/master
Better naming in the logging
2011-06-22 01:25:30 -07:00
Arnout Kazemier
4672ab65d7 Merge branch 'master' of https://github.com/LearnBoost/Socket.IO-node
Conflicts:
	lib/transports/http-polling.js
2011-06-22 10:24:35 +02:00
Guillermo Rauch
331e6e85b9 Release 0.7.2 2011-06-22 04:15:01 -03:00
Guillermo Rauch
f2df40aa02 Bumped client version.
Bumped version
2011-06-22 04:12:26 -03:00
Guillermo Rauch
36ecd49ad2 Updated tests. 2011-06-22 04:09:19 -03:00
Guillermo Rauch
aef5027640 Make sure to write a packet (of type noop) when closing a poll. This solves a problem
with cross-domain requests being flagged as aborted and reconnection being triggered.
2011-06-22 04:08:28 -03:00
Guillermo Rauch
4933cf1a9e Added noop message type. 2011-06-22 04:08:11 -03:00
Arnout Kazemier
28b396c3fc Merge branch 'master' of https://github.com/LearnBoost/Socket.IO-node 2011-06-22 08:58:33 +02:00
Guillermo Rauch
c425724bd2 Release 0.7.1 2011-06-21 21:05:11 -03:00
Guillermo Rauch
0b9bc61ff1 Added CORS test to xhr-polling suite. 2011-06-21 21:02:22 -03:00
Guillermo Rauch
3bab44506b Added header override support in test client. 2011-06-21 21:02:11 -03:00
Guillermo Rauch
aeac79faa9 Fixed; restored headers in POST responses. 2011-06-21 20:34:20 -03:00
Guillermo Rauch
903f7ec9ff Fixed cross-domain POST XHR requests for Firefox. 2011-06-21 20:17:24 -03:00
Arnout Kazemier
808e794ec5 Merge branch 'master' of github.com:3rd-Eden/Socket.IO-node 2011-06-21 21:35:42 +02:00
Arnout Kazemier
cb7aa0a79c Merge branch 'master' of https://github.com/LearnBoost/Socket.IO-node 2011-06-21 21:35:26 +02:00
Arnout Kazemier
1bddfc45dd Added client handler to the options, just for the sake of consistency 2011-06-21 21:09:41 +02:00
Arnout Kazemier
836eb1d1c2 Merge branch 'master' of https://github.com/LearnBoost/Socket.IO-node 2011-06-21 11:55:24 +02:00
Arnout Kazemier
4c5dfd53f0 Merge branch 'master' of https://github.com/LearnBoost/Socket.IO-node 2011-06-21 10:03:20 +02:00
Arnout Kazemier
a62bced081 this.name > prototype.name 2011-06-21 10:00:13 +02:00
Arnout Kazemier
397dfbc51d Naming 2011-06-21 09:44:26 +02:00
57 changed files with 7782 additions and 1770 deletions

1
.gitignore vendored
View File

@@ -6,4 +6,5 @@ lib-cov
*.dat
*.out
*.pid
benchmarks/*.png
node_modules

6
.travis.yml Normal file
View File

@@ -0,0 +1,6 @@
language: node_js
node_js:
- 0.6
notifications:
irc: "irc.freenode.org#socket.io"

View File

@@ -1,5 +1,342 @@
0.0.1 / 2010-01-03
0.9.19 / 2017-05-16
===================
* Properly require EventEmitter
0.9.18 / 2017-05-07
===================
* Remove process.EventEmitter usage for Node 7.x
0.9.17 / 2014-05-22
===================
* use static channels for remote syncing instead of subscribing/unsubscribing 5 channels for every connection
* Use destroy buffer size on websocket transport method as well
* http-polling : adding 'X-XSS-Protection : 0;' to headers necessary not only to jsonp-polling but http-polling
0.9.16 / 2013-06-06
===================
* transports: added tests for htmlfile escaping/unescaping
0.9.15 / 2013-06-06
===================
* transports: added escaping to htmlfile (fixes #1251)
0.9.14 / 2013-03-29
===================
* manager: fix memory leak with SSL [jpallen]
0.9.13 / 2012-12-13
===================
* package: fixed `base64id` requirement
0.9.12 / 2012-12-13
===================
* manager: fix for latest node which is returning a clone with `listeners` [viirya]
0.9.11 / 2012-11-02
===================
* package: move redis to optionalDependenices [3rd-Eden]
* bumped client
0.9.10 / 2012-08-10
===================
* Don't lowercase log messages
* Always set the HTTP response in case an error should be returned to the client
* Create or destroy the flash policy server on configuration change
* Honour configuration to disable flash policy server
* Add express 3.0 instructions on Readme.md
* Bump client
0.9.9 / 2012-08-01
==================
* Initial release
* Fixed sync disconnect xhrs handling
* Put license text in its own file (#965)
* Add warning to .listen() to ease the migration to Express 3.x
* Restored compatibility with node 0.4.x
0.9.8 / 2012-07-24
==================
* Bumped client.
0.9.7 / 2012-07-24
==================
* Prevent crash when socket leaves a room twice.
* Corrects unsafe usage of for..in
* Fix for node 0.8 with `gzip compression` [vadimi]
* Update redis to support Node 0.8.x
* Made ID generation securely random
* Fix Redis Store race condition in manager onOpen unsubscribe callback
* Fix for EventEmitters always reusing the same Array instance for listeners
0.9.6 / 2012-04-17
==================
* Fixed XSS in jsonp-polling.
0.9.5 / 2012-04-05
==================
* Added test for polling and socket close.
* Ensure close upon request close.
* Fix disconnection reason being lost for polling transports.
* Ensure that polling transports work with Connection: close.
* Log disconnection reason.
0.9.4 / 2012-04-01
==================
* Disconnecting from namespace improvement (#795) [DanielBaulig]
* Bumped client with polling reconnection loop (#438)
0.9.3 / 2012-03-28
==================
* Fix "Syntax error" on FF Web Console with XHR Polling [mikito]
0.9.2 / 2012-03-13
==================
* More sensible close `timeout default` (fixes disconnect issue)
0.9.1-1 / 2012-03-02
====================
* Bumped client with NPM dependency fix.
0.9.1 / 2012-03-02
==================
* Changed heartbeat timeout and interval defaults (60 and 25 seconds)
* Make tests work both on 0.4 and 0.6
* Updated client (improvements + bug fixes).
0.9.0 / 2012-02-26
==================
* Make it possible to use a regexp to match the socket.io resource URL.
We need this because we have to prefix the socket.io URL with a variable ID.
* Supplemental fix to gavinuhma/authfix, it looks like the same Access-Control-Origin logic is needed in the http and xhr-polling transports
* Updated express dep for windows compatibility.
* Combine two substr calls into one in decodePayload to improve performance
* Minor documentation fix
* Minor. Conform to style of other files.
* Switching setting to 'match origin protocol'
* Revert "Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect()."
* Revert "Handle leaked dispatch:[id] subscription."
* Merge pull request #667 from dshaw/patch/redis-disconnect
* Handle leaked dispatch:[id] subscription.
* Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect().
* Prevent memory leaking on uncompleted requests & add max post size limitation
* Fix for testcase
* Set Access-Control-Allow-Credentials true, regardless of cookie
* Remove assertvarnish from package as it breaks on 0.6
* Correct irc channel
* Added proper return after reserved field error
* Fixes manager.js failure to close connection after transport error has happened
* Added implicit port 80 for origin checks. fixes #638
* Fixed bug #432 in 0.8.7
* Set Access-Control-Allow-Origin header to origin to enable withCredentials
* Adding configuration variable matchOriginProtocol
* Fixes location mismatch error in Safari.
* Use tty to detect if we should add colors or not by default.
* Updated the package location.
0.8.7 / 2011-11-05
==================
* Fixed memory leaks in closed clients.
* Fixed memory leaks in namespaces.
* Fixed websocket handling for malformed requests from proxies. [einaros]
* Node 0.6 compatibility. [einaros] [3rd-Eden]
* Adapted tests and examples.
0.8.6 / 2011-10-27
==================
* Added JSON decoding on jsonp-polling transport.
* Fixed README example.
* Major speed optimizations [3rd-Eden] [einaros] [visionmedia]
* Added decode/encode benchmarks [visionmedia]
* Added support for black-listing client sent events.
* Fixed logging options, closes #540 [3rd-Eden]
* Added vary header for gzip [3rd-Eden]
* Properly cleaned up async websocket / flashsocket tests, after patching node-websocket-client
* Patched to properly shut down when a finishClose call is made during connection establishment
* Added support for socket.io version on url and far-future Expires [3rd-Eden] [getify]
* Began IE10 compatibility [einaros] [tbranyen]
* Misc WebSocket fixes [einaros]
* Added UTF8 to respone headers for htmlfile [3rd-Eden]
0.8.5 / 2011-10-07
==================
* Added websocket draft HyBi-16 support. [einaros]
* Fixed websocket continuation bugs. [einaros]
* Fixed flashsocket transport name.
* Fixed websocket tests.
* Ensured `parser#decodePayload` doesn't choke.
* Added http referrer verification to manager verifyOrigin.
* Added access control for cross domain xhr handshakes [3rd-Eden]
* Added support for automatic generation of socket.io files [3rd-Eden]
* Added websocket binary support [einaros]
* Added gzip support for socket.io.js [3rd-Eden]
* Expose socket.transport [3rd-Eden]
* Updated client.
0.8.4 / 2011-09-06
==================
* Client build
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
==================
* Fixed general dispatching when a client has closed.
0.7.5 / 2011-06-30
==================
* Fixed dispatching to clients that are disconnected.
0.7.4 / 2011-06-30
==================
* Fixed; only clear handlers if they were set. [level09]
0.7.3 / 2011-06-30
==================
* Exposed handshake data to clients.
* Refactored dispatcher interface.
* Changed; Moved id generation method into the manager.
* Added sub-namespace authorization. [3rd-Eden]
* Changed; normalized SocketNamespace local eventing [dvv]
* Changed; Use packet.reason or default to 'packet' [3rd-Eden]
* Changed console.error to console.log.
* Fixed; bind both servers at the same time do that the test never times out.
* Added 304 support.
* Removed `Transport#name` for abstract interface.
* Changed; lazily require http and https module only when needed. [3rd-Eden]
0.7.2 / 2011-06-22
==================
* Make sure to write a packet (of type `noop`) when closing a poll.
This solves a problem with cross-domain requests being flagged as aborted and
reconnection being triggered.
* Added `noop` message type.
0.7.1 / 2011-06-21
==================
* Fixed cross-domain XHR.
* Added CORS test to xhr-polling suite.
0.7.0 / 2010-06-21
==================
* http://socket.io/announcement.html

22
LICENSE Normal file
View File

@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2011 Guillermo Rauch <guillermo@learnboost.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,19 +1,31 @@
ALL_TESTS = $(shell find test/ -name '*.test.js')
ALL_BENCH = $(shell find benchmarks -name '*.bench.js')
run-tests:
@npm link > /dev/null --local
@./node_modules/.bin/expresso \
-t 3000 \
-I support \
-I lib \
--serial \
$(TESTFLAGS) \
$(TESTS)
test:
@$(MAKE) TESTS="$(ALL_TESTS)" run-tests
@$(MAKE) NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests
test-cov:
@TESTFLAGS=--cov $(MAKE) test
.PHONY: test
test-leaks:
@ls test/leaks/* | xargs node --expose_debug_as=debug --expose_gc
run-bench:
@node $(PROFILEFLAGS) benchmarks/runner.js
bench:
@$(MAKE) BENCHMARKS="$(ALL_BENCH)" run-bench
profile:
@PROFILEFLAGS='--prof --trace-opt --trace-bailout --trace-deopt' $(MAKE) bench
.PHONY: test bench profile

View File

@@ -1,4 +1,3 @@
# Socket.IO
Socket.IO is a Node.JS project that makes WebSockets and realtime possible in
@@ -7,7 +6,9 @@ horizontal scalability, automatic JSON encoding/decoding, and more.
## How to Install
npm install socket.io
```bash
npm install socket.io
```
## How to use
@@ -20,8 +21,27 @@ var io = require('socket.io');
Next, attach it to a HTTP/HTTPS server. If you're using the fantastic `express`
web framework:
#### Express 3.x
```js
var app = express.createServer();
var app = express()
, server = require('http').createServer(app)
, io = io.listen(server);
server.listen(80);
io.sockets.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
```
#### Express 2.x
```js
var app = express.createServer()
, io = io.listen(app);
app.listen(80);
@@ -61,14 +81,14 @@ 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);
});
socket.on('disconnect', function () {
sockets.emit('user disconnected');
io.sockets.emit('user disconnected');
});
});
```
@@ -89,7 +109,7 @@ io.sockets.on('connection', function (socket) {
});
socket.on('msg', function () {
socket.get('nickname', function (name) {
socket.get('nickname', function (err, name) {
console.log('Chat message by ', name);
});
});
@@ -103,10 +123,10 @@ io.sockets.on('connection', function (socket) {
var socket = io.connect('http://localhost');
socket.on('connect', function () {
socket.emit('set nickname', confirm('What is your nickname?'));
socket.emit('set nickname', prompt('What is your nickname?'));
socket.on('ready', function () {
console.log('Connected !');
socket.emit('msg', confirm('What is your message?'));
socket.emit('msg', prompt('What is your message?'));
});
});
</script>
@@ -132,7 +152,7 @@ The following example defines a socket that listens on '/chat' and one for
var io = require('socket.io').listen(80);
var chat = io
.of('/chat');
.of('/chat')
.on('connection', function (socket) {
socket.emit('a message', { that: 'only', '/chat': 'will get' });
chat.emit('a message', { everyone: 'in', '/chat': 'will get' });
@@ -149,9 +169,8 @@ var news = io
```html
<script>
var socket = io.connect('http://localhost/')
, chat = socket.of('/chat')
, news = socket.of('/news');
var chat = io.connect('http://localhost/chat')
, news = io.connect('http://localhost/news');
chat.on('connect', function () {
chat.emit('hi!');
@@ -224,7 +243,7 @@ io.sockets.on('connection', function (socket) {
```html
<script>
var socket = io.connect(); // TIP: .connect with no args does auto-discovery
socket.on('connection', function () {
socket.on('connect', function () { // TIP: you can avoid listening on `connect` and listen on events directly too!
socket.emit('ferret', 'tobi', function (data) {
console.log(data); // data will be 'woot'
});
@@ -277,7 +296,7 @@ Simply leverage `send` and listen on the `message` event:
#### Server side
```js
var io = require('socket.io-node').listen(80);
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
socket.on('message', function () { });
@@ -307,7 +326,7 @@ Configuration in socket.io is TJ-style:
#### Server side
```js
var io = require('socket.io-node').listen(80);
var io = require('socket.io').listen(80);
io.configure(function () {
io.set('transports', ['websocket', 'flashsocket', 'xhr-polling']);

View File

@@ -0,0 +1,64 @@
/**
* Module dependencies.
*/
var benchmark = require('benchmark')
, colors = require('colors')
, io = require('../')
, parser = io.parser
, suite = new benchmark.Suite('Decode packet');
suite.add('string', function () {
parser.decodePacket('4:::"2"');
});
suite.add('event', function () {
parser.decodePacket('5:::{"name":"woot"}');
});
suite.add('event+ack', function () {
parser.decodePacket('5:1+::{"name":"tobi"}');
});
suite.add('event+data', function () {
parser.decodePacket('5:::{"name":"edwald","args":[{"a": "b"},2,"3"]}');
});
suite.add('heartbeat', function () {
parser.decodePacket('2:::');
});
suite.add('error', function () {
parser.decodePacket('7:::2+0');
});
var payload = parser.encodePayload([
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
]);
suite.add('payload', function () {
parser.decodePayload(payload);
});
suite.on('cycle', function (bench, details) {
console.log('\n' + suite.name.grey, details.name.white.bold);
console.log([
details.hz.toFixed(2).cyan + ' ops/sec'.grey
, details.count.toString().white + ' times executed'.grey
, 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey
,
].join(', '.grey));
});
if (!module.parent) {
suite.run();
} else {
module.exports = suite;
}

View File

@@ -0,0 +1,90 @@
/**
* Module dependencies.
*/
var benchmark = require('benchmark')
, colors = require('colors')
, io = require('../')
, parser = io.parser
, suite = new benchmark.Suite('Encode packet');
suite.add('string', function () {
parser.encodePacket({
type: 'json'
, endpoint: ''
, data: '2'
});
});
suite.add('event', function () {
parser.encodePacket({
type: 'event'
, name: 'woot'
, endpoint: ''
, args: []
});
});
suite.add('event+ack', function () {
parser.encodePacket({
type: 'json'
, id: 1
, ack: 'data'
, endpoint: ''
, data: { a: 'b' }
});
});
suite.add('event+data', function () {
parser.encodePacket({
type: 'event'
, name: 'edwald'
, endpoint: ''
, args: [{a: 'b'}, 2, '3']
});
});
suite.add('heartbeat', function () {
parser.encodePacket({
type: 'heartbeat'
, endpoint: ''
})
});
suite.add('error', function () {
parser.encodePacket({
type: 'error'
, reason: 'unauthorized'
, advice: 'reconnect'
, endpoint: ''
})
})
suite.add('payload', function () {
parser.encodePayload([
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
]);
});
suite.on('cycle', function (bench, details) {
console.log('\n' + suite.name.grey, details.name.white.bold);
console.log([
details.hz.toFixed(2).cyan + ' ops/sec'.grey
, details.count.toString().white + ' times executed'.grey
, 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey
,
].join(', '.grey));
});
if (!module.parent) {
suite.run();
} else {
module.exports = suite;
}

55
benchmarks/runner.js Normal file
View File

@@ -0,0 +1,55 @@
/**
* Benchmark runner dependencies
*/
var colors = require('colors')
, path = require('path');
/**
* Find all the benchmarks
*/
var benchmarks_files = process.env.BENCHMARKS.split(' ')
, all = [].concat(benchmarks_files)
, first = all.shift()
, benchmarks = {};
// find the benchmarks and load them all in our obj
benchmarks_files.forEach(function (file) {
benchmarks[file] = require(path.join(__dirname, '..', file));
});
// setup the complete listeners
benchmarks_files.forEach(function (file) {
var benchmark = benchmarks[file]
, next_file = all.shift()
, next = benchmarks[next_file];
/**
* Generate a oncomplete function for the tests, either we are done or we
* have more benchmarks to process.
*/
function complete () {
if (!next) {
console.log(
'\n\nBenchmark completed in'.grey
, (Date.now() - start).toString().green + ' ms'.grey
);
} else {
console.log('\nStarting benchmark '.grey + next_file.yellow);
next.run();
}
}
// attach the listener
benchmark.on('complete', complete);
});
/**
* Start the benchmark
*/
var start = Date.now();
console.log('Starting benchmark '.grey + first.yellow);
benchmarks[first].run();

View File

@@ -1,10 +1,3 @@
/**
* Bootstrap app.
*/
require.paths.unshift(__dirname + '/../../lib/');
/**
* Module dependencies.
*/
@@ -12,7 +5,7 @@ require.paths.unshift(__dirname + '/../../lib/');
var express = require('express')
, stylus = require('stylus')
, nib = require('nib')
, sio = require('socket.io');
, sio = require('../../lib/socket.io');
/**
* App.
@@ -25,7 +18,7 @@ var app = express.createServer();
*/
app.configure(function () {
app.use(stylus.middleware({ src: __dirname + '/public', compile: compile }))
app.use(stylus.middleware({ src: __dirname + '/public', compile: compile }));
app.use(express.static(__dirname + '/public'));
app.set('views', __dirname);
app.set('view engine', 'jade');
@@ -61,14 +54,6 @@ app.listen(3000, function () {
var io = sio.listen(app)
, nicknames = {};
io.set('transports', [
'websocket'
, 'flashsocket'
, 'htmlfile'
, 'xhr-polling'
, 'jsonp-polling'
]);
io.sockets.on('connection', function (socket) {
socket.on('user message', function (msg) {
socket.broadcast.emit('user message', socket.nickname, msg);

View File

@@ -3,9 +3,9 @@
, "description": "example chat application with socket.io"
, "version": "0.0.1"
, "dependencies": {
"express": "2.3.11"
, "jade": "0.12.1"
, "stylus": "0.13.3"
, "nib": "0.0.8"
"express": "2.5.5"
, "jade": "0.16.4"
, "stylus": "0.19.0"
, "nib": "0.2.0"
}
}

View File

@@ -1,10 +1,3 @@
/**
* Bootstrap app.
*/
require.paths.unshift(__dirname + '/../../lib/');
/**
* Module dependencies.
*/
@@ -12,7 +5,7 @@ require.paths.unshift(__dirname + '/../../lib/');
var express = require('express')
, stylus = require('stylus')
, nib = require('nib')
, sio = require('socket.io')
, sio = require('../../lib/socket.io')
, irc = require('./irc');
/**

View File

@@ -2,7 +2,7 @@
* From https://github.com/felixge/nodelog/
*/
var sys = require('sys');
var sys = require('util');
var tcp = require('net');
var irc = exports;
@@ -39,7 +39,7 @@ var Client = irc.Client = function(host, port) {
this.user = null;
this.real = null;
}
sys.inherits(Client, process.EventEmitter);
sys.inherits(Client, require('events').EventEmitter);
Client.prototype.connect = function(nick, user, real) {
var connection = tcp.createConnection(this.port, this.host);

View File

@@ -2,9 +2,9 @@
"name": "socket.io-irc"
, "version": "0.0.1"
, "dependencies": {
"express": "2.3.11"
, "jade": "0.12.1"
, "stylus": "0.13.3"
, "nib": "0.0.8"
"express": "2.5.5"
, "jade": "0.16.4"
, "stylus": "0.19.0"
, "nib": "0.2.0"
}
}

View File

@@ -60,6 +60,7 @@ var Logger = module.exports = function (opts) {
opts = opts || {}
this.colors = false !== opts.colors;
this.level = 3;
this.enabled = true;
};
/**
@@ -71,10 +72,10 @@ var Logger = module.exports = function (opts) {
Logger.prototype.log = function (type) {
var index = levels.indexOf(type);
if (index > this.level)
if (index > this.level || !this.enabled)
return this;
console.error.apply(
console.log.apply(
console
, [this.colors
? ' \033[' + colors[index] + 'm' + pad(type) + ' -\033[39m'

View File

@@ -1,4 +1,3 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
@@ -9,10 +8,10 @@
* Module dependencies.
*/
var http = require('http')
, https = require('https')
, fs = require('fs')
var fs = require('fs')
, url = require('url')
, tty = require('tty')
, crypto = require('crypto')
, util = require('./util')
, store = require('./store')
, client = require('socket.io-client')
@@ -21,7 +20,8 @@ var http = require('http')
, Socket = require('./socket')
, MemoryStore = require('./stores/memory')
, SocketNamespace = require('./namespace')
, EventEmitter = process.EventEmitter;
, Static = require('./static')
, EventEmitter = require('events').EventEmitter;
/**
* Export the constructor.
@@ -45,7 +45,8 @@ var defaultTransports = exports.defaultTransports = [
*/
var parent = module.parent.exports
, protocol = parent.protocol;
, protocol = parent.protocol
, jsonpolling_re = /^\d+$/;
/**
* Manager constructor.
@@ -55,7 +56,7 @@ var parent = module.parent.exports
* @api public
*/
function Manager (server) {
function Manager (server, options) {
this.server = server;
this.namespaces = {};
this.sockets = this.of('');
@@ -64,29 +65,56 @@ function Manager (server) {
, log: true
, store: new MemoryStore
, logger: new Logger
, static: new Static(this)
, heartbeats: true
, resource: '/socket.io'
, transports: defaultTransports
, authorization: false
, blacklist: ['disconnect']
, 'log level': 3
, 'close timeout': 25
, 'heartbeat timeout': 15
, 'heartbeat interval': 20
, 'log colors': tty.isatty(process.stdout.fd)
, 'close timeout': 60
, 'heartbeat interval': 25
, 'heartbeat timeout': 60
, 'polling duration': 20
, 'flash policy server': true
, 'flash policy port': 843
, 'flash policy port': 10843
, 'destroy upgrade': true
, 'destroy buffer size': 10E7
, 'browser client': true
, 'browser client cache': true
, 'browser client minification': false
, 'browser client etag': false
, 'browser client expires': 315360000
, 'browser client gzip': false
, 'browser client handler': false
, 'client store expiration': 15
, 'match origin protocol': false
};
// reset listeners
this.oldListeners = server.listeners('request');
server.removeAllListeners('request');
for (var i in options) {
if (options.hasOwnProperty(i)) {
this.settings[i] = options[i];
}
}
var self = this;
// default error handler
server.on('error', function(err) {
self.log.warn('error raised: ' + err);
});
this.initStore();
this.on('set:store', function() {
self.initStore();
});
// reset listeners
this.oldListeners = server.listeners('request').splice(0);
server.removeAllListeners('request');
server.on('request', function (req, res) {
self.handleRequest(req, res);
});
@@ -95,12 +123,30 @@ 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);
if (transports.hasOwnProperty(i)) {
if (transports[i].init) {
transports[i].init(this);
}
}
}
// forward-compatibility with 1.0
var self = this;
this.sockets.on('connection', function (conn) {
self.emit('connection', conn);
});
this.sequenceNumber = Date.now() | 0;
this.log.info('socket.io started');
};
@@ -125,14 +171,25 @@ Manager.prototype.__defineGetter__('store', function () {
*/
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;
logger.colors = this.get('log colors');
logger.enabled = this.enabled('log');
return logger;
});
/**
* Static accessor.
*
* @api public
*/
Manager.prototype.__defineGetter__('static', function () {
return this.get('static');
});
/**
* Get settings.
*
@@ -209,13 +266,314 @@ Manager.prototype.disabled = function (key) {
Manager.prototype.configure = function (env, fn) {
if ('function' == typeof env) {
env.call(this);
} else if (env == process.env.NODE_ENV) {
} else if (env == (process.env.NODE_ENV || 'development')) {
fn.call(this);
}
return this;
};
/**
* Initializes everything related to the message dispatcher.
*
* @api private
*/
Manager.prototype.initStore = function () {
this.handshaken = {};
this.connected = {};
this.open = {};
this.closed = {};
this.rooms = {};
this.roomClients = {};
var self = this;
this.store.subscribe('handshake', function (id, data) {
self.onHandshake(id, data);
});
this.store.subscribe('connect', function (id) {
self.onConnect(id);
});
this.store.subscribe('open', function (id) {
self.onOpen(id);
});
this.store.subscribe('join', function (id, room) {
self.onJoin(id, room);
});
this.store.subscribe('leave', function (id, room) {
self.onLeave(id, room);
});
this.store.subscribe('close', function (id) {
self.onClose(id);
});
this.store.subscribe('dispatch', function (room, packet, volatile, exceptions) {
self.onDispatch(room, packet, volatile, exceptions);
});
this.store.subscribe('disconnect', function (id) {
self.onDisconnect(id);
});
// we need to do this in a pub/sub way since the client can POST the message
// over a different socket (ie: different Transport instance)
//use persistent channel for these, don't add and remove 5 channels for every connection
//eg. for 10,000 concurrent users this creates 50,000 channels in redis, which kind of slows things down
//we only need 5 (extra) total channels at all times
this.store.subscribe('message-remote',function (id, packet) {
self.onClientMessage(id, packet);
});
this.store.subscribe('disconnect-remote', function (id, reason) {
self.onClientDisconnect(id, reason);
});
this.store.subscribe('dispatch-remote', function (id, packet, volatile) {
var transport = self.transports[id];
if (transport) {
transport.onDispatch(packet, volatile);
}
if (!volatile) {
self.onClientDispatch(id, packet);
}
});
this.store.subscribe('heartbeat-clear', function (id) {
var transport = self.transports[id];
if (transport) {
transport.onHeartbeatClear();
}
});
this.store.subscribe('disconnect-force', function (id) {
var transport = self.transports[id];
if (transport) {
transport.onForcedDisconnect();
}
});
};
/**
* Called when a client handshakes.
*
* @param text
*/
Manager.prototype.onHandshake = function (id, data) {
this.handshaken[id] = data;
};
/**
* Called when a client connects (ie: transport first opens)
*
* @api private
*/
Manager.prototype.onConnect = function (id) {
this.connected[id] = true;
};
/**
* Called when a client opens a request in a different node.
*
* @api private
*/
Manager.prototype.onOpen = function (id) {
this.open[id] = true;
if (this.closed[id]) {
var self = this;
var transport = self.transports[id];
if (self.closed[id] && self.closed[id].length && transport) {
// if we have buffered messages that accumulate between calling
// onOpen an this async callback, send them if the transport is
// still open, otherwise leave them buffered
if (transport.open) {
transport.payload(self.closed[id]);
self.closed[id] = [];
}
}
}
// clear the current transport
if (this.transports[id]) {
this.transports[id].discard();
this.transports[id] = null;
}
};
/**
* Called when a message is sent to a namespace and/or room.
*
* @api private
*/
Manager.prototype.onDispatch = function (room, packet, volatile, exceptions) {
if (this.rooms[room]) {
for (var i = 0, l = this.rooms[room].length; i < l; i++) {
var id = this.rooms[room][i];
if (!~exceptions.indexOf(id)) {
if (this.transports[id] && this.transports[id].open) {
this.transports[id].onDispatch(packet, volatile);
} else if (!volatile) {
this.onClientDispatch(id, packet);
}
}
}
}
};
/**
* Called when a client joins a nsp / room.
*
* @api private
*/
Manager.prototype.onJoin = function (id, name) {
if (!this.roomClients[id]) {
this.roomClients[id] = {};
}
if (!this.rooms[name]) {
this.rooms[name] = [];
}
if (!~this.rooms[name].indexOf(id)) {
this.rooms[name].push(id);
this.roomClients[id][name] = true;
}
};
/**
* Called when a client leaves a nsp / room.
*
* @param private
*/
Manager.prototype.onLeave = function (id, room) {
if (this.rooms[room]) {
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];
}
if (this.roomClients[id]) {
delete this.roomClients[id][room];
}
}
};
/**
* Called when a client closes a request in different node.
*
* @api private
*/
Manager.prototype.onClose = function (id) {
if (this.open[id]) {
delete this.open[id];
}
this.closed[id] = [];
var self = this;
};
/**
* Dispatches a message for a closed client.
*
* @api private
*/
Manager.prototype.onClientDispatch = function (id, packet) {
if (this.closed[id]) {
this.closed[id].push(packet);
}
};
/**
* Receives a message for a client.
*
* @api private
*/
Manager.prototype.onClientMessage = function (id, packet) {
if (this.namespaces[packet.endpoint]) {
this.namespaces[packet.endpoint].handlePacket(id, packet);
}
};
/**
* Fired when a client disconnects (not triggered).
*
* @api private
*/
Manager.prototype.onClientDisconnect = function (id, reason) {
for (var name in this.namespaces) {
if (this.namespaces.hasOwnProperty(name)) {
this.namespaces[name].handleDisconnect(id, reason, typeof this.roomClients[id] !== 'undefined' &&
typeof this.roomClients[id][name] !== 'undefined');
}
}
this.onDisconnect(id);
};
/**
* Called when a client disconnects.
*
* @param text
*/
Manager.prototype.onDisconnect = function (id) {
delete this.handshaken[id];
if (this.open[id]) {
delete this.open[id];
}
if (this.connected[id]) {
delete this.connected[id];
}
if (this.transports[id]) {
this.transports[id].discard();
delete this.transports[id];
}
if (this.closed[id]) {
delete this.closed[id];
}
if (this.roomClients[id]) {
for (var room in this.roomClients[id]) {
if (this.roomClients[id].hasOwnProperty(room)) {
this.onLeave(id, room);
}
}
delete this.roomClients[id]
}
this.store.destroyClient(id, this.get('client store expiration'));
};
/**
* Handles an HTTP request.
*
@@ -235,7 +593,7 @@ Manager.prototype.handleRequest = function (req, res) {
if (data.static || !data.transport && !data.protocol) {
if (data.static && this.enabled('browser client')) {
this.handleClientRequest(req, res, data);
this.static.write(data.path, req, res);
} else {
res.writeHead(200);
res.end('Welcome to socket.io.');
@@ -281,6 +639,7 @@ Manager.prototype.handleUpgrade = function (req, socket, head) {
req.head = head;
this.handleClient(data, req);
req.head = null;
};
/**
@@ -302,12 +661,18 @@ Manager.prototype.handleHTTPRequest = function (data, req, res) {
Manager.prototype.handleClient = function (data, req) {
var socket = req.socket
, newTransport = false
, store = this.store
, self = this;
// handle sync disconnect xhrs
if (undefined != data.query.disconnect) {
self.log.debug('handling disconnection url');
self.store.disconnect(data.id, true);
if (this.transports[data.id] && this.transports[data.id].open) {
this.transports[data.id].onForcedDisconnect();
} else {
this.store.publish('disconnect-force', data.id);
}
req.res.writeHead(200);
req.res.end();
return;
}
@@ -317,124 +682,79 @@ Manager.prototype.handleClient = function (data, req) {
return;
}
var transport = new transports[data.transport](this, data);
transport.pause();
transport.request = req;
var transport = new transports[data.transport](this, data, req)
, handshaken = this.handshaken[data.id];
if (!transport.open) {
this.log.debug('transport not writeable, not subscribing');
if (transport.disconnected) {
// failed during transport setup
req.connection.end();
return;
}
if (handshaken) {
if (transport.open) {
if (this.closed[data.id] && this.closed[data.id].length) {
transport.payload(this.closed[data.id]);
this.closed[data.id] = [];
}
this.store.isHandshaken(data.id, function (err, handshaken) {
if (err || !handshaken) {
if (err) console.error(err);
transport.error('client not handshaken', 'reconnect');
return;
this.onOpen(data.id);
this.store.publish('open', data.id);
this.transports[data.id] = transport;
}
self.store.client(data.id).count(function (err, count) {
transport.resume();
if (!this.connected[data.id]) {
this.onConnect(data.id);
this.store.publish('connect', data.id);
if (count == 1) {
// initialize the socket for all namespaces
for (var i in self.namespaces) {
var socket = self.namespaces[i].socket(data.id, true);
// 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) {
if (this.namespaces.hasOwnProperty(i)) {
var socket = this.namespaces[i].socket(data.id, true);
// echo back connect packet and fire connection event
if (i === '') {
self.namespaces[i].handlePacket(data.id, { type: 'connect' });
this.namespaces[i].handlePacket(data.id, { type: 'connect' });
}
}
// handle packets for the client (all namespaces)
self.store.on('message:' + data.id, function (packet) {
self.handlePacket(data.id, packet);
});
}
});
});
};
}
} else {
if (transport.open) {
transport.error('client not handshaken', 'reconnect');
}
/**
* Dictionary for static file serving
*
* @api public
*/
Manager.static = {
cache:{}
, paths: {
'/static/flashsocket/WebSocketMain.swf': client.dist + '/WebSocketMain.swf'
, '/static/flashsocket/WebSocketMainInsecure.swf': client.dist + '/WebSocketMainInsecure.swf'
, '/socket.io.js': client.dist + '/socket.io.js'
, '/socket.io.js.min': client.dist + '/socket.io.min.js'
}
, contentType: {
'js': 'application/javascript'
, 'swf': 'application/x-shockwave-flash'
}
, encoding:{
'js': 'utf8'
, 'swf': 'binary'
transport.discard();
}
};
/**
* Serves the client.
* Generates a session id.
*
* @api private
*/
Manager.prototype.handleClientRequest = function (req, res, data) {
var static = Manager.static
, extension = data.path.split('.').pop()
, file = data.path + (this.enabled('browser client minification') && extension == 'js' ? '.min' : '')
, location = static.paths[file]
, cache = static.cache[file];
var self = this;
function serve () {
var headers = {
'Content-Type': static.contentType[extension]
, 'Content-Length': cache.length
};
if (self.enabled('browser client etag') && cache.Etag) {
headers.Etag = cache.Etag;
}
res.writeHead(200, headers);
res.end(cache.content, cache.encoding);
self.log.debug('served static ' + data.path);
Manager.prototype.generateId = function () {
var rand = new Buffer(15); // multiple of 3 for base64
if (!rand.writeInt32BE) {
return Math.abs(Math.random() * Math.random() * Date.now() | 0).toString()
+ Math.abs(Math.random() * Math.random() * Date.now() | 0).toString();
}
if (this.get('browser client handler')) {
this.get('browser client handler').call(this, req, res);
} else if (!cache) {
fs.readFile(location, function (err, data) {
if (err) {
res.writeHead(500);
res.end('Error serving socket.io client.');
self.log.warn('Can\'t cache socket.io client, ' + err.message);
return;
}
cache = Manager.static.cache[file] = {
content: data
, length: data.length
, Etag: client.version
, encoding: static.encoding[extension]
};
serve();
});
this.sequenceNumber = (this.sequenceNumber + 1) | 0;
rand.writeInt32BE(this.sequenceNumber, 11);
if (crypto.randomBytes) {
crypto.randomBytes(12).copy(rand);
} else {
serve();
// not secure for node 0.4
[0, 4, 8].forEach(function(i) {
rand.writeInt32BE(Math.random() * Math.pow(2, 32) | 0, i);
});
}
return rand.toString('base64').replace(/\//g, '_').replace(/\+/g, '-');
};
/**
@@ -444,14 +764,18 @@ Manager.prototype.handleClientRequest = function (req, res, data) {
*/
Manager.prototype.handleHandshake = function (data, req, res) {
var self = this;
var self = this
, origin = req.headers.origin
, headers = {
'Content-Type': 'text/plain'
};
function writeErr (status, message) {
if (data.query.jsonp) {
if (data.query.jsonp && jsonpolling_re.test(data.query.jsonp)) {
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end('io.j[' + data.query.jsonp + '](new Error("' + message + '"));');
} else {
res.writeHead(status);
res.writeHead(status, headers);
res.end(message);
}
};
@@ -466,38 +790,81 @@ Manager.prototype.handleHandshake = function (data, req, res) {
return;
}
this.authorize(data, function (err, authorized) {
var handshakeData = this.handshakeData(data);
if (origin) {
// https://developer.mozilla.org/En/HTTP_Access_Control
headers['Access-Control-Allow-Origin'] = origin;
headers['Access-Control-Allow-Credentials'] = 'true';
}
this.authorize(handshakeData, function (err, authorized, newData) {
if (err) return error(err);
self.log.info('handshake ' + (authorized ? 'authorized' : 'unauthorized'));
if (authorized) {
self.store.handshake(data, function (err, id) {
if (err) return error(err);
var id = self.generateId()
, hs = [
id
, self.enabled('heartbeats') ? self.get('heartbeat timeout') || '' : ''
, self.get('close timeout') || ''
, self.transports(data).join(',')
].join(':');
var hs = [
id
, self.get('heartbeat timeout') || ''
, self.get('close timeout') || ''
, self.transports(data).join(',')
].join(':');
if (data.query.jsonp && jsonpolling_re.test(data.query.jsonp)) {
hs = 'io.j[' + data.query.jsonp + '](' + JSON.stringify(hs) + ');';
res.writeHead(200, { 'Content-Type': 'application/javascript' });
} else {
res.writeHead(200, headers);
}
if (data.query.jsonp) {
hs = 'io.j[' + data.query.jsonp + '](' + JSON.stringify(hs) + ');';
res.writeHead(200, { 'Content-Type': 'application/javascript' });
} else {
res.writeHead(200);
}
self.onHandshake(id, newData || handshakeData);
self.store.publish('handshake', id, newData || handshakeData);
res.end(hs);
self.log.info('handshaken', id);
});
res.end(hs);
self.log.info('handshake authorized', id);
} else {
writeErr(403, 'handshake unauthorized');
self.log.info('handshake unauthorized');
}
})
};
/**
* Gets normalized handshake data
*
* @api private
*/
Manager.prototype.handshakeData = function (data) {
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: date.toString()
, query: data.query
, url: data.request.url
, xdomain: !!data.request.headers.origin
, secure: data.request.connection.secure
, issued: +date
};
};
/**
* Verifies the origin of a request.
*
@@ -505,7 +872,7 @@ Manager.prototype.handleHandshake = function (data, req, res) {
*/
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 = '*';
@@ -517,14 +884,20 @@ Manager.prototype.verifyOrigin = function (request) {
if (origin) {
try {
var parts = url.parse(origin);
return
~origins.indexOf(parts.host + ':' + parts.port) ||
~origins.indexOf(parts.host + ':*') ||
parts.port = parts.port || 80;
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;
};
@@ -596,8 +969,17 @@ var regexp = /^\/([^\/]+)\/?([^\/]+)?\/?([^\/]+)?\/?$/
Manager.prototype.checkRequest = function (req) {
var resource = this.get('resource');
if (req.url.substr(0, resource.length) == resource) {
var uri = url.parse(req.url.substr(resource.length), true)
var match;
if (typeof resource === 'string') {
match = req.url.substr(0, resource.length);
if (match !== resource) match = null;
} else {
match = resource.exec(req.url);
if (match) match = match[0];
}
if (match) {
var uri = url.parse(req.url.substr(match.length), true)
, path = uri.pathname || ''
, pieces = path.match(regexp);
@@ -613,7 +995,7 @@ Manager.prototype.checkRequest = function (req) {
data.protocol = Number(pieces[1]);
data.transport = pieces[2];
data.id = pieces[3];
data.static = !!Manager.static.paths[path];
data.static = !!this.static.has(path);
};
return data;
@@ -624,6 +1006,8 @@ Manager.prototype.checkRequest = function (req) {
/**
* Declares a socket namespace
*
* @api public
*/
Manager.prototype.of = function (nsp) {
@@ -633,3 +1017,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

@@ -1,10 +1,9 @@
/**
* Module dependencies.
*/
var Socket = require('./socket')
, EventEmitter = process.EventEmitter
, EventEmitter = require('events').EventEmitter
, parser = require('./parser')
, util = require('./util');
@@ -24,6 +23,7 @@ function SocketNamespace (mgr, name) {
this.manager = mgr;
this.name = name || '';
this.sockets = {};
this.auth = false;
this.setFlags();
};
@@ -33,6 +33,33 @@ function SocketNamespace (mgr, name) {
SocketNamespace.prototype.__proto__ = EventEmitter.prototype;
/**
* Copies emit since we override it.
*
* @api private
*/
SocketNamespace.prototype.$emit = EventEmitter.prototype.emit;
/**
* Retrieves all clients as Socket instances as an array.
*
* @api public
*/
SocketNamespace.prototype.clients = function (room) {
var room = this.name + (room !== undefined ?
'/' + room : '');
if (!this.manager.rooms[room]) {
return [];
}
return this.manager.rooms[room].map(function (id) {
return this.socket(id);
}, this);
};
/**
* Access logger interface.
*
@@ -76,18 +103,18 @@ SocketNamespace.prototype.__defineGetter__('volatile', function () {
});
/**
* Overrides the room to relay messages to (flag)
* Overrides the room to relay messages to (flag).
*
* @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;
};
/**
* Adds a session id we should prevent relaying messages to (flag)
* Adds a session id we should prevent relaying messages to (flag).
*
* @api public
*/
@@ -112,7 +139,7 @@ SocketNamespace.prototype.setFlags = function () {
};
/**
* Sends out a packet
* Sends out a packet.
*
* @api private
*/
@@ -126,20 +153,8 @@ SocketNamespace.prototype.packet = function (packet) {
, exceptions = this.flags.exceptions
, packet = parser.encodePacket(packet);
store.clients(this.flags.endpoint, function (clients) {
clients.forEach(function (id) {
if (~exceptions.indexOf(id)) {
log.debug('ignoring packet to ', id);
return;
}
if (volatile) {
store.publish('volatile:' + id, packet);
} else {
store.client(id).publish(packet);
}
});
});
this.manager.onDispatch(this.flags.endpoint, packet, volatile, exceptions);
this.store.publish('dispatch', this.flags.endpoint, packet, volatile, exceptions);
this.setFlags();
@@ -160,14 +175,14 @@ SocketNamespace.prototype.send = function (data) {
};
/**
* Emits to everyone (override)
* Emits to everyone (override).
*
* @api private
* @api public
*/
SocketNamespace.prototype.emit = function (name) {
if (name == 'connection' || name == 'newListener') {
return EventEmitter.prototype.emit.apply(this, arguments);
if (name == 'newListener') {
return this.$emit.apply(this, arguments);
}
return this.packet({
@@ -181,7 +196,7 @@ SocketNamespace.prototype.emit = function (name) {
* Retrieves or creates a write-only socket for a client, unless specified.
*
* @param {Boolean} whether the socket will be readable when initialized
* @api private
* @api public
*/
SocketNamespace.prototype.socket = function (sid, readable) {
@@ -192,6 +207,54 @@ SocketNamespace.prototype.socket = function (sid, readable) {
return this.sockets[sid];
};
/**
* Sets authorization for this namespace.
*
* @api public
*/
SocketNamespace.prototype.authorization = function (fn) {
this.auth = fn;
return this;
};
/**
* Called when a socket disconnects entirely.
*
* @api private
*/
SocketNamespace.prototype.handleDisconnect = function (sid, reason, raiseOnDisconnect) {
if (this.sockets[sid] && this.sockets[sid].readable) {
if (raiseOnDisconnect) this.sockets[sid].onDisconnect(reason);
delete this.sockets[sid];
}
};
/**
* Performs authentication.
*
* @param Object client request data
* @api private
*/
SocketNamespace.prototype.authorize = function (data, fn) {
if (this.auth) {
var self = this;
this.auth.call(this, data, function (err, authorized) {
self.log.debug('client ' +
(authorized ? '' : 'un') + 'authorized for ' + self.name);
fn(err, authorized);
});
} else {
this.log.debug('client authorized for ' + this.name);
fn(null, true);
}
return this;
};
/**
* Handles a packet.
*
@@ -201,6 +264,7 @@ SocketNamespace.prototype.socket = function (sid, readable) {
SocketNamespace.prototype.handlePacket = function (sessid, packet) {
var socket = this.socket(sessid)
, dataAck = packet.ack == 'data'
, manager = this.manager
, self = this;
function ack () {
@@ -212,13 +276,41 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
});
};
function error (err) {
self.log.warn('handshake error ' + err + ' for ' + self.name);
socket.packet({ type: 'error', reason: err });
};
function connect () {
self.manager.onJoin(sessid, self.name);
self.store.publish('join', sessid, self.name);
// packet echo
socket.packet({ type: 'connect' });
// emit connection event
self.$emit('connection', socket);
};
switch (packet.type) {
case 'connect':
this.store.join(sessid, this.name, function () {
// packet echo
socket.packet({ type: 'connect' });
self.emit('connection', socket);
});
if (packet.endpoint == '') {
connect();
} else {
var handshakeData = manager.handshaken[sessid];
this.authorize(handshakeData, function (err, authorized, newData) {
if (err) return error(err);
if (authorized) {
manager.onHandshake(sessid, newData || handshakeData);
self.store.publish('handshake', sessid, newData || handshakeData);
connect();
} else {
error('unauthorized');
}
});
}
break;
case 'ack':
@@ -230,16 +322,25 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
break;
case 'event':
var params = [packet.name].concat(packet.args);
// check if the emitted event is not blacklisted
if (-~manager.get('blacklist').indexOf(packet.name)) {
this.log.debug('ignoring blacklisted event `' + packet.name + '`');
} else {
var params = [packet.name].concat(packet.args);
if (dataAck)
params.push(ack);
if (dataAck) {
params.push(ack);
}
socket.$emit.apply(socket, params);
socket.$emit.apply(socket, params);
}
break;
case 'disconnect':
socket.emit('disconnect');
this.manager.onLeave(sessid, this.name);
this.store.publish('leave', sessid, this.name);
socket.$emit('disconnect', packet.reason || 'packet');
break;
case 'json':
@@ -249,6 +350,6 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
if (dataAck)
params.push(ack);
socket.emit.apply(socket, params);
socket.$emit.apply(socket, params);
};
};

View File

@@ -13,34 +13,38 @@
* Packet types.
*/
var packets = exports.packets = [
'disconnect'
, 'connect'
, 'heartbeat'
, 'message'
, 'json'
, 'event'
, 'ack'
, 'error'
];
var packets = exports.packets = {
'disconnect': 0
, 'connect': 1
, 'heartbeat': 2
, 'message': 3
, 'json': 4
, 'event': 5
, 'ack': 6
, 'error': 7
, 'noop': 8
}
, packetslist = Object.keys(packets);
/**
* Errors reasons.
*/
var reasons = exports.reasons = [
'transport not supported'
, 'client not handshaken'
, 'unauthorized'
];
var reasons = exports.reasons = {
'transport not supported': 0
, 'client not handshaken': 1
, 'unauthorized': 2
}
, reasonslist = Object.keys(reasons);
/**
* Errors advice.
*/
var advice = exports.advice = [
'reconnect'
];
var advice = exports.advice = {
'reconnect': 0
}
, advicelist = Object.keys(advice);
/**
* Encodes a packet.
@@ -49,22 +53,13 @@ var advice = exports.advice = [
*/
exports.encodePacket = function (packet) {
var type = packets.indexOf(packet.type)
var type = packets[packet.type]
, id = packet.id || ''
, endpoint = packet.endpoint || ''
, ack = packet.ack
, data = null;
switch (packet.type) {
case 'error':
var reason = packet.reason ? reasons.indexOf(packet.reason) : ''
, adv = packet.advice ? advice.indexOf(packet.advice) : ''
if (reason !== '' || adv !== '')
data = reason + (adv !== '' ? ('+' + adv) : '')
break;
case 'message':
if (packet.data !== '')
data = packet.data;
@@ -84,30 +79,35 @@ exports.encodePacket = function (packet) {
data = JSON.stringify(packet.data);
break;
case 'connect':
if (packet.qs)
data = packet.qs;
break;
case 'ack':
data = packet.ackId
+ (packet.args && packet.args.length
? '+' + JSON.stringify(packet.args) : '');
break;
case 'connect':
if (packet.qs)
data = packet.qs;
break;
case 'error':
var reason = packet.reason ? reasons[packet.reason] : ''
, adv = packet.advice ? advice[packet.advice] : ''
if (reason !== '' || adv !== '')
data = reason + (adv !== '' ? ('+' + adv) : '')
break;
}
// construct packet with required fragments
var encoded = [
type
, id + (ack == 'data' ? '+' : '')
, endpoint
];
var encoded = type + ':' + id + (ack == 'data' ? '+' : '') + ':' + endpoint;
// data fragment is optional
if (data !== null && data !== undefined)
encoded.push(data);
encoded += ':' + data;
return encoded.join(':');
return encoded;
};
/**
@@ -137,7 +137,19 @@ exports.encodePayload = function (packets) {
* @api private
*/
var regexp = /^([^:]+):([0-9]+)?(\+)?:([^:]+)?:?(.*)?$/;
var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;
/**
* Wrap the JSON.parse in a seperate function the crankshaft optimizer will
* only punish this function for the usage for try catch
*
* @api private
*/
function parse (data) {
try { return JSON.parse(data) }
catch (e) { return false }
}
exports.decodePacket = function (data) {
var pieces = data.match(regexp);
@@ -147,7 +159,7 @@ exports.decodePacket = function (data) {
var id = pieces[2] || ''
, data = pieces[5] || ''
, packet = {
type: packets[pieces[1]]
type: packetslist[pieces[1]]
, endpoint: pieces[4] || ''
};
@@ -162,30 +174,22 @@ exports.decodePacket = function (data) {
// handle different packet types
switch (packet.type) {
case 'error':
var pieces = data.split('+');
packet.reason = reasons[pieces[0]] || '';
packet.advice = advice[pieces[1]] || '';
break;
case 'message':
packet.data = data || '';
break;
case 'event':
try {
var opts = JSON.parse(data);
packet.name = opts.name;
packet.args = opts.args;
} catch (e) { }
pieces = parse(data);
if (pieces) {
packet.name = pieces.name;
packet.args = pieces.args;
}
packet.args = packet.args || [];
break;
case 'json':
try {
packet.data = JSON.parse(data);
} catch (e) { }
packet.data = parse(data);
break;
case 'connect':
@@ -193,23 +197,22 @@ exports.decodePacket = function (data) {
break;
case 'ack':
var pieces = data.match(/^([0-9]+)(\+)?(.*)/);
pieces = data.match(/^([0-9]+)(\+)?(.*)/);
if (pieces) {
packet.ackId = pieces[1];
packet.args = [];
if (pieces[3]) {
try {
packet.args = pieces[3] ? JSON.parse(pieces[3]) : [];
} catch (e) { }
packet.args = parse(pieces[3]) || [];
}
}
break;
case 'disconnect':
case 'heartbeat':
break;
};
case 'error':
pieces = data.split('+');
packet.reason = reasonslist[pieces[0]] || '';
packet.advice = advicelist[pieces[1]] || '';
}
return packet;
};
@@ -222,12 +225,16 @@ exports.decodePacket = function (data) {
*/
exports.decodePayload = function (data) {
if (undefined == data || null == data) {
return [];
}
if (data[0] == '\ufffd') {
var ret = [];
for (var i = 1, length = ''; i < data.length; i++) {
if (data[i] == '\ufffd') {
ret.push(exports.decodePacket(data.substr(i + 1).substr(0, length)));
ret.push(exports.decodePacket(data.substr(i + 1, length)));
i += Number(length) + 1;
length = '';
} else {

View File

@@ -9,15 +9,13 @@
* Module dependencies.
*/
var http = require('http')
, https = require('https')
, client = require('socket.io-client');
var client = require('socket.io-client');
/**
* Version.
*/
exports.version = '0.7.0';
exports.version = '0.9.16';
/**
* Supported protocol version.
@@ -34,10 +32,20 @@ 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
*/
exports.listen = function (server, options, fn) {
if ('function' == typeof server) {
console.warn('Socket.IO\'s `listen()` method expects an `http.Server` instance\n'
+ 'as its first parameter. Are you migrating from Express 2.x to 3.x?\n'
+ 'If so, check out the "Socket.IO compatibility" section at:\n'
+ 'https://github.com/visionmedia/express/wiki/Migrating-from-2.x-to-3.x');
}
if ('function' == typeof options) {
fn = options;
options = {};
@@ -53,9 +61,9 @@ exports.listen = function (server, options, fn) {
var port = server;
if (options && options.key)
server = https.createServer(options, server);
server = require('https').createServer(options);
else
server = http.createServer();
server = require('http').createServer();
// default response
server.on('request', function (req, res) {
@@ -67,7 +75,7 @@ exports.listen = function (server, options, fn) {
}
// otherwise assume a http/s server
return new exports.Manager(server);
return new exports.Manager(server, options);
};
/**
@@ -94,6 +102,14 @@ exports.Transport = require('./transport');
exports.Socket = require('./socket');
/**
* Static constructor.
*
* @api public
*/
exports.Static = require('./static');
/**
* Store constructor.
*
@@ -102,6 +118,22 @@ exports.Socket = require('./socket');
exports.Store = require('./store');
/**
* Memory Store constructor.
*
* @api public
*/
exports.MemoryStore = require('./stores/memory');
/**
* Redis Store constructor.
*
* @api public
*/
exports.RedisStore = require('./stores/redis');
/**
* Parser.
*

View File

@@ -11,7 +11,7 @@
var parser = require('./parser')
, util = require('./util')
, EventEmitter = process.EventEmitter;
, EventEmitter = require('events').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.
@@ -53,14 +43,9 @@ function Socket (manager, id, nsp, readable) {
this.ackPackets = 0;
this.acks = {};
this.setFlags();
if (readable) {
var self = this;
this.store.once('disconnect:' + id, function (reason) {
self.onDisconnect(reason);
});
}
this.readable = readable;
this.store = this.manager.store.client(this.id);
this.on('error', defaultError);
};
/**
@@ -70,13 +55,23 @@ function Socket (manager, id, nsp, readable) {
Socket.prototype.__proto__ = EventEmitter.prototype;
/**
* Accessor shortcut for the store.
* Accessor shortcut for the handshake data
*
* @api private
*/
Socket.prototype.__defineGetter__('store', function () {
return this.manager.store;
Socket.prototype.__defineGetter__('handshake', function () {
return this.manager.handshaken[this.id];
});
/**
* Accessor shortcut for the transport type
*
* @api private
*/
Socket.prototype.__defineGetter__('transport', function () {
return this.manager.transports[this.id].name;
});
/**
@@ -128,7 +123,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;
};
@@ -155,7 +150,7 @@ Socket.prototype.setFlags = function () {
Socket.prototype.onDisconnect = function (reason) {
if (!this.disconnected) {
this.emit('disconnect', reason);
this.$emit('disconnect', reason);
this.disconnected = true;
}
};
@@ -167,20 +162,38 @@ Socket.prototype.onDisconnect = function (reason) {
*/
Socket.prototype.join = function (name, fn) {
var nsp = this.namespace.name;
this.store.join(this.id, (nsp === '' ? '' : (nsp + '/')) + name, fn);
var nsp = this.namespace.name
, name = (nsp + '/') + name;
this.manager.onJoin(this.id, name);
this.manager.store.publish('join', this.id, name);
if (fn) {
this.log.warn('Client#join callback is deprecated');
fn();
}
return this;
};
/**
* Joins a user to a room.
* Un-joins a user from a room.
*
* @api public
*/
Socket.prototype.leave = function (name, fn) {
var nsp = this.namespace.name;
this.store.leave(this.id, (nsp === '' ? '' : (nsp + '/')) + name, fn);
var nsp = this.namespace.name
, name = (nsp + '/') + name;
this.manager.onLeave(this.id, name);
this.manager.store.publish('leave', this.id, name);
if (fn) {
this.log.warn('Client#leave callback is deprecated');
fn();
}
return this;
};
@@ -198,11 +211,7 @@ Socket.prototype.packet = function (packet) {
packet.endpoint = this.flags.endpoint;
packet = parser.encodePacket(packet);
if (this.flags.volatile) {
this.store.publish('volatile:' + this.id, packet);
} else {
this.store.client(this.id).publish(packet);
}
this.dispatch(packet, this.flags.volatile);
}
this.setFlags();
@@ -210,6 +219,24 @@ Socket.prototype.packet = function (packet) {
return this;
};
/**
* Dispatches a packet
*
* @api private
*/
Socket.prototype.dispatch = function (packet, volatile) {
if (this.manager.transports[this.id] && this.manager.transports[this.id].open) {
this.manager.transports[this.id].onDispatch(packet, volatile);
} else {
if (!volatile) {
this.manager.onClientDispatch(this.id, packet, volatile);
}
this.manager.store.publish('dispatch-remote', this.id, packet, volatile);
}
};
/**
* Stores data for the client.
*
@@ -217,7 +244,7 @@ Socket.prototype.packet = function (packet) {
*/
Socket.prototype.set = function (key, value, fn) {
this.store.client(this.id).set(key, value, fn);
this.store.set(key, value, fn);
return this;
};
@@ -228,7 +255,29 @@ Socket.prototype.set = function (key, value, fn) {
*/
Socket.prototype.get = function (key, fn) {
this.store.client(this.id).get(key, fn);
this.store.get(key, fn);
return this;
};
/**
* Checks data for the client
*
* @api public
*/
Socket.prototype.has = function (key, fn) {
this.store.has(key, fn);
return this;
};
/**
* Deletes data for the client
*
* @api public
*/
Socket.prototype.del = function (key, fn) {
this.store.del(key, fn);
return this;
};
@@ -241,7 +290,20 @@ Socket.prototype.get = function (key, fn) {
Socket.prototype.disconnect = function () {
if (!this.disconnected) {
this.log.info('booting client');
this.store.disconnect(this.id, true);
if ('' === this.namespace.name) {
if (this.manager.transports[this.id] && this.manager.transports[this.id].open) {
this.manager.transports[this.id].onForcedDisconnect();
} else {
this.manager.onClientDisconnect(this.id);
this.manager.store.publish('disconnect-remote', this.id);
}
} else {
this.packet({type: 'disconnect'});
this.manager.onLeave(this.id, this.namespace.name);
this.$emit('disconnect', 'booted');
}
}
return this;
@@ -283,7 +345,7 @@ Socket.prototype.$emit = EventEmitter.prototype.emit;
*/
Socket.prototype.emit = function (ev) {
if (events[ev]) {
if (ev == 'newListener') {
return this.$emit.apply(this, arguments);
}

395
lib/static.js Normal file
View File

@@ -0,0 +1,395 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var client = require('socket.io-client')
, cp = require('child_process')
, fs = require('fs')
, util = require('./util');
/**
* File type details.
*
* @api private
*/
var mime = {
js: {
type: 'application/javascript'
, encoding: 'utf8'
, gzip: true
}
, swf: {
type: 'application/x-shockwave-flash'
, encoding: 'binary'
, gzip: false
}
};
/**
* Regexp for matching custom transport patterns. Users can configure their own
* socket.io bundle based on the url structure. Different transport names are
* concatinated using the `+` char. /socket.io/socket.io+websocket.js should
* create a bundle that only contains support for the websocket.
*
* @api private
*/
var bundle = /\+((?:\+)?[\w\-]+)*(?:\.v\d+\.\d+\.\d+)?(?:\.js)$/
, versioning = /\.v\d+\.\d+\.\d+(?:\.js)$/;
/**
* Export the constructor
*/
exports = module.exports = Static;
/**
* Static constructor
*
* @api public
*/
function Static (manager) {
this.manager = manager;
this.cache = {};
this.paths = {};
this.init();
}
/**
* Initialize the Static by adding default file paths.
*
* @api public
*/
Static.prototype.init = function () {
/**
* Generates a unique id based the supplied transports array
*
* @param {Array} transports The array with transport types
* @api private
*/
function id (transports) {
var id = transports.join('').split('').map(function (char) {
return ('' + char.charCodeAt(0)).split('').pop();
}).reduce(function (char, id) {
return char +id;
});
return client.version + ':' + id;
}
/**
* Generates a socket.io-client file based on the supplied transports.
*
* @param {Array} transports The array with transport types
* @param {Function} callback Callback for the static.write
* @api private
*/
function build (transports, callback) {
client.builder(transports, {
minify: self.manager.enabled('browser client minification')
}, function (err, content) {
callback(err, content ? new Buffer(content) : null, id(transports));
}
);
}
var self = this;
// add our default static files
this.add('/static/flashsocket/WebSocketMain.swf', {
file: client.dist + '/WebSocketMain.swf'
});
this.add('/static/flashsocket/WebSocketMainInsecure.swf', {
file: client.dist + '/WebSocketMainInsecure.swf'
});
// generates dedicated build based on the available transports
this.add('/socket.io.js', function (path, callback) {
build(self.manager.get('transports'), callback);
});
this.add('/socket.io.v', { mime: mime.js }, function (path, callback) {
build(self.manager.get('transports'), callback);
});
// allow custom builds based on url paths
this.add('/socket.io+', { mime: mime.js }, function (path, callback) {
var available = self.manager.get('transports')
, matches = path.match(bundle)
, transports = [];
if (!matches) return callback('No valid transports');
// make sure they valid transports
matches[0].split('.')[0].split('+').slice(1).forEach(function (transport) {
if (!!~available.indexOf(transport)) {
transports.push(transport);
}
});
if (!transports.length) return callback('No valid transports');
build(transports, callback);
});
// clear cache when transports change
this.manager.on('set:transports', function (key, value) {
delete self.cache['/socket.io.js'];
Object.keys(self.cache).forEach(function (key) {
if (bundle.test(key)) {
delete self.cache[key];
}
});
});
};
/**
* Gzip compress buffers.
*
* @param {Buffer} data The buffer that needs gzip compression
* @param {Function} callback
* @api public
*/
Static.prototype.gzip = function (data, callback) {
var gzip = cp.spawn('gzip', ['-9', '-c', '-f', '-n'])
, encoding = Buffer.isBuffer(data) ? 'binary' : 'utf8'
, buffer = []
, err;
gzip.stdout.on('data', function (data) {
buffer.push(data);
});
gzip.stderr.on('data', function (data) {
err = data +'';
buffer.length = 0;
});
gzip.on('close', function () {
if (err) return callback(err);
var size = 0
, index = 0
, i = buffer.length
, content;
while (i--) {
size += buffer[i].length;
}
content = new Buffer(size);
i = buffer.length;
buffer.forEach(function (buffer) {
var length = buffer.length;
buffer.copy(content, index, 0, length);
index += length;
});
buffer.length = 0;
callback(null, content);
});
gzip.stdin.end(data, encoding);
};
/**
* Is the path a static file?
*
* @param {String} path The path that needs to be checked
* @api public
*/
Static.prototype.has = function (path) {
// fast case
if (this.paths[path]) return this.paths[path];
var keys = Object.keys(this.paths)
, i = keys.length;
while (i--) {
if (-~path.indexOf(keys[i])) return this.paths[keys[i]];
}
return false;
};
/**
* Add new paths new paths that can be served using the static provider.
*
* @param {String} path The path to respond to
* @param {Options} options Options for writing out the response
* @param {Function} [callback] Optional callback if no options.file is
* supplied this would be called instead.
* @api public
*/
Static.prototype.add = function (path, options, callback) {
var extension = /(?:\.(\w{1,4}))$/.exec(path);
if (!callback && typeof options == 'function') {
callback = options;
options = {};
}
options.mime = options.mime || (extension ? mime[extension[1]] : false);
if (callback) options.callback = callback;
if (!(options.file || options.callback) || !options.mime) return false;
this.paths[path] = options;
return true;
};
/**
* Writes a static response.
*
* @param {String} path The path for the static content
* @param {HTTPRequest} req The request object
* @param {HTTPResponse} res The response object
* @api public
*/
Static.prototype.write = function (path, req, res) {
/**
* Write a response without throwing errors because can throw error if the
* response is no longer writable etc.
*
* @api private
*/
function write (status, headers, content, encoding) {
try {
res.writeHead(status, headers || undefined);
// only write content if it's not a HEAD request and we actually have
// some content to write (304's doesn't have content).
res.end(
req.method !== 'HEAD' && content ? content : ''
, encoding || undefined
);
} catch (e) {}
}
/**
* Answers requests depending on the request properties and the reply object.
*
* @param {Object} reply The details and content to reply the response with
* @api private
*/
function answer (reply) {
var cached = req.headers['if-none-match'] === reply.etag;
if (cached && self.manager.enabled('browser client etag')) {
return write(304);
}
var accept = req.headers['accept-encoding'] || ''
, gzip = !!~accept.toLowerCase().indexOf('gzip')
, mime = reply.mime
, versioned = reply.versioned
, headers = {
'Content-Type': mime.type
};
// check if we can add a etag
if (self.manager.enabled('browser client etag') && reply.etag && !versioned) {
headers['Etag'] = reply.etag;
}
// see if we need to set Expire headers because the path is versioned
if (versioned) {
var expires = self.manager.get('browser client expires');
headers['Cache-Control'] = 'private, x-gzip-ok="", max-age=' + expires;
headers['Date'] = new Date().toUTCString();
headers['Expires'] = new Date(Date.now() + (expires * 1000)).toUTCString();
}
if (gzip && reply.gzip) {
headers['Content-Length'] = reply.gzip.length;
headers['Content-Encoding'] = 'gzip';
headers['Vary'] = 'Accept-Encoding';
write(200, headers, reply.gzip.content, mime.encoding);
} else {
headers['Content-Length'] = reply.length;
write(200, headers, reply.content, mime.encoding);
}
self.manager.log.debug('served static content ' + path);
}
var self = this
, details;
// most common case first
if (this.manager.enabled('browser client cache') && this.cache[path]) {
return answer(this.cache[path]);
} else if (this.manager.get('browser client handler')) {
return this.manager.get('browser client handler').call(this, req, res);
} else if ((details = this.has(path))) {
/**
* A small helper function that will let us deal with fs and dynamic files
*
* @param {Object} err Optional error
* @param {Buffer} content The data
* @api private
*/
function ready (err, content, etag) {
if (err) {
self.manager.log.warn('Unable to serve file. ' + (err.message || err));
return write(500, null, 'Error serving static ' + path);
}
// store the result in the cache
var reply = self.cache[path] = {
content: content
, length: content.length
, mime: details.mime
, etag: etag || client.version
, versioned: versioning.test(path)
};
// check if gzip is enabled
if (details.mime.gzip && self.manager.enabled('browser client gzip')) {
self.gzip(content, function (err, content) {
if (!err) {
reply.gzip = {
content: content
, length: content.length
}
}
answer(reply);
});
} else {
answer(reply);
}
}
if (details.file) {
fs.readFile(details.file, ready);
} else if(details.callback) {
details.callback.call(this, path, ready);
} else {
write(404, null, 'File handle not found');
}
} else {
write(404, null, 'File not found');
}
};

View File

@@ -5,41 +5,86 @@
* MIT Licensed
*/
/**
* Module dependencies.
*/
var EventEmitter = process.EventEmitter;
/**
* Expose the constructor.
*/
exports = module.exports = Store;
/**
* Module dependencies.
*/
var EventEmitter = require('events').EventEmitter;
/**
* Store interface
*
* @api public
*/
function Store () {};
function Store (options) {
this.options = options;
this.clients = {};
};
/**
* Inherits from EventEmitter
* Inherit from EventEmitter.
*/
Store.prototype.__proto__ = EventEmitter.prototype;
/**
* Log accessor.
* Initializes a client store
*
* @param {String} id
* @api public
*/
Store.prototype.__defineGetter__('log', function () {
return this.manager.log;
});
Store.prototype.client = function (id) {
if (!this.clients[id]) {
this.clients[id] = new (this.constructor.Client)(this, id);
}
return this.clients[id];
};
/**
* Destroys a client
*
* @api {String} sid
* @param {Number} number of seconds to expire client data
* @api private
*/
Store.prototype.destroyClient = function (id, expiration) {
if (this.clients[id]) {
this.clients[id].destroy(expiration);
delete this.clients[id];
}
return this;
};
/**
* Destroys the store
*
* @param {Number} number of seconds to expire client data
* @api private
*/
Store.prototype.destroy = function (clientExpiration) {
var keys = Object.keys(this.clients)
, count = keys.length;
for (var i = 0, l = count; i < l; i++) {
this.destroyClient(keys[i], clientExpiration);
}
this.clients = {};
return this;
};
/**
* Client.
@@ -50,6 +95,4 @@ Store.prototype.__defineGetter__('log', function () {
Store.Client = function (store, id) {
this.store = store;
this.id = id;
this.buffer = [];
this.dict = {};
};

View File

@@ -10,7 +10,7 @@
*/
var crypto = require('crypto')
, Store = require('../store')
, Store = require('../store');
/**
* Exports the constructor.
@@ -26,9 +26,7 @@ Memory.Client = Client;
*/
function Memory (opts) {
this.handshaken = [];
this.clientsMap = {};
this.rooms = {};
Store.call(this, opts);
};
/**
@@ -38,192 +36,28 @@ function Memory (opts) {
Memory.prototype.__proto__ = Store.prototype;
/**
* Handshake a client.
*
* @param {Object} client request object
* @param {Function} callback
* @api public
*/
Memory.prototype.handshake = function (data, fn) {
var id = this.generateId();
this.handshaken.push(id);
fn(null, id);
return this;
};
/**
* Checks if a client is handshaken.
*
* @api public
*/
Memory.prototype.isHandshaken = function (id, fn) {
fn(null, ~this.handshaken.indexOf(id));
return this;
};
/**
* Generates a random id.
* Publishes a message.
*
* @api private
*/
Memory.prototype.generateId = function () {
var rand = String(Math.random() * Math.random() * Date.now());
return crypto.createHash('md5').update(rand).digest('hex');
};
Memory.prototype.publish = function () { };
/**
* Retrieves a client store instance.
*
* @api public
*/
Memory.prototype.client = function (id) {
if (!this.clientsMap[id]) {
this.clientsMap[id] = new Memory.Client(this, id);
this.log.debug('initializing client store for', id);
}
return this.clientsMap[id];
};
/**
* Called when a client disconnects.
*
* @api public
*/
Memory.prototype.disconnect = function (id, force, reason) {
if (~this.handshaken.indexOf(id)) {
this.log.debug('destroying dispatcher for', id);
this.handshaken.splice(this.handshaken.indexOf(id), 1);
this.clientsMap[id].destroy();
this.clientsMap[id] = null;
if (force)
this.publish('disconnect-force:' + id, reason);
this.publish('disconnect:' + id, reason);
}
return this;
};
/**
* Relays a heartbeat message.
* Subscribes to a channel
*
* @api private
*/
Memory.prototype.heartbeat = function (id) {
return this.publish('heartbeat-clear:' + id);
};
Memory.prototype.subscribe = function () { };
/**
* Relays a packet
* Unsubscribes
*
* @api private
*/
Memory.prototype.message = function (id, packet) {
return this.publish('message:' + id, packet);
};
/**
* Returns client ids in a particular room
*
* @api public
*/
Memory.prototype.clients = function (room, fn) {
if ('function' == typeof room) {
fn = room;
room = '';
}
fn && fn(this.rooms[room] || []);
};
/**
* Joins a user to a room
*
* @api private
*/
Memory.prototype.join = function (sid, room, fn) {
if (!this.rooms[room]) {
this.rooms[room] = [];
}
this.client(sid).rooms[room] = this.rooms[room].length;
this.rooms[room].push(sid);
fn && fn();
return this;
};
/**
* Removes a user from a room
*
* @api private
*/
Memory.prototype.leave = function (sid, room, fn) {
if (!this.rooms[room] || this.client(sid).rooms[room] === undefined) {
return this;
}
var i = this.client(sid).rooms[room];
this.rooms[room][i] = null;
delete this.client(sid).rooms[room];
fn && fn();
return this;
};
/**
* Simple publish
*
* @api public
*/
Memory.prototype.publish = function (ev, data, fn) {
if ('function' == typeof data) {
fn = data;
data = undefined;
}
this.emit(ev, data);
if (fn) fn();
return this;
};
/**
* Simple subscribe
*
* @api public
*/
Memory.prototype.subscribe = function (chn, fn) {
this.on(chn, fn);
return this;
};
/**
* Simple unsubscribe
*
* @api public
*/
Memory.prototype.unsubscribe = function (chn) {
this.removeAllListeners(chn);
};
Memory.prototype.unsubscribe = function () { };
/**
* Client constructor
@@ -233,9 +67,7 @@ Memory.prototype.unsubscribe = function (chn) {
function Client () {
Store.Client.apply(this, arguments);
this.reqs = 0;
this.paused = true;
this.rooms = {};
this.data = {};
};
/**
@@ -244,73 +76,6 @@ function Client () {
Client.prototype.__proto__ = Store.Client;
/**
* Counts transport requests.
*
* @api public
*/
Client.prototype.count = function (fn) {
fn(null, ++this.reqs);
return this;
};
/**
* Sets up queue consumption
*
* @api public
*/
Client.prototype.consume = function (fn) {
this.consumer = fn;
this.paused = false;
if (this.buffer.length) {
fn(this.buffer, null);
this.buffer = [];
}
return this;
};
/**
* Publishes a message to be sent to the client.
*
* @String encoded message
* @api public
*/
Client.prototype.publish = function (msg) {
if (this.paused) {
this.buffer.push(msg);
} else {
this.consumer(null, msg);
}
return this;
};
/**
* Pauses the stream.
*
* @api public
*/
Client.prototype.pause = function () {
this.paused = true;
return this;
};
/**
* Destroys the client.
*
* @api public
*/
Client.prototype.destroy = function () {
this.buffer = null;
};
/**
* Gets a key
*
@@ -318,7 +83,7 @@ Client.prototype.destroy = function () {
*/
Client.prototype.get = function (key, fn) {
fn(null, this.dict[key]);
fn(null, this.data[key] === undefined ? null : this.data[key]);
return this;
};
@@ -329,18 +94,50 @@ Client.prototype.get = function (key, fn) {
*/
Client.prototype.set = function (key, value, fn) {
this.dict[key] = value;
this.data[key] = value;
fn && fn(null);
return this;
};
/**
* Emits a message incoming from client.
* Has a key
*
* @api public
*/
Client.prototype.has = function (key, fn) {
fn(null, key in this.data);
};
/**
* Deletes a key
*
* @api public
*/
Client.prototype.del = function (key, fn) {
delete this.data[key];
fn && fn(null);
return this;
};
/**
* Destroys the client.
*
* @param {Number} number of seconds to expire data
* @api private
*/
Client.prototype.onMessage = function (msg) {
this.store.emit('message:' + id, msg);
};
Client.prototype.destroy = function (expiration) {
if ('number' != typeof expiration) {
this.data = {};
} else {
var self = this;
setTimeout(function () {
self.data = {};
}, expiration * 1000);
}
return this;
};

269
lib/stores/redis.js Normal file
View File

@@ -0,0 +1,269 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var crypto = require('crypto')
, Store = require('../store')
, assert = require('assert');
/**
* Exports the constructor.
*/
exports = module.exports = Redis;
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
* - pack (fn) custom packing, defaults to JSON or msgpack if installed
* - unpack (fn) custom packing, defaults to JSON or msgpack if installed
*
* @api public
*/
function Redis (opts) {
opts = opts || {};
// node id to uniquely identify this node
var nodeId = opts.nodeId || function () {
// by default, we generate a random id
return Math.abs(Math.random() * Math.random() * Date.now() | 0);
};
this.nodeId = nodeId();
// packing / unpacking mechanism
if (opts.pack) {
this.pack = opts.pack;
this.unpack = opts.unpack;
} else {
try {
var msgpack = require('msgpack');
this.pack = msgpack.pack;
this.unpack = msgpack.unpack;
} catch (e) {
this.pack = JSON.stringify;
this.unpack = JSON.parse;
}
}
var redis = opts.redis || require('redis')
, RedisClient = redis.RedisClient;
// initialize a pubsub client and a regular client
if (opts.redisPub instanceof RedisClient) {
this.pub = opts.redisPub;
} else {
opts.redisPub || (opts.redisPub = {});
this.pub = redis.createClient(opts.redisPub.port, opts.redisPub.host, opts.redisPub);
}
if (opts.redisSub instanceof RedisClient) {
this.sub = opts.redisSub;
} else {
opts.redisSub || (opts.redisSub = {});
this.sub = redis.createClient(opts.redisSub.port, opts.redisSub.host, opts.redisSub);
}
if (opts.redisClient instanceof RedisClient) {
this.cmd = opts.redisClient;
} else {
opts.redisClient || (opts.redisClient = {});
this.cmd = redis.createClient(opts.redisClient.port, opts.redisClient.host, opts.redisClient);
}
Store.call(this, opts);
this.sub.setMaxListeners(0);
this.setMaxListeners(0);
};
/**
* Inherits from Store.
*/
Redis.prototype.__proto__ = Store.prototype;
/**
* Publishes a message.
*
* @api private
*/
Redis.prototype.publish = function (name) {
var args = Array.prototype.slice.call(arguments, 1);
this.pub.publish(name, this.pack({ nodeId: this.nodeId, args: args }));
this.emit.apply(this, ['publish', name].concat(args));
};
/**
* Subscribes to a channel
*
* @api private
*/
Redis.prototype.subscribe = function (name, consumer, fn) {
this.sub.subscribe(name);
if (consumer || fn) {
var self = this;
self.sub.on('subscribe', function subscribe (ch) {
if (name == ch) {
function message (ch, msg) {
if (name == ch) {
msg = self.unpack(msg);
// we check that the message consumed wasnt emitted by this node
if (self.nodeId != msg.nodeId) {
consumer.apply(null, msg.args);
}
}
};
self.sub.on('message', message);
self.on('unsubscribe', function unsubscribe (ch) {
if (name == ch) {
self.sub.removeListener('message', message);
self.removeListener('unsubscribe', unsubscribe);
}
});
self.sub.removeListener('subscribe', subscribe);
fn && fn();
}
});
}
this.emit('subscribe', name, consumer, fn);
};
/**
* Unsubscribes
*
* @api private
*/
Redis.prototype.unsubscribe = function (name, fn) {
this.sub.unsubscribe(name);
if (fn) {
var client = this.sub;
client.on('unsubscribe', function unsubscribe (ch) {
if (name == ch) {
fn();
client.removeListener('unsubscribe', unsubscribe);
}
});
}
this.emit('unsubscribe', name, fn);
};
/**
* Destroys the store
*
* @api public
*/
Redis.prototype.destroy = function () {
Store.prototype.destroy.call(this);
this.pub.end();
this.sub.end();
this.cmd.end();
};
/**
* Client constructor
*
* @api private
*/
function Client (store, id) {
Store.Client.call(this, store, id);
};
/**
* Inherits from Store.Client
*/
Client.prototype.__proto__ = Store.Client;
/**
* Redis hash get
*
* @api private
*/
Client.prototype.get = function (key, fn) {
this.store.cmd.hget(this.id, key, fn);
return this;
};
/**
* Redis hash set
*
* @api private
*/
Client.prototype.set = function (key, value, fn) {
this.store.cmd.hset(this.id, key, value, fn);
return this;
};
/**
* Redis hash del
*
* @api private
*/
Client.prototype.del = function (key, fn) {
this.store.cmd.hdel(this.id, key, fn);
return this;
};
/**
* Redis hash has
*
* @api private
*/
Client.prototype.has = function (key, fn) {
this.store.cmd.hexists(this.id, key, function (err, has) {
if (err) return fn(err);
fn(null, !!has);
});
return this;
};
/**
* Destroys client
*
* @param {Number} number of seconds to expire data
* @api private
*/
Client.prototype.destroy = function (expiration) {
if ('number' != typeof expiration) {
this.store.cmd.del(this.id);
} else {
this.store.cmd.expire(this.id, expiration);
}
return this;
};

View File

@@ -23,22 +23,13 @@ exports = module.exports = Transport;
* @api public
*/
function Transport (mng, data) {
function Transport (mng, data, req) {
this.manager = mng;
this.id = data.id;
this.paused = true;
this.disconnected = false;
this.drained = true;
};
/**
* Sets the corresponding request object.
*/
Transport.prototype.__defineSetter__('request', function (req) {
this.log.debug('setting request', req.method, req.url);
this.handleRequest(req);
});
};
/**
* Access the logger.
@@ -67,29 +58,14 @@ Transport.prototype.__defineGetter__('store', function () {
*/
Transport.prototype.handleRequest = function (req) {
this.log.debug('setting request', req.method, req.url);
this.req = req;
if (req.method == 'GET') {
this.socket = req.socket;
this.open = true;
this.drained = true;
this.log.debug('publishing that', this.id, 'connected');
var self = this;
this.store.publish('open:' + this.id, function () {
self.store.once('open:' + self.id, function () {
self.log.info('request for existing session connection change');
self.close();
self.clearTimeouts();
self.clearHandlers();
});
if (!self.paused) {
self.subscribe();
}
});
this.setHeartbeatInterval();
this.setHandlers();
this.onSocketConnect();
@@ -113,19 +89,6 @@ Transport.prototype.onSocketConnect = function () { };
Transport.prototype.setHandlers = function () {
var self = this;
this.store.once('disconnect-force:' + this.id, function () {
self.onForcedDisconnect();
});
this.store.on('heartbeat-clear:' + this.id, function () {
self.clearHeartbeatTimeout();
self.setHeartbeatInterval();
});
this.store.on('volatile:' + this.id, function (packet) {
self.writeVolatile(packet);
});
this.bound = {
end: this.onSocketEnd.bind(this)
, close: this.onSocketClose.bind(this)
@@ -137,6 +100,8 @@ Transport.prototype.setHandlers = function () {
this.socket.on('close', this.bound.close);
this.socket.on('error', this.bound.error);
this.socket.on('drain', this.bound.drain);
this.handlersSet = true;
};
/**
@@ -146,14 +111,12 @@ Transport.prototype.setHandlers = function () {
*/
Transport.prototype.clearHandlers = function () {
this.store.unsubscribe('disconnect-force:' + this.id);
this.store.unsubscribe('heartbeat-clear:' + this.id);
this.store.unsubscribe('volatile:' + this.id);
this.socket.removeListener('end', this.bound.end);
this.socket.removeListener('close', this.bound.close);
this.socket.removeListener('error', this.bound.error);
this.socket.removeListener('drain', this.bound.drain);
if (this.handlersSet) {
this.socket.removeListener('end', this.bound.end);
this.socket.removeListener('close', this.bound.close);
this.socket.removeListener('error', this.bound.error);
this.socket.removeListener('drain', this.bound.drain);
}
};
/**
@@ -163,10 +126,7 @@ Transport.prototype.clearHandlers = function () {
*/
Transport.prototype.onSocketEnd = function () {
// we check that the socket wasn't swapped
// we don't want to sever a connection that's not active, since we don't kill
// inactive sockets that the browser might reuse for other purposes
this.end(false, 'socket end');
this.end('socket end');
};
/**
@@ -176,7 +136,7 @@ Transport.prototype.onSocketEnd = function () {
*/
Transport.prototype.onSocketClose = function (error) {
this.end(false, error ? 'socket error' : 'socket close');
this.end(error ? 'socket error' : 'socket close');
};
/**
@@ -204,6 +164,17 @@ Transport.prototype.onSocketDrain = function () {
this.drained = true;
};
/**
* Called upon receiving a heartbeat packet.
*
* @api private
*/
Transport.prototype.onHeartbeatClear = function () {
this.clearHeartbeatTimeout();
this.setHeartbeatInterval();
};
/**
* Called upon a forced disconnection.
*
@@ -216,7 +187,21 @@ Transport.prototype.onForcedDisconnect = function () {
if (this.open) {
this.packet({ type: 'disconnect' });
}
this.end(true);
this.end('booted');
}
};
/**
* Dispatches a packet.
*
* @api private
*/
Transport.prototype.onDispatch = function (packet, volatile) {
if (volatile) {
this.writeVolatile(packet);
} else {
this.write(packet);
}
};
@@ -231,7 +216,7 @@ Transport.prototype.setCloseTimeout = function () {
this.closeTimeout = setTimeout(function () {
self.log.debug('fired close timeout for client', self.id);
self.closeTimeout = null;
self.end(false, 'close timeout');
self.end('close timeout');
}, this.manager.get('close timeout') * 1000);
this.log.debug('set close timeout for client', this.id);
@@ -256,13 +241,13 @@ 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 () {
self.log.debug('fired heartbeat timeout for client', self.id);
self.heartbeatTimeout = null;
self.end(false, 'heartbeat timeout');
self.end('heartbeat timeout');
}, this.manager.get('heartbeat timeout') * 1000);
this.log.debug('set heartbeat timeout for client', this.id);
@@ -276,7 +261,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);
@@ -291,11 +276,12 @@ Transport.prototype.clearHeartbeatTimeout = function () {
*/
Transport.prototype.setHeartbeatInterval = function () {
if (!this.heartbeatTimeout) {
if (!this.heartbeatInterval && this.manager.enabled('heartbeats')) {
var self = this;
this.heartbeatInterval = setTimeout(function () {
self.heartbeat();
self.heartbeatInterval = null;
}, this.manager.get('heartbeat interval') * 1000);
this.log.debug('set heartbeat interval for client', this.id);
@@ -338,25 +324,52 @@ Transport.prototype.heartbeat = function () {
*/
Transport.prototype.onMessage = function (packet) {
var current = this.manager.transports[this.id];
if ('heartbeat' == packet.type) {
this.log.debug('got heartbeat packet');
this.store.heartbeat(this.id);
} else if ('disconnect' == packet.type && packet.endpoint == '') {
this.log.debug('got disconnection packet');
this.store.disconnect(this.id, true);
if (current && current.open) {
current.onHeartbeatClear();
} else {
this.store.publish('heartbeat-clear', this.id);
}
} else {
this.log.debug('got packet');
if ('disconnect' == packet.type && packet.endpoint == '') {
this.log.debug('got disconnection packet');
if (current) {
current.onForcedDisconnect();
} else {
this.store.publish('disconnect-force', this.id);
}
return;
}
if (packet.id && packet.ack != 'data') {
this.log.debug('acknowledging packet automatically');
this.store.client(this.id).publish(parser.encodePacket({
var ack = parser.encodePacket({
type: 'ack'
, ackId: packet.id
, endpoint: packet.endpoint || ''
}));
});
if (current && current.open) {
current.onDispatch(ack);
} else {
this.manager.onClientDispatch(this.id, ack);
this.store.publish('dispatch-remote', this.id, ack);
}
}
this.store.message(this.id, packet);
// handle packet locally or publish it
if (current) {
this.manager.onClientMessage(this.id, packet);
} else {
this.store.publish('message-remote', this.id, packet);
}
}
};
@@ -367,7 +380,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);
@@ -382,7 +395,7 @@ Transport.prototype.clearHeartbeatInterval = function () {
Transport.prototype.disconnect = function (reason) {
this.packet({ type: 'disconnect' });
this.end(false, reason);
this.end(reason);
return this;
};
@@ -409,8 +422,10 @@ Transport.prototype.close = function () {
Transport.prototype.onClose = function () {
if (this.open) {
this.setCloseTimeout();
this.unsubscribe();
this.clearHandlers();
this.open = false;
this.manager.onClose(this.id);
this.store.publish('close', this.id);
}
};
@@ -420,43 +435,36 @@ Transport.prototype.onClose = function () {
* @api private
*/
Transport.prototype.end = function (forced, reason) {
Transport.prototype.end = function (reason) {
if (!this.disconnected) {
this.log.info('ending socket');
this.log.info('transport end (' + reason + ')');
var local = this.manager.transports[this.id];
this.close();
this.clearTimeouts();
if (!forced)
this.store.disconnect(this.id, false, reason);
this.disconnected = true;
if (local) {
this.manager.onClientDisconnect(this.id, reason);
}
this.store.publish('disconnect-remote', this.id, reason);
}
};
/**
* Signals that the transport can start flushing buffers.
*
* @api public
*/
Transport.prototype.resume = function () {
if (!this.disconnected) {
this.paused = false;
this.setHeartbeatInterval();
this.subscribe();
}
return this;
};
/**
* Signals that the transport should pause and buffer data.
*
* @api public
*/
Transport.prototype.pause = function () {
this.paused = true;
Transport.prototype.discard = function () {
this.log.debug('discarding transport');
this.discarded = true;
this.clearTimeouts();
this.clearHandlers();
return this;
};
@@ -476,7 +484,7 @@ Transport.prototype.error = function (reason, advice) {
});
this.log.warn(reason, advice ? ('client should ' + advice) : '');
this.end(false, 'error');
this.end('error');
};
/**
@@ -489,44 +497,6 @@ Transport.prototype.packet = function (obj) {
return this.write(parser.encodePacket(obj));
};
/**
* Subscribe client.
*
* @api private
*/
Transport.prototype.subscribe = function () {
if (!this.subscribed) {
this.log.debug('subscribing', this.id);
var self = this;
// subscribe to buffered + normal messages
this.store.client(this.id).consume(function (payload, packet) {
if (payload) {
self.payload(payload);
} else {
self.write(packet);
}
});
this.subscribed = true;
}
};
/**
* Unsubscribe client.
*
* @api private
*/
Transport.prototype.unsubscribe = function () {
this.log.info('unsubscribing', this.id);
this.store.client(this.id).pause();
this.subscribed = false;
};
/**
* Writes a volatile message.
*

View File

@@ -1,4 +1,3 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
@@ -23,8 +22,8 @@ exports = module.exports = FlashSocket;
* @api public
*/
function FlashSocket () {
WebSocket.apply(this, arguments);
function FlashSocket (mng, data, req) {
return WebSocket.call(this, mng, data, req);
}
/**
@@ -33,6 +32,14 @@ function FlashSocket () {
FlashSocket.prototype.__proto__ = WebSocket.prototype;
/**
* Transport name
*
* @api public
*/
FlashSocket.prototype.name = 'flashsocket';
/**
* Listens for new configuration changes of the Manager
* this way we can enable and disable the flash server.
@@ -41,13 +48,20 @@ FlashSocket.prototype.__proto__ = WebSocket.prototype;
* @api private
*/
var server;
FlashSocket.init = function (manager) {
var server;
function create () {
// Drop out immediately if the user has
// disabled the flash policy server
if (!manager.get('flash policy server')) {
return;
}
server = require('policyfile').createServer({
log: function(msg){
manager.log.info(msg.toLowerCase());
manager.log.info(msg);
}
}, manager.get('origins'));
@@ -72,10 +86,32 @@ FlashSocket.init = function (manager) {
// destory the server and create a new server
manager.on('set:flash policy port', function (value, key) {
var transports = manager.get('transports');
if (~transports.indexOf('flashsocket')) {
if (server) {
if (server.port === value) return;
// destroy the server and rebuild it on a new port
try {
server.close();
}
catch (e) { /* ignore exception. could e.g. be that the server isn't started yet */ }
}
create();
}
});
if (server && server.port !== value && ~transports.indexOf('flashsocket')) {
// destroy the server and rebuild it on a new port
server.close();
// create or destroy the server
manager.on('set:flash policy server', function (value, key) {
var transports = manager.get('transports');
if (~transports.indexOf('flashsocket')) {
if (server && !value) {
// destroy the server
try {
server.close();
}
catch (e) { /* ignore exception. could e.g. be that the server isn't started yet */ }
}
} else if (!server && value) {
// create the server
create();
}
});
@@ -86,7 +122,6 @@ FlashSocket.init = function (manager) {
create();
}
});
// check if we need to initialize at start
if (~manager.get('transports').indexOf('flashsocket')){
create();

View File

@@ -23,8 +23,8 @@ exports = module.exports = HTMLFile;
* @api public
*/
function HTMLFile (mng, data) {
HTTPTransport.call(this, mng, data);
function HTMLFile (mng, data, req) {
HTTPTransport.call(this, mng, data, req);
};
/**
@@ -33,6 +33,14 @@ function HTMLFile (mng, data) {
HTMLFile.prototype.__proto__ = HTTPTransport.prototype;
/**
* Transport name
*
* @api public
*/
HTMLFile.prototype.name = 'htmlfile';
/**
* Handles the request.
*
@@ -44,7 +52,7 @@ HTMLFile.prototype.handleRequest = function (req) {
if (req.method == 'GET') {
req.res.writeHead(200, {
'Content-Type': 'text/html'
'Content-Type': 'text/html; charset=UTF-8'
, 'Connection': 'keep-alive'
, 'Transfer-Encoding': 'chunked'
});
@@ -64,11 +72,12 @@ HTMLFile.prototype.handleRequest = function (req) {
*/
HTMLFile.prototype.write = function (data) {
data = '<script>_(' + JSON.stringify(data) + ');</script>';
// escape all forward slashes. see GH-1251
data = '<script>_(' + JSON.stringify(data).replace(/\//g, '\\/') + ');</script>';
if (this.response.write(data)) {
this.drained = true;
}
this.log.debug('htmlfile writing', data);
this.log.debug(this.name + ' writing', data);
};

View File

@@ -23,8 +23,8 @@ exports = module.exports = HTTPPolling;
* @api public.
*/
function HTTPPolling (mng, data) {
HTTPTransport.call(this, mng, data);
function HTTPPolling (mng, data, req) {
HTTPTransport.call(this, mng, data, req);
};
/**
@@ -35,6 +35,26 @@ function HTTPPolling (mng, data) {
HTTPPolling.prototype.__proto__ = HTTPTransport.prototype;
/**
* Transport name
*
* @api public
*/
HTTPPolling.prototype.name = 'httppolling';
/**
* Override setHandlers
*
* @api private
*/
HTTPPolling.prototype.setHandlers = function () {
HTTPTransport.prototype.setHandlers.call(this);
this.socket.removeListener('end', this.bound.end);
this.socket.removeListener('close', this.bound.close);
};
/**
* Removes heartbeat timeouts for polling.
*/
@@ -56,8 +76,8 @@ HTTPPolling.prototype.handleRequest = function (req) {
var self = this;
this.pollTimeout = setTimeout(function () {
self.close();
self.log.debug('polling closed due to exceeded duration');
self.packet({ type: 'noop' });
self.log.debug(self.name + ' closed due to exceeded duration');
}, this.manager.get('polling duration') * 1000);
this.log.debug('setting poll timeout');
@@ -120,8 +140,8 @@ HTTPPolling.prototype.write = function (data, close) {
* @api private
*/
HTTPPolling.prototype.end = function () {
HTTPPolling.prototype.end = function (reason) {
this.clearPollTimeout();
return HTTPTransport.prototype.end.call(this);
return HTTPTransport.prototype.end.call(this, reason);
};

View File

@@ -25,8 +25,8 @@ exports = module.exports = HTTPTransport;
* @api public
*/
function HTTPTransport (mng, data) {
Transport.call(this, mng, data);
function HTTPTransport (mng, data, req) {
Transport.call(this, mng, data, req);
};
/**
@@ -42,35 +42,46 @@ HTTPTransport.prototype.__proto__ = Transport.prototype;
*/
HTTPTransport.prototype.handleRequest = function (req) {
// Always set the response in case an error is returned to the client
this.response = req.res;
if (req.method == 'POST') {
var buffer = ''
, res = req.res
, origin = req.headers.origin
, headers = { 'Content-Length': 0 }
, headers = { 'Content-Length': 1, 'Content-Type': 'text/plain; charset=UTF-8' }
, self = this;
req.on('data', function (data) {
buffer += data;
if (Buffer.byteLength(buffer) >= self.manager.get('destroy buffer size')) {
buffer = '';
req.connection.destroy();
}
});
req.on('end', function () {
res.writeHead(200, headers);
res.end('1');
self.onData(self.postEncoded ? qs.parse(buffer).d : buffer);
});
// prevent memory leaks for uncompleted requests
req.on('close', function () {
buffer = '';
self.onClose();
});
if (origin) {
// https://developer.mozilla.org/En/HTTP_Access_Control
headers['Access-Control-Allow-Origin'] = '*';
if (req.headers.cookie) {
headers['Access-Control-Allow-Credentials'] = 'true';
}
headers['Access-Control-Allow-Origin'] = origin;
headers['Access-Control-Allow-Credentials'] = 'true';
headers['X-XSS-Protection'] = '0';
}
res.writeHead(200);
res.end('');
} else {
this.response = req.res;
Transport.prototype.handleRequest.call(this, req);
}
};
@@ -83,9 +94,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('xhr received data packet', data);
this.onMessage(messages[i]);
}
};

View File

@@ -10,6 +10,7 @@
*/
var HTTPPolling = require('./http-polling');
var jsonpolling_re = /^\d+$/
/**
* Export the constructor.
@@ -23,13 +24,13 @@ exports = module.exports = JSONPPolling;
* @api public
*/
function JSONPPolling (mng, data) {
HTTPPolling.call(this, mng, data);
function JSONPPolling (mng, data, req) {
HTTPPolling.call(this, mng, data, req);
this.head = 'io.j[0](';
this.foot = ');';
if (data.query.i) {
if (data.query.i && jsonpolling_re.test(data.query.i)) {
this.head = 'io.j[' + data.query.i + '](';
}
};
@@ -40,12 +41,38 @@ function JSONPPolling (mng, data) {
JSONPPolling.prototype.__proto__ = HTTPPolling.prototype;
/**
* Transport name
*
* @api public
*/
JSONPPolling.prototype.name = 'jsonppolling';
/**
* Make sure POST are decoded.
*/
JSONPPolling.prototype.postEncoded = true;
/**
* Handles incoming data.
* Due to a bug in \n handling by browsers, we expect a JSONified string.
*
* @api private
*/
JSONPPolling.prototype.onData = function (data) {
try {
data = JSON.parse(data);
} catch (e) {
this.error('parse', 'reconnect');
return;
}
HTTPPolling.prototype.onData.call(this, data);
};
/**
* Performs the write.
*
@@ -62,8 +89,9 @@ 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);
this.log.debug('json-p writing', data);
this.log.debug(this.name + ' writing', 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.
@@ -27,316 +24,13 @@ exports = module.exports = WebSocket;
* @api public
*/
function WebSocket (mng, data) {
// parser
var self = this;
this.parser = new Parser();
this.parser.on('data', function (packet) {
self.log.debug('websocket 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);
};
/**
* Inherits from Transport.
*/
WebSocket.prototype.__proto__ = Transport.prototype;
/**
* 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('WebSocket connection invalid');
this.end();
return;
function WebSocket (mng, data, req) {
var transport
, version = req.headers['sec-websocket-version'];
if (typeof version !== 'undefined' && typeof protocolVersions[version] !== 'undefined') {
transport = 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('websocket 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));
}
};
/**
* 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 WebSocket 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;
else transport = new protocolVersions['default'](mng, data, req);
if (typeof this.name !== 'undefined') transport.name = this.name;
return transport;
};

View File

@@ -0,0 +1,376 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var Transport = require('../../transport')
, EventEmitter = require('events').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({maxBuffer: mng.get('destroy buffer size')});
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();
});
this.parser.on('kick', function (reason) {
self.log.warn(self.name + ' parser forced user kick: ' + reason);
self.onMessage({type: 'disconnect', endpoint: ''});
self.end();
});
Transport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
WebSocket.prototype.__proto__ = Transport.prototype;
/**
* Transport name
*
* @api public
*/
WebSocket.prototype.name = 'websocket';
/**
* Websocket draft version
*
* @api public
*/
WebSocket.prototype.protocolVersion = 'hixie-76';
/**
* 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']
, waitingForNonce = false;
if(this.manager.settings['match origin protocol']){
location = (origin.indexOf('https')>-1 ? 'wss' : 'ws') + '://' + this.req.headers.host + this.req.url;
}else if(this.socket.encrypted){
location = 'wss://' + this.req.headers.host + this.req.url;
}else{
location = 'ws://' + this.req.headers.host + this.req.url;
}
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 (opts) {
this._maxBuffer = (opts && opts.maxBuffer) || 10E7;
this._dataLength = 0;
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._dataLength += data.length;
if(this._dataLength > this._maxBuffer) {
this.buffer = ''; //Clear buffer
this.emit('kick', 'max buffer size reached');
return;
}
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,642 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var Transport = require('../../transport')
, EventEmitter = require('events').EventEmitter
, crypto = require('crypto')
, url = require('url')
, 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.manager = mng;
this.parser = new Parser({maxBuffer: mng.get('destroy buffer size')});
this.parser.on('data', function (packet) {
self.onMessage(parser.decodePacket(packet));
});
this.parser.on('ping', function () {
// version 8 ping => pong
try {
self.socket.write('\u008a\u0000');
}
catch (e) {
self.end();
return;
}
});
this.parser.on('close', function () {
self.end();
});
this.parser.on('error', function (reason) {
self.log.warn(self.name + ' parser error: ' + reason);
self.end();
});
this.parser.on('kick', function (reason) {
self.log.warn(self.name + ' parser forced user kick: ' + reason);
self.onMessage({type: 'disconnect', endpoint: ''});
self.end();
});
Transport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
WebSocket.prototype.__proto__ = Transport.prototype;
/**
* Transport name
*
* @api public
*/
WebSocket.prototype.name = 'websocket';
/**
* Websocket draft version
*
* @api public
*/
WebSocket.prototype.protocolVersion = '07-12';
/**
* Called when the socket connects.
*
* @api private
*/
WebSocket.prototype.onSocketConnect = function () {
var self = this;
if (typeof this.req.headers.upgrade === 'undefined' ||
this.req.headers.upgrade.toLowerCase() !== 'websocket') {
this.log.warn(this.name + ' connection invalid');
this.end();
return;
}
var origin = this.req.headers['sec-websocket-origin']
, location = ((this.manager.settings['match origin protocol'] ?
origin.match(/^https/) : this.socket.encrypted) ?
'wss' : 'ws')
+ '://' + this.req.headers.host + this.req.url;
if (!this.verifyOrigin(origin)) {
this.log.warn(this.name + ' connection invalid: origin mismatch');
this.end();
return;
}
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);
});
};
/**
* Verifies the origin of a request.
*
* @api private
*/
WebSocket.prototype.verifyOrigin = function (origin) {
var origins = this.manager.get('origins');
if (origin === 'null') origin = '*';
if (origins.indexOf('*:*') !== -1) {
return true;
}
if (origin) {
try {
var parts = url.parse(origin);
parts.port = parts.port || 80;
var ok =
~origins.indexOf(parts.hostname + ':' + parts.port) ||
~origins.indexOf(parts.hostname + ':*') ||
~origins.indexOf('*:' + parts.port);
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 websocket call, yet required by config');
}
return false;
};
/**
* Writes to the socket.
*
* @api private
*/
WebSocket.prototype.write = function (data) {
if (this.open) {
var buf = this.frame(0x81, data);
try {
this.socket.write(buf, 'binary');
}
catch (e) {
this.end();
return;
}
this.log.debug(this.name + ' writing', data);
}
};
/**
* 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;
};
/**
* 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 (opts) {
this.state = {
activeFragmentedOperation: null,
lastFragment: false,
masked: false,
opcode: 0
};
this.overflow = null;
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
this.currentMessage = '';
this._maxBuffer = (opts && opts.maxBuffer) || 10E7;
this._dataLength = 0;
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));
});
}
},
// binary
'2': function(data) {
var finish = function(mask, data) {
if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list
self.currentMessage.push(self.unmask(mask, data, true));
if (self.state.lastFragment) {
self.emit('binary', self.concatBuffers(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) {
this._dataLength += data.length;
if (this._dataLength > this._maxBuffer) {
// Clear data
this.overflow = null;
this.expectBuffer = null;
// Kick client
this.emit('kick', 'max buffer size reached');
return;
}
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) {
if (length > this._maxBuffer) {
this.emit('kick', 'expected input larger than max buffer');
return;
}
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 must be 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
this.state.opcode = this.state.activeFragmentedOperation;
if (!(this.state.opcode == 1 || this.state.opcode == 2)) {
this.error('continuation frame cannot follow current opcode')
return;
}
}
else {
this.state.opcode = opcode;
if (this.state.lastFragment === false) {
this.state.activeFragmentedOperation = opcode;
}
}
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, binary) {
if (mask != null) {
for (var i = 0, ll = buf.length; i < ll; i++) {
buf[i] ^= mask[i % 4];
}
}
if (binary) return buf;
return buf != null ? buf.toString('utf8') : '';
}
/**
* Concatenates a list of buffers.
*
* @api private
*/
Parser.prototype.concatBuffers = function(buffers) {
var length = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
length += buffers[i].length;
}
var mergedBuffer = new Buffer(length);
var offset = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
buffers[i].copy(mergedBuffer, offset);
offset += buffers[i].length;
}
return mergedBuffer;
}
/**
* Handles an error
*
* @api private
*/
Parser.prototype.error = function (reason) {
this.reset();
this.emit('error', reason);
return this;
};

View File

@@ -0,0 +1,642 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var Transport = require('../../transport')
, EventEmitter = require('events').EventEmitter
, crypto = require('crypto')
, url = require('url')
, 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.manager = mng;
this.parser = new Parser({maxBuffer: mng.get('destroy buffer size')});
this.parser.on('data', function (packet) {
self.onMessage(parser.decodePacket(packet));
});
this.parser.on('ping', function () {
// version 8 ping => pong
try {
self.socket.write('\u008a\u0000');
}
catch (e) {
self.end();
return;
}
});
this.parser.on('close', function () {
self.end();
});
this.parser.on('error', function (reason) {
self.log.warn(self.name + ' parser error: ' + reason);
self.end();
});
this.parser.on('kick', function (reason) {
self.log.warn(self.name + ' parser forced user kick: ' + reason);
self.onMessage({type: 'disconnect', endpoint: ''});
self.end();
});
Transport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
WebSocket.prototype.__proto__ = Transport.prototype;
/**
* Transport name
*
* @api public
*/
WebSocket.prototype.name = 'websocket';
/**
* Websocket draft version
*
* @api public
*/
WebSocket.prototype.protocolVersion = '16';
/**
* Called when the socket connects.
*
* @api private
*/
WebSocket.prototype.onSocketConnect = function () {
var self = this;
if (typeof this.req.headers.upgrade === 'undefined' ||
this.req.headers.upgrade.toLowerCase() !== 'websocket') {
this.log.warn(this.name + ' connection invalid');
this.end();
return;
}
var origin = this.req.headers['origin'] || ''
, location = ((this.manager.settings['match origin protocol'] ?
origin.match(/^https/) : this.socket.encrypted) ?
'wss' : 'ws')
+ '://' + this.req.headers.host + this.req.url;
if (!this.verifyOrigin(origin)) {
this.log.warn(this.name + ' connection invalid: origin mismatch');
this.end();
return;
}
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);
});
};
/**
* Verifies the origin of a request.
*
* @api private
*/
WebSocket.prototype.verifyOrigin = function (origin) {
var origins = this.manager.get('origins');
if (origin === 'null') origin = '*';
if (origins.indexOf('*:*') !== -1) {
return true;
}
if (origin) {
try {
var parts = url.parse(origin);
parts.port = parts.port || 80;
var ok =
~origins.indexOf(parts.hostname + ':' + parts.port) ||
~origins.indexOf(parts.hostname + ':*') ||
~origins.indexOf('*:' + parts.port);
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 websocket call, yet required by config');
}
return false;
};
/**
* Writes to the socket.
*
* @api private
*/
WebSocket.prototype.write = function (data) {
if (this.open) {
var buf = this.frame(0x81, data);
try {
this.socket.write(buf, 'binary');
}
catch (e) {
this.end();
return;
}
this.log.debug(this.name + ' writing', data);
}
};
/**
* 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;
};
/**
* 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 (opts) {
this.state = {
activeFragmentedOperation: null,
lastFragment: false,
masked: false,
opcode: 0
};
this.overflow = null;
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
this.currentMessage = '';
this._maxBuffer = (opts && opts.maxBuffer) || 10E7;
this._dataLength = 0;
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));
});
}
},
// binary
'2': function(data) {
var finish = function(mask, data) {
if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list
self.currentMessage.push(self.unmask(mask, data, true));
if (self.state.lastFragment) {
self.emit('binary', self.concatBuffers(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) {
this._dataLength += data.length;
if (this._dataLength > this._maxBuffer) {
// Clear data
this.overflow = null;
this.expectBuffer = null;
// Kick client
this.emit('kick', 'max buffer size reached');
return;
}
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) {
if (length > this._maxBuffer) {
this.emit('kick', 'expected input larger than max buffer');
return;
}
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 must be empty');
return;
}
this.state.lastFragment = (data[0] & 0x80) == 0x80;
this.state.masked = (data[1] & 0x80) == 0x80;
var opcode = data[0] & 0xf;
if (opcode == 0) {
// continuation frame
this.state.opcode = this.state.activeFragmentedOperation;
if (!(this.state.opcode == 1 || this.state.opcode == 2)) {
this.error('continuation frame cannot follow current opcode')
return;
}
}
else {
this.state.opcode = opcode;
if (this.state.lastFragment === false) {
this.state.activeFragmentedOperation = opcode;
}
}
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, binary) {
if (mask != null) {
for (var i = 0, ll = buf.length; i < ll; i++) {
buf[i] ^= mask[i % 4];
}
}
if (binary) return buf;
return buf != null ? buf.toString('utf8') : '';
}
/**
* Concatenates a list of buffers.
*
* @api private
*/
Parser.prototype.concatBuffers = function(buffers) {
var length = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
length += buffers[i].length;
}
var mergedBuffer = new Buffer(length);
var offset = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
buffers[i].copy(mergedBuffer, offset);
offset += buffers[i].length;
}
return mergedBuffer;
}
/**
* Handles an error
*
* @api private
*/
Parser.prototype.error = function (reason) {
this.reset();
this.emit('error', reason);
return this;
};

View File

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

View File

@@ -23,8 +23,8 @@ exports = module.exports = XHRPolling;
* @api public
*/
function XHRPolling (mng, data) {
HTTPPolling.call(this, mng, data);
function XHRPolling (mng, data, req) {
HTTPPolling.call(this, mng, data, req);
};
/**
@@ -33,6 +33,14 @@ function XHRPolling (mng, data) {
XHRPolling.prototype.__proto__ = HTTPPolling.prototype;
/**
* Transport name
*
* @api public
*/
XHRPolling.prototype.name = 'xhr-polling';
/**
* Frames data prior to write.
*
@@ -51,14 +59,11 @@ XHRPolling.prototype.doWrite = function (data) {
if (origin) {
// https://developer.mozilla.org/En/HTTP_Access_Control
headers['Access-Control-Allow-Origin'] = '*';
if (this.req.headers.cookie) {
headers['Access-Control-Allow-Credentials'] = 'true';
}
headers['Access-Control-Allow-Origin'] = origin;
headers['Access-Control-Allow-Credentials'] = 'true';
}
this.response.writeHead(200, headers);
this.response.write(data);
this.log.debug('xhr-polling writing', data);
this.log.debug(this.name + ' writing', data);
};

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.0"
, "description": "Realtime apps made cross-browser & easy with a WebSocket-like API"
, "version": "0.9.19"
, "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,30 @@
{ "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.0"
, "policyfile": ">= 0.0.3"
"socket.io-client": "0.9.16"
, "policyfile": "0.0.4"
, "base64id": "0.1.0"
}
, "devDependencies": {
"expresso": "0.7.7"
, "should": "0.0.4"
"expresso": "0.9.2"
, "should": "*"
, "benchmark": "0.2.2"
, "microtime": "2.1.3"
, "colors": "0.5.1"
}
, "optionalDependencies": {
"redis": "0.7.3"
}
, "main": "index"
, "engines": { "node": ">= 0.4.0" }
, "scripts": {
"test": "make test"
}
}

View File

@@ -5,7 +5,7 @@ var events = require('events');
var http = require('http');
var net = require('net');
var urllib = require('url');
var sys = require('sys');
var sys = require('util');
var FRAME_NO = 0;
var FRAME_LO = 1;
@@ -347,7 +347,6 @@ var WebSocket = function(url, proto, opts) {
// that we've closed.
var finishClose = self.finishClose = function() {
readyState = CLOSED;
if (stream) {
stream.end();
stream.destroy();
@@ -469,31 +468,54 @@ var WebSocket = function(url, proto, opts) {
// that http.Client passes its constructor arguments through,
// un-inspected to net.Stream.connect(). The latter accepts a
// string as its first argument to connect to a UNIX socket.
var httpClient = undefined;
var opt = {};
var agent = null;
switch (getUrlScheme(url)) {
case 'ws':
var u = urllib.parse(url);
httpClient = http.createClient(u.port || 80, u.hostname);
httpPath = (u.pathname || '/') + (u.search || '');
httpHeaders.Host = u.hostname + (u.port ? (":" + u.port) : "");
agent = new http.Agent({
host: u.hostname,
port: u.port || 80
});
opt.agent = agent;
opt.host = u.hostname;
opt.port = u.port || 80;
opt.path = (u.pathname || '/') + (u.search || '');
opt.headers = httpHeaders;
break;
case 'ws+unix':
var sockPath = url.substring('ws+unix://'.length, url.length);
httpClient = http.createClient(sockPath);
httpHeaders.Host = 'localhost';
var u = urllib.parse(url);
agent = new http.Agent({
host: 'localhost',
port: sockPath
});
opt.agent = agent;
opt.host = 'localhost';
opt.path = sockPath;
opt.headers = httpHeaders;
break;
default:
throw new Error('Invalid URL scheme \'' + urlScheme + '\' specified.');
}
httpClient.on('upgrade', (function() {
var httpReq = http.request(opt, function() { });
var upgradeHandler = (function() {
var data = undefined;
return function(req, s, head) {
req.socket.setNoDelay(true);
stream = s;
if (readyState == CLOSED) {
stream.end();
stream.destroy();
stream = undefined;
return;
}
stream.on('data', function(d) {
if (d.length <= 0) {
return;
@@ -547,7 +569,7 @@ var WebSocket = function(url, proto, opts) {
//
// XXX: This is lame. We should only remove the listeners
// that we added.
httpClient.removeAllListeners('upgrade');
httpReq.removeAllListeners('upgrade');
stream.removeAllListeners('data');
stream.on('data', dataListener);
@@ -575,13 +597,9 @@ var WebSocket = function(url, proto, opts) {
stream.emit('data', head);
};
})());
httpClient.on('error', function(e) {
httpClient.end();
errorListener(e);
});
var httpReq = httpClient.request(httpPath, httpHeaders);
})();
agent.on('upgrade', upgradeHandler); // node v0.4
httpReq.on('upgrade', upgradeHandler); // node v0.5+
httpReq.write(challenge, 'binary');
httpReq.end();

View File

@@ -9,10 +9,11 @@
* Test dependencies.
*/
var io = require('socket.io')
var io = require('../')
, parser = io.parser
, http = require('http')
, https = require('https');
, https = require('https')
, WebSocket = require('../support/node-websocket-client/lib/websocket').WebSocket;
/**
* Exports.
@@ -53,10 +54,10 @@ HTTPClient.prototype.request = function (path, opts, fn) {
opts.host = 'localhost';
opts.port = this.port;
opts.path = path.replace(/{protocol}/g, io.protocol);
opts.headers = {
'Host': 'localhost'
, 'Connection': 'keep-alive'
};
opts.headers = opts.headers || {};
opts.headers.Host = 'localhost';
opts.headers.Connection = 'keep-alive';
var req = http.request(opts, function (res) {
if (false === opts.buffer)
@@ -90,8 +91,25 @@ HTTPClient.prototype.request = function (path, opts, fn) {
*/
HTTPClient.prototype.end = function () {
this.agent.sockets.forEach(function (socket) {
socket.end();
// node <v0.5 compat
if (this.agent.sockets.forEach) {
this.agent.sockets.forEach(function (socket) {
if (socket.end) socket.end();
});
return;
}
// node >=v0.5 compat
var self = this;
Object.keys(this.agent.sockets).forEach(function (socket) {
for (var i = 0, l = self.agent.sockets[socket].length; i < l; ++i) {
if (self.agent.sockets[socket][i]._handle) {
if (self.agent.sockets[socket][i]._handle.socket) {
self.agent.sockets[socket][i]._handle.socket.end();
} else {
self.agent.sockets[socket][i]._handle.owner.end();
}
}
}
});
};
@@ -146,6 +164,24 @@ HTTPClient.prototype.post = function (path, data, opts, fn) {
return this.request(path, opts, fn);
};
/**
* Issue a HEAD request
*
* @api private
*/
HTTPClient.prototype.head = function (path, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.method = 'HEAD';
return this.request(path, opts, fn);
};
/**
* Performs a handshake (GET) request
*
@@ -178,6 +214,65 @@ client = function (port) {
*/
create = function (cl) {
console.error('');
return io.listen(cl.port);
var manager = io.listen(cl.port);
manager.set('client store expiration', 0);
return manager;
};
/**
* WebSocket socket.io client.
*
* @api private
*/
function WSClient (port, sid, transport) {
this.sid = sid;
this.port = port;
this.transportName = transport || 'websocket';
WebSocket.call(
this
, 'ws://localhost:' + port + '/socket.io/'
+ io.protocol + '/' + this.transportName + '/' + sid
);
};
/**
* Inherits from WebSocket.
*/
WSClient.prototype.__proto__ = WebSocket.prototype;
/**
* Overrides message event emission.
*
* @api private
*/
WSClient.prototype.emit = function (name) {
var args = arguments;
if (name == 'message' || name == 'data') {
args[1] = parser.decodePacket(args[1].toString());
}
return WebSocket.prototype.emit.apply(this, arguments);
};
/**
* Writes a packet
*/
WSClient.prototype.packet = function (pack) {
this.write(parser.encodePacket(pack));
return this;
};
/**
* Creates a websocket client.
*
* @api public
*/
websocket = function (cl, sid, transport) {
return new WSClient(cl.port, sid, transport);
};

99
test/hybi-common.js Normal file
View File

@@ -0,0 +1,99 @@
/**
* Returns a Buffer from a "ff 00 ff"-type hex string.
*/
getBufferFromHexString = function(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;
}
/**
* Returns a hex string from a Buffer.
*/
getHexStringFromBuffer = function(data) {
var s = '';
for (var i = 0; i < data.length; ++i) {
s += padl(data[i].toString(16), 2, '0') + ' ';
}
return s.trim();
}
/**
* Splits a buffer in two parts.
*/
splitBuffer = function(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 or buffer.
*/
mask = function(buf, maskString) {
if (typeof buf == 'string') buf = new Buffer(buf);
var mask = getBufferFromHexString(maskString || '34 83 a8 68');
for (var i = 0; i < buf.length; ++i) {
buf[i] ^= mask[i % 4];
}
return buf;
}
/**
* Returns a hex string representing the length of a message
*/
getHybiLengthAsHexString = function(len, masked) {
if (len < 126) {
var buf = new Buffer(1);
buf[0] = (masked ? 0x80 : 0) | len;
}
else if (len < 65536) {
var buf = new Buffer(3);
buf[0] = (masked ? 0x80 : 0) | 126;
getBufferFromHexString(pack(4, len)).copy(buf, 1);
}
else {
var buf = new Buffer(9);
buf[0] = (masked ? 0x80 : 0) | 127;
getBufferFromHexString(pack(16, len)).copy(buf, 1);
}
return getHexStringFromBuffer(buf);
}
/**
* Unpacks a Buffer into a number.
*/
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;
}
/**
* Returns a hex string, representing a specific byte count 'length', from a number.
*/
pack = function(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'.
*/
padl = function(s, n, c) {
return new Array(1 + n - s.length).join(c) + s;
}

View File

@@ -9,7 +9,7 @@
* Test dependencies.
*/
var sio = require('socket.io')
var sio = require('../')
, fs = require('fs')
, http = require('http')
, https = require('https')
@@ -116,10 +116,10 @@ module.exports = {
io.server.close();
done();
});
done();
} catch (e) {
e.should.match(/EACCES/);
done();
}
}
};

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>
@@ -9,7 +8,7 @@
* Test dependencies.
*/
var sio = require('socket.io')
var sio = require('../')
, http = require('http')
, should = require('./common')
, ports = 15100;
@@ -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();
@@ -104,6 +117,7 @@ module.exports = {
io.disable('foo');
calls.should.eql(3);
done();
},
@@ -134,215 +148,6 @@ module.exports = {
});
},
'test that the client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that the client etag is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.enable('browser client etag');
});
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that the cached client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
var static = sio.Manager.static;
static.cache['/socket.io.js'].content.should.match(/XMLHttpRequest/);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
});
},
'test that the cached client etag is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.enable('browser client etag');
});
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
var static = sio.Manager.static
, cache = static.cache['/socket.io.js'];
cache.content.toString().should.match(/XMLHttpRequest/);
Buffer.isBuffer(cache.content).should.be.true;
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
});
},
'test that client minification works': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.enable('browser client minification');
});
cl.get('/socket.io/socket.io.js', function (res, data) {
var length = data.length;
cl.end();
io.server.close();
// start a new server with minification enabled and compare lengths
var port = ++ports
, io2 = sio.listen(port)
, cl2 = client(port);
cl2.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
data.length.should.be.greaterThan(length);
cl2.end();
io2.server.close();
done();
});
});
},
'test that the WebSocketMain.swf is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/static/flashsocket/WebSocketMain.swf', function (res, data) {
res.headers['content-type'].should.eql('application/x-shockwave-flash');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
var static = sio.Manager.static
, cache = static.cache['/static/flashsocket/WebSocketMain.swf'];
Buffer.isBuffer(cache.content).should.be.true;
cl.end();
io.server.close();
done();
});
},
'test that the WebSocketMainInsecure.swf is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/static/flashsocket/WebSocketMainInsecure.swf', function (res, data) {
res.headers['content-type'].should.eql('application/x-shockwave-flash');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
var static = sio.Manager.static
, cache = static.cache['/static/flashsocket/WebSocketMain.swf'];
Buffer.isBuffer(cache.content).should.be.true;
cl.end();
io.server.close();
done();
});
},
'test that you can serve custom clients': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('browser client handler', function (req, res) {
res.writeHead(200, {
'Content-Type': 'application/javascript'
, 'Content-Length': 13
, 'ETag': '1.0'
});
res.end('custom_client');
});
});
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.eql(13);
res.headers.etag.should.eql('1.0');
data.should.eql('custom_client');
cl.end();
io.server.close();
done();
});
},
'test that you can disable clients': function (done) {
var port = ++ports
, io = sio.listen(port)
@@ -438,6 +243,128 @@ 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 a referer with implicit port 80 is accepted for foo.bar.com:80 origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:80');
});
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 handshake cross domain access control': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port)
, headers = {
Origin: 'http://example.org:1337'
, Cookie: 'name=value'
};
cl.get('/socket.io/{protocol}/', { headers:headers }, function (res, data) {
res.statusCode.should.eql(200);
res.headers['access-control-allow-origin'].should.eql('http://example.org:1337');
res.headers['access-control-allow-credentials'].should.eql('true');
cl.end();
io.server.close();
done();
});
},
'test limiting the supported transports for a manager': function (done) {
var port = ++ports
, io = sio.listen(port)
@@ -513,6 +440,150 @@ module.exports = {
io.server.close();
done();
});
}
},
'test disabling heartbeats': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, 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;
ws.finishClose();
cl.end();
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);
process.nextTick(function() {
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');
process.nextTick(function() {
io.server.close();
done();
});
},
'test disabling the log': function (done) {
var port = ++ports
, io = sio.listen(port, { log: false })
, _console = console.log
, calls = 0;
// the logger uses console.log to output data, override it to see if get's
// used
console.log = function () { ++calls };
io.log.debug('test');
io.log.log('testing');
console.log = _console;
calls.should.equal(0);
process.nextTick(function() {
io.server.close();
done();
});
},
'test disabling logging with colors': function (done) {
var port = ++ports
, io = sio.listen(port, { 'log colors': false })
, _console = console.log
, calls = 0;
// the logger uses console.log to output data, override it to see if get's
// used
console.log = function (data) {
++calls;
data.indexOf('\033').should.equal(-1);
};
io.log.debug('test');
io.log.log('testing');
console.log = _console;
calls.should.equal(2);
process.nextTick(function() {
io.server.close();
done();
});
}
};

327
test/namespace.test.js Normal file
View File

@@ -0,0 +1,327 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, should = require('./common')
, ports = 15700;
/**
* Test.
*/
module.exports = {
'namespace pass no authentication': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.of('/a')
.on('connection', function (socket) {
cl.end();
ws.finishClose();
io.server.close()
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/a'
});
})
});
},
'namespace pass authentication': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.of('/a')
.authorization(function (data, fn) {
fn(null, true);
})
.on('connection', function (socket) {
cl.end();
ws.finishClose();
io.server.close()
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/a'
});
})
});
},
'namespace authentication handshake data': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.of('/a')
.authorization(function (data, fn) {
data.foo = 'bar';
fn(null, true);
})
.on('connection', function (socket) {
(!!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/);
socket.handshake.foo.should.equal('bar');
cl.end();
ws.finishClose();
io.server.close()
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/a'
});
})
});
},
'namespace fail authentication': function (done) {
var cl = client(++ports)
, io = create(cl)
, calls = 0
, ws;
io.of('/a')
.authorization(function (data, fn) {
fn(null, false);
})
.on('connection', function (socket) {
throw new Error('Should not be called');
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/a'
});
});
ws.on('message', function (data) {
if (data.endpoint == '/a') {
data.type.should.eql('error');
data.reason.should.eql('unauthorized')
cl.end();
ws.finishClose();
io.server.close()
done();
}
})
});
},
'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){
if (connect < 2) {
return;
}
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('message', function (data) {
if (data.type === 'connect') {
if (connect == 0) {
cl.handshake(function (sid) {
ws2 = websocket(cl, sid);
ws2.on('open', function () {
ws2.packet({
type: 'connect'
, endpoint: 'a'
});
});
});
}
++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();
}
});
ws1.on('open', function() {
ws1.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();
}
});
})
},
'ignoring blacklisted events': function (done) {
var cl = client(++ports)
, io = create(cl)
, calls = 0
, ws;
io.set('heartbeat interval', 1);
io.set('blacklist', ['foobar']);
io.sockets.on('connection', function (socket) {
socket.on('foobar', function () {
calls++;
});
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function (){
ws.packet({
type: 'event'
, name: 'foobar'
, endpoint: ''
});
});
ws.on('message', function (data) {
if (data.type === 'heartbeat') {
cl.end();
ws.finishClose();
io.server.close();
calls.should.equal(0);
done();
}
});
});
},
'disconnecting from namespace only': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws1
, ws2;
io.of('/foo').on('connection', function (socket) {
socket.disconnect();
});
cl.handshake(function (sid) {
ws1 = websocket(cl, sid);
ws1.on('open', function () {
ws1.packet({
type: 'connect'
, endpoint: '/bar'
});
cl.handshake(function (sid) {
ws2 = websocket(cl, sid);
ws2.on('open', function () {
ws2.packet({
type: 'connect'
, endpoint: '/foo'
});
});
ws2.on('message', function (data) {
if ('disconnect' === data.type) {
cl.end();
ws1.finishClose();
ws2.finishClose();
io.server.close();
data.endpoint.should.eql('/foo');
done();
}
});
});
});
});
}
};

View File

@@ -3,7 +3,7 @@
* Test dependencies.
*/
var parser = require('socket.io').parser
var parser = require('../').parser
, decode = parser.decode
, should = require('./common');
@@ -95,7 +95,7 @@ module.exports = {
'decoding json packet with message id and ack data': function () {
parser.decodePacket('4:1+::{"a":"b"}').should.eql({
type: 'json'
, id: 1
, id: '1'
, ack: 'data'
, endpoint: ''
, data: { a: 'b' }
@@ -114,7 +114,7 @@ module.exports = {
'decoding an event packet with message id and ack': function () {
parser.decodePacket('5:1+::{"name":"tobi"}').should.eql({
type: 'event'
, id: 1
, id: '1'
, ack: 'data'
, endpoint: ''
, name: 'tobi'
@@ -143,7 +143,7 @@ module.exports = {
'decoding a message packet with id and endpoint': function () {
parser.decodePacket('3:5:/tobi').should.eql({
type: 'message'
, id: 5
, id: '5'
, ack: true
, endpoint: '/tobi'
, data: ''
@@ -245,7 +245,7 @@ module.exports = {
'encoding json packet with message id and ack data': function () {
parser.encodePacket({
type: 'json'
, id: 1
, id: '1'
, ack: 'data'
, endpoint: ''
, data: { a: 'b' }
@@ -264,7 +264,7 @@ module.exports = {
'encoding an event packet with message id and ack': function () {
parser.encodePacket({
type: 'event'
, id: 1
, id: '1'
, ack: 'data'
, endpoint: ''
, name: 'tobi'
@@ -292,7 +292,7 @@ module.exports = {
'encoding a message packet with id and endpoint': function () {
parser.encodePacket({
type: 'message'
, id: 5
, id: '5'
, ack: true
, endpoint: '/tobi'
, data: ''
@@ -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

@@ -1,15 +0,0 @@
/**
* Test dependencies.
*/
var sio = require('socket.io')
, should = require('./common');
/**
* Test.
*/
module.exports = {
};

549
test/static.test.js Normal file
View File

@@ -0,0 +1,549 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('../')
, should = require('./common')
, ports = 15400;
/**
* Test.
*/
module.exports = {
'test that the default static files are available': function (done) {
var port = ++ports
, io = sio.listen(port);
(!!io.static.has('/socket.io.js')).should.be.true;
(!!io.static.has('/socket.io.v1.0.0.js')).should.be.true;
(!!io.static.has('/socket.io+xhr-polling.js')).should.be.true;
(!!io.static.has('/socket.io+xhr-polling.v1.0.0.js')).should.be.true;
(!!io.static.has('/static/flashsocket/WebSocketMain.swf')).should.be.true;
(!!io.static.has('/static/flashsocket/WebSocketMainInsecure.swf')).should.be.true;
process.nextTick(function() {
io.server.close();
done();
});
},
'test that static files are correctly looked up': function (done) {
var port = ++ports
, io = sio.listen(port);
(!!io.static.has('/socket.io.js')).should.be.true;
(!!io.static.has('/invalidfilehereplease.js')).should.be.false;
process.nextTick(function() {
io.server.close();
done();
});
},
'test that the client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that the custom build client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io+websocket.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);
cl.end();
io.server.close();
done();
});
},
'test that the client is build with the enabled transports': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('transports', ['websocket']);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);
cl.end();
io.server.close();
done();
});
},
'test that the client cache is cleared when transports change': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('transports', ['websocket']);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);
io.set('transports', ['xhr-polling']);
should.strictEqual(io.static.cache['/socket.io.js'], undefined);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
data.should.match(/XMLHttpRequest/);
data.should.match(/XHRPolling\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/WS\.prototype\.name/);
cl.end();
io.server.close();
done();
});
});
},
'test that the client etag is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that the client etag is changed for new transports': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('transports', ['websocket']);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.js', function (res, data) {
var wsEtag = res.headers.etag;
io.set('transports', ['xhr-polling']);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers.etag.should.not.equal(wsEtag);
cl.end();
io.server.close();
done();
});
});
},
'test that the client is served with gzip': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client gzip');
cl.get('/socket.io/socket.io.js', {
headers: {
'accept-encoding': 'deflate, gzip'
}
}
, function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-encoding'].should.eql('gzip');
res.headers['content-length'].should.match(/([0-9]+)/);
cl.end();
io.server.close();
done();
}
);
},
'test that the cached client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
var static = io.static;
static.cache['/socket.io.js'].content.should.match(/XMLHttpRequest/);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
});
},
'test that the client is not cached': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.static.add('/random.js', function (path, callback) {
var random = Math.floor(Date.now() * Math.random()).toString();
callback(null, new Buffer(random));
});
io.disable('browser client cache');
cl.get('/socket.io/random.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
cl.get('/socket.io/random.js', function (res, random) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.not.equal(random);
cl.end();
io.server.close();
done();
});
});
},
'test that the cached client etag is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
var static = io.static
, cache = static.cache['/socket.io.js'];
cache.content.toString().should.match(/XMLHttpRequest/);
Buffer.isBuffer(cache.content).should.be.true;
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
});
},
'test that the cached client sends a 304 header': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.js', function (res, data) {
cl.get('/socket.io/socket.io.js', {
headers: {
'if-none-match': res.headers.etag
}
}, function (res, data) {
res.statusCode.should.eql(304);
cl.end();
io.server.close();
done();
}
);
});
},
'test that client minification works': function (done) {
// server 1
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
// server 2
var port = ++ports
, io2 = sio.listen(port)
, cl2 = client(port);
io.enable('browser client minification');
cl.get('/socket.io/socket.io.js', function (res, data) {
var length = data.length;
cl.end();
io.server.close();
cl2.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
data.length.should.be.greaterThan(length);
cl2.end();
io2.server.close();
done();
});
});
},
'test that the WebSocketMain.swf is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/static/flashsocket/WebSocketMain.swf', function (res, data) {
res.headers['content-type'].should.eql('application/x-shockwave-flash');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
var static = io.static
, cache = static.cache['/static/flashsocket/WebSocketMain.swf'];
Buffer.isBuffer(cache.content).should.be.true;
cl.end();
io.server.close();
done();
});
},
'test that the WebSocketMainInsecure.swf is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/static/flashsocket/WebSocketMainInsecure.swf', function (res, data) {
res.headers['content-type'].should.eql('application/x-shockwave-flash');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
var static = io.static
, cache = static.cache['/static/flashsocket/WebSocketMainInsecure.swf'];
Buffer.isBuffer(cache.content).should.be.true;
cl.end();
io.server.close();
done();
});
},
'test that swf files are not served with gzip': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client gzip');
cl.get('/socket.io/static/flashsocket/WebSocketMain.swf', {
headers: {
'accept-encoding': 'deflate, gzip'
}
}
, function (res, data) {
res.headers['content-type'].should.eql('application/x-shockwave-flash');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers['content-encoding'], undefined);
cl.end();
io.server.close();
done();
}
);
},
'test that you can serve custom clients': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('browser client handler', function (req, res) {
res.writeHead(200, {
'Content-Type': 'application/javascript'
, 'Content-Length': 13
, 'ETag': '1.0'
});
res.end('custom_client');
});
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.eql('13');
res.headers.etag.should.eql('1.0');
data.should.eql('custom_client');
cl.end();
io.server.close();
done();
});
},
'test that HEAD requests work': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.head('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
data.should.eql('');
cl.end();
io.server.close()
done();
});
},
'test that a versioned client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.v0.8.9.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that a custom versioned build client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('browser client expires', 1337);
cl.get('/socket.io/socket.io+websocket.v0.8.10.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);
data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);
cl.end();
io.server.close();
done();
});
},
'test that etags are ignored for versioned requests': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.v0.8.9.js', function (res, data) {
should.strictEqual(res.headers.etag, undefined);
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
};

190
test/stores.memory.test.js Normal file
View File

@@ -0,0 +1,190 @@
/**
* Test dependencies
*
* @api private
*/
var sio = require('../')
, should = require('should')
, MemoryStore = sio.MemoryStore;
/**
* Test.
*/
module.exports = {
'test storing data for a client': function (done) {
var store = new MemoryStore
, client = store.client('test');
client.id.should.equal('test');
client.set('a', 'b', function (err) {
should.strictEqual(err, null);
client.get('a', function (err, val) {
should.strictEqual(err, null);
val.should.eql('b');
client.has('a', function (err, has) {
should.strictEqual(err, null);
has.should.be.true;
client.has('b', function (err, has) {
should.strictEqual(err, null);
has.should.be.false;
client.del('a', function (err) {
should.strictEqual(err, null);
client.has('a', function (err, has) {
should.strictEqual(err, null);
has.should.be.false;
client.set('b', 'c', function (err) {
should.strictEqual(err, null);
client.set('c', 'd', function (err) {
should.strictEqual(err, null);
client.get('b', function (err, val) {
should.strictEqual(err, null);
val.should.equal('c');
client.get('c', function (err, val) {
should.strictEqual(err, null);
val.should.equal('d');
store.destroy();
done();
});
});
});
});
});
});
});
});
});
});
},
'test cleaning up clients data': function (done) {
var rand1 = Math.abs(Math.random() * Date.now() | 0)
, rand2 = Math.abs(Math.random() * Date.now() | 0);
var store = new MemoryStore()
, client1 = store.client(rand1)
, client2 = store.client(rand2);
client1.set('a', 'b', function (err) {
should.strictEqual(err, null);
client2.set('c', 'd', function (err) {
should.strictEqual(err, null);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
client2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
store.destroy();
var newstore = new MemoryStore()
, newclient1 = newstore.client(rand1)
, newclient2 = newstore.client(rand2);
newclient1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.false;
newclient2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.false;
newstore.destroy();
done();
});
});
});
});
});
});
},
'test cleaning up a particular client': function (done) {
var rand1 = Math.abs(Math.random() * Date.now() | 0)
, rand2 = Math.abs(Math.random() * Date.now() | 0);
var store = new MemoryStore()
, client1 = store.client(rand1)
, client2 = store.client(rand2);
client1.set('a', 'b', function (err) {
should.strictEqual(err, null);
client2.set('c', 'd', function (err) {
should.strictEqual(err, null);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
client2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
store.clients.should.have.property(rand1);
store.clients.should.have.property(rand2);
store.destroyClient(rand1);
store.clients.should.not.have.property(rand1);
store.clients.should.have.property(rand2);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal(false);
store.destroy();
done();
});
});
});
});
});
},
'test destroy expiration': function (done) {
var store = new MemoryStore()
, id = Math.abs(Math.random() * Date.now() | 0)
, client = store.client(id);
client.set('a', 'b', function (err) {
should.strictEqual(err, null);
store.destroyClient(id, 1);
setTimeout(function () {
client.get('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal('b');
});
}, 500);
setTimeout(function () {
client.get('a', function (err, val) {
should.strictEqual(err, null);
should.strictEqual(val, null);
store.destroy();
done();
});
}, 1900);
});
}
};

240
test/stores.redis.test.js Normal file
View File

@@ -0,0 +1,240 @@
/**
* Test dependencies
*
* @api private
*/
var sio = require('../')
, redis = require('redis')
, should = require('should')
, RedisStore = sio.RedisStore;
/**
* Test.
*/
module.exports = {
'test publishing doesnt get caught by the own store subscriber': function (done) {
var a = new RedisStore
, b = new RedisStore;
a.subscribe('woot', function (arg) {
arg.should.equal('bb');
a.destroy();
b.destroy();
done();
}, function () {
a.publish('woot', 'aa');
b.publish('woot', 'bb');
});
},
'test publishing to multiple subscribers': function (done) {
var a = new RedisStore
, b = new RedisStore
, c = new RedisStore
, subscriptions = 3
, messages = 2;
a.subscribe('tobi', function () {
throw new Error('Shouldnt publish to itself');
}, publish);
function subscription (arg1, arg2, arg3) {
arg1.should.equal(1);
arg2.should.equal(2);
arg3.should.equal(3);
--messages || finish();
}
b.subscribe('tobi', subscription, publish);
c.subscribe('tobi', subscription, publish);
function publish () {
--subscriptions || a.publish('tobi', 1, 2, 3);
}
function finish () {
a.destroy();
b.destroy();
c.destroy();
done();
}
},
'test storing data for a client': function (done) {
var store = new RedisStore
, rand = 'test-' + Date.now()
, client = store.client(rand);
client.id.should.equal(rand);
client.set('a', 'b', function (err) {
should.strictEqual(err, null);
client.get('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal('b');
client.has('a', function (err, has) {
should.strictEqual(err, null);
has.should.be.true;
client.has('b', function (err, has) {
should.strictEqual(err, null);
has.should.be.false;
client.del('a', function (err) {
should.strictEqual(err, null);
client.has('a', function (err, has) {
should.strictEqual(err, null);
has.should.be.false;
client.set('b', 'c', function (err) {
should.strictEqual(err, null);
client.set('c', 'd', function (err) {
should.strictEqual(err, null);
client.get('b', function (err, val) {
should.strictEqual(err, null);
val.should.equal('c');
client.get('c', function (err, val) {
should.strictEqual(err, null);
val.should.equal('d');
store.destroy();
done();
});
});
});
});
});
});
});
});
});
});
},
'test cleaning up clients data': function (done) {
var rand1 = Math.abs(Math.random() * Date.now() | 0)
, rand2 = Math.abs(Math.random() * Date.now() | 0);
var store = new RedisStore()
, client1 = store.client(rand1)
, client2 = store.client(rand2);
client1.set('a', 'b', function (err) {
should.strictEqual(err, null);
client2.set('c', 'd', function (err) {
should.strictEqual(err, null);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
client2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
store.destroy();
var newstore = new RedisStore()
, newclient1 = newstore.client(rand1)
, newclient2 = newstore.client(rand2);
newclient1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.false;
newclient2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.false;
newstore.destroy();
done();
});
});
});
});
});
});
},
'test cleaning up a particular client': function (done) {
var rand1 = Math.abs(Math.random() * Date.now() | 0)
, rand2 = Math.abs(Math.random() * Date.now() | 0);
var store = new RedisStore()
, client1 = store.client(rand1)
, client2 = store.client(rand2);
client1.set('a', 'b', function (err) {
should.strictEqual(err, null);
client2.set('c', 'd', function (err) {
should.strictEqual(err, null);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
client2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
store.clients.should.have.property(rand1);
store.clients.should.have.property(rand2);
store.destroyClient(rand1);
store.clients.should.not.have.property(rand1);
store.clients.should.have.property(rand2);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal(false);
store.destroy();
done();
});
});
});
});
});
},
'test destroy expiration': function (done) {
var store = new RedisStore()
, id = Math.abs(Math.random() * Date.now() | 0)
, client = store.client(id);
client.set('a', 'b', function (err) {
should.strictEqual(err, null);
store.destroyClient(id, 1);
setTimeout(function () {
client.get('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal('b');
});
}, 500);
setTimeout(function () {
client.get('a', function (err, val) {
should.strictEqual(err, null);
should.strictEqual(val, null);
store.destroy();
done();
});
}, 2000);
});
}
};

View File

@@ -9,7 +9,7 @@
* Test dependencies.
*/
var sio = require('socket.io')
var sio = require('../')
, net = require('net')
, http = require('http')
, should = require('./common')
@@ -30,7 +30,7 @@ function FlashSocket (port, sid) {
WebSocket.call(
this
, 'ws://localhost:' + port + '/socket.io/'
, 'ws://localhost:' + port + '/socket.io/'
+ sio.protocol + '/flashsocket/' + sid
);
};
@@ -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);
@@ -104,7 +104,7 @@ module.exports = {
netConnection(port, function (err, data){
should.strictEqual(err, null);
data.toString().should.include.string('<cross-domain-policy>');
data.toString().should.match(/<cross-domain-policy>/);
this.destroy();
io.flashPolicyServer.close();
@@ -133,7 +133,7 @@ module.exports = {
netConnection(next, function (err, data){
should.strictEqual(err, null);
data.toString().should.include.string('<cross-domain-policy>');
data.toString().should.match(/<cross-domain-policy>/);
this.destroy();
io.flashPolicyServer.close();
@@ -159,10 +159,29 @@ module.exports = {
server.origins.should.not.contain('google.com:80');
server.origins.should.contain('foo.bar:80');
server.origins.should.contain('socket.io:1337');
server.buffer.toString('utf8').should.include.string('socket.io');
server.buffer.toString('utf8').should.match(/socket\.io/);
io.flashPolicyServer.close();
done();
}
},
'flashsocket identifies as flashsocket': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.set('transports', ['flashsocket']);
io.sockets.on('connection', function (socket) {
socket.manager.transports[socket.id].name.should.equal('flashsocket');
ws.finishClose();
cl.end();
io.flashPolicyServer.close();
io.server.close();
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid, 'flashsocket');
});
}
};

View File

@@ -9,7 +9,7 @@
* Test dependencies.
*/
var sio = require('socket.io')
var sio = require('../')
, should = require('./common')
, HTTPClient = should.HTTPClient
, parser = sio.parser
@@ -77,10 +77,14 @@ HTMLFile.prototype.data = function (path, opts, fn) {
case 2:
if (buf.indexOf(foot) != -1) {
var data = buf.slice(0, buf.indexOf(foot))
, obj = JSON.parse(data);
var data = buf.slice(0, buf.indexOf(foot));
fn(obj === '' ? obj : parser.decodePayload(obj), ++messages);
if (false === opts.parse) {
fn(data, ++messages);
} else {
var obj = JSON.parse(data);
fn(obj === '' ? obj : parser.decodePayload(obj), ++messages);
}
buf = buf.substr(data.length + foot.length);
state = 1;
@@ -453,6 +457,78 @@ module.exports = {
}
});
});
},
'test escaping for security': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, messaged = false;
io.configure(function () {
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
socket.emit('</script> woot');
socket.on('disconnect', function () {
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.data('/socket.io/{protocol}/htmlfile/' + sid, { parse: false }, function (msg, i) {
switch (i) {
case 2:
msg.should.not.include('</script');
cl.end();
}
});
});
},
'test that unescaping works': function(done){
var port = ++ports
, cl = client(port)
, io = create(cl)
, messaged = false;
io.configure(function () {
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
socket.emit('woot </script> <//script>', '</script><script>');
socket.on('disconnect', function () {
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs, i) {
switch (i) {
case 1:
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
msgs[0].endpoint.should.eql('');
break;
case 2:
msgs.should.have.length(1);
msgs[0].should.eql({
type: 'event'
, name: 'woot </script> <//script>'
, endpoint: ''
, args: ['</script><script>']
});
cl.end();
}
});
});
}
};

View File

@@ -9,7 +9,7 @@
* Test dependencies.
*/
var sio = require('socket.io')
var sio = require('../')
, should = require('./common')
, qs = require('querystring')
, HTTPClient = should.HTTPClient
@@ -177,10 +177,10 @@ module.exports = {
--total || finish();
});
// we rely on a small poll duration to close this request quickly
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
data.should.eql('');
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
--total || finish();
});
});
@@ -243,7 +243,6 @@ module.exports = {
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
// here we close the request instead of relying on a small poll timeout
setTimeout(function () {
cl.end();
}, 10);
@@ -251,15 +250,14 @@ module.exports = {
});
},
'test the disconnection event when the client sends ?disconnect req':
function (done) {
'test the disconnection event when the client sends ?disconnect req': function (done) {
var cl = client(++ports)
, io = create(cl)
, disconnected = false
, sid;
io.configure(function () {
io.set('close timeout', .05);
io.set('close timeout', .1);
});
io.sockets.on('connection', function (socket) {
@@ -280,12 +278,17 @@ module.exports = {
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'disconnect', endpoint: '' });
disconnected.should.be.true;
cl.end();
io.server.close();
cl.end();
done();
});
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid + '/?disconnect');
// with the new http bits in node 0.5, there's no guarantee that
// the previous request is actually dispatched (and received) before the following
// reset call is sent. to not waste more time on a workaround, a timeout is added.
setTimeout(function() {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid + '/?disconnect');
}, 500);
});
});
},
@@ -324,10 +327,10 @@ module.exports = {
io.sockets.on('connection', function (client) {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'disconnect' })
, JSON.stringify(parser.encodePacket({ type: 'disconnect' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
@@ -391,14 +394,14 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePayload([
, JSON.stringify(parser.encodePayload([
parser.encodePacket({ type: 'message', data: 'a' })
, parser.encodePacket({ type: 'message', data: 'b' })
, parser.encodePacket({ type: 'disconnect' })
])
]))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
@@ -459,8 +462,9 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
data.should.eql('');
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
tobi();
@@ -504,10 +508,10 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/woot' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/woot' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
});
@@ -585,24 +589,24 @@ module.exports = {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function () {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/woot' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/woot' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'disconnect', endpoint: '/woot' })
, JSON.stringify(parser.encodePacket({ type: 'disconnect', endpoint: '/woot' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', data: 'ferret' })
, JSON.stringify(parser.encodePacket({ type: 'message', data: 'ferret' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
}
@@ -621,7 +625,7 @@ module.exports = {
io.configure(function () {
io.set('polling duration', .2);
io.set('close timeout', .2);
io.set('close timeout', .5);
});
io.sockets.on('connection', function (socket) {
@@ -649,25 +653,26 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
data.should.eql('');
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/a' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/a' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/b' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/b' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
});
@@ -720,26 +725,26 @@ module.exports = {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', data: '' })
, JSON.stringify(parser.encodePacket({ type: 'message', data: '' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/a' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/a' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', endpoint: '/a', data: 'a' })
, JSON.stringify(parser.encodePacket({ type: 'message', endpoint: '/a', data: 'a' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
}
@@ -747,17 +752,17 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/b' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/b' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', endpoint: '/b', data: 'b' })
, JSON.stringify(parser.encodePacket({ type: 'message', endpoint: '/b', data: 'b' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
}

View File

@@ -0,0 +1,262 @@
/**
* Test dependencies.
*/
var assert = require('assert');
var Parser = require('../lib/transports/websocket/hybi-07-12.js').Parser;
require('./hybi-common');
/**
* 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(getBufferFromHexString(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(getBufferFromHexString(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(getBufferFromHexString(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 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(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 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(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 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet1));
p.add(getBufferFromHexString(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 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(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(getBufferFromHexString(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 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(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(getBufferFromHexString(packet1));
p.add(getBufferFromHexString(pingPacket));
p.add(getBufferFromHexString(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 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(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(getBufferFromHexString(packet1)));
buffers = buffers.concat(splitBuffer(getBufferFromHexString(pingPacket)));
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet2)));
for (var i = 0; i < buffers.length; ++i) {
p.add(buffers[i]);
}
assert.ok(gotData);
assert.ok(gotPing);
},
'can parse a 100 byte long masked binary message': function() {
var p = new Parser();
var length = 100;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 256 byte long masked binary message': function() {
var p = new Parser();
var length = 256;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 200kb long masked binary message': function() {
var p = new Parser();
var length = 200 * 1024;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 200kb long unmasked binary message': function() {
var p = new Parser();
var length = 200 * 1024;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message);
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
};

View File

@@ -0,0 +1,262 @@
/**
* Test dependencies.
*/
var assert = require('assert');
var Parser = require('../lib/transports/websocket/hybi-16.js').Parser;
require('./hybi-common');
/**
* 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(getBufferFromHexString(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(getBufferFromHexString(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(getBufferFromHexString(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 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(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 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(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 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet1));
p.add(getBufferFromHexString(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 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(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(getBufferFromHexString(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 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(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(getBufferFromHexString(packet1));
p.add(getBufferFromHexString(pingPacket));
p.add(getBufferFromHexString(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 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(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(getBufferFromHexString(packet1)));
buffers = buffers.concat(splitBuffer(getBufferFromHexString(pingPacket)));
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet2)));
for (var i = 0; i < buffers.length; ++i) {
p.add(buffers[i]);
}
assert.ok(gotData);
assert.ok(gotPing);
},
'can parse a 100 byte long masked binary message': function() {
var p = new Parser();
var length = 100;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 256 byte long masked binary message': function() {
var p = new Parser();
var length = 256;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 200kb long masked binary message': function() {
var p = new Parser();
var length = 200 * 1024;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 200kb long unmasked binary message': function() {
var p = new Parser();
var length = 200 * 1024;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message);
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
};

View File

@@ -9,87 +9,242 @@
* Test dependencies.
*/
var sio = require('socket.io')
var sio = require('../')
, should = require('./common')
, HTTPClient = should.HTTPClient
, WebSocket = require('../support/node-websocket-client/lib/websocket').WebSocket
, parser = sio.parser
, ports = 15400;
/**
* Exports WSClient.
*/
module.exports = exports = WSClient;
/**
* WebSocket socket.io client.
*
* @api private
*/
function WSClient (port, sid) {
this.sid = sid;
this.port = port;
WebSocket.call(
this
, 'ws://localhost:' + port + '/socket.io/'
+ sio.protocol + '/websocket/' + sid
);
};
/**
* Inherits from WebSocket.
*/
WSClient.prototype.__proto__ = WebSocket.prototype;
/**
* Overrides message event emission.
*
* @api private
*/
WSClient.prototype.emit = function (name) {
var args = arguments;
if (name == 'message' || name == 'data') {
args[1] = parser.decodePacket(args[1].toString());
}
return WebSocket.prototype.emit.apply(this, arguments);
};
/**
* Writes a packet
*/
WSClient.prototype.packet = function (pack) {
this.write(parser.encodePacket(pack));
return this;
};
/**
* Creates a websocket client.
*
* @api public
*/
function websocket (cl, sid) {
return new WSClient(cl.port, sid);
};
, ports = 15800;
/**
* Tests.
*/
module.exports = {
'websocket identifies as websocket': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.set('transports', ['websocket']);
io.on('connection', function (socket) {
socket.manager.transports[socket.id].name.should.equal('websocket');
ws.finishClose();
cl.end();
io.server.close();
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
});
},
'default websocket draft parser is used for unknown sec-websocket-version': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.set('transports', ['websocket']);
io.sockets.on('connection', function (socket) {
socket.manager.transports[socket.id].protocolVersion.should.equal('hixie-76');
ws.finishClose();
cl.end();
io.server.close();
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
});
},
'hybi-07-12 websocket draft parser is used for sec-websocket-version: 8': function (done) {
var cl = client(++ports)
, io = create(cl);
io.set('transports', ['websocket']);
io.sockets.on('connection', function (socket) {
socket.manager.transports[socket.id].protocolVersion.should.equal('07-12');
cl.end();
io.server.close();
done();
});
var headers = {
'sec-websocket-version': 8,
'upgrade': 'websocket',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
}
cl.get('/socket.io/{protocol}', {}, function (res, data) {
var sid = data.split(':')[0];
var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
cl.get(url, {headers: headers}, function (res, data) {});
});
},
'hybi-16 websocket draft parser is used for sec-websocket-version: 13': function (done) {
var cl = client(++ports)
, io = create(cl)
io.set('transports', ['websocket']);
io.sockets.on('connection', function (socket) {
socket.manager.transports[socket.id].protocolVersion.should.equal('16');
cl.end();
io.server.close();
done();
});
var headers = {
'sec-websocket-version': 13,
'upgrade': 'websocket',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
}
cl.get('/socket.io/{protocol}', {}, function (res, data) {
var sid = data.split(':')[0];
var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
cl.get(url, {headers: headers}, function (res, data) {});
});
},
'hybi-07-12 origin filter blocks access for mismatched sec-websocket-origin': function (done) {
var cl = client(++ports)
, io = create(cl)
io.set('transports', ['websocket']);
io.set('origins', 'foo.bar.com:*');
var notConnected = true;
io.sockets.on('connection', function() {
notConnected = false;
});
var headers = {
'sec-websocket-version': 8,
'upgrade': 'websocket',
'Sec-WebSocket-Origin': 'http://baz.bar.com',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
}
// handshake uses correct origin -- we want to block the actual websocket call
cl.get('/socket.io/{protocol}', {headers: {origin: 'http://foo.bar.com'}}, function (res, data) {
var sid = data.split(':')[0];
var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
var req = cl.get(url, {headers: headers}, function (res, data) {});
var closed = false;
(req.socket || req).on('close', function() {
if (closed) return;
closed = true;
notConnected.should.be.true;
cl.end();
try {
io.server.close();
}
catch (e) {}
done();
});
});
},
'hybi-16 origin filter blocks access for mismatched sec-websocket-origin': function (done) {
var cl = client(++ports)
, io = create(cl);
io.set('transports', ['websocket']);
io.set('origins', 'foo.bar.com:*');
var notConnected = true;
io.sockets.on('connection', function() {
notConnected = false;
});
var headers = {
'sec-websocket-version': 13,
'upgrade': 'websocket',
'origin': 'http://baz.bar.com',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
}
// handshake uses correct origin -- we want to block the actual websocket call
cl.get('/socket.io/{protocol}', {headers: {origin: 'http://foo.bar.com'}}, function (res, data) {
var sid = data.split(':')[0];
var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
var req = cl.get(url, {headers: headers}, function (res, data) {});
var closed = false;
(req.socket || req).on('close', function() {
if (closed) return;
closed = true;
notConnected.should.be.true;
cl.end();
try {
io.server.close();
}
catch (e) {}
done();
});
});
},
'hybi-07-12 origin filter accepts implicit port 80 for sec-websocket-origin': function (done) {
done();return;
var cl = client(++ports)
, io = create(cl)
io.set('transports', ['websocket']);
io.set('origins', 'foo.bar.com:80');
var headers = {
'sec-websocket-version': 8,
'upgrade': 'websocket',
'Sec-WebSocket-Origin': 'http://foo.bar.com',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
}
io.sockets.on('connection', function() {
cl.end();
io.server.close();
done();
});
// handshake uses correct origin -- we want to block the actual websocket call
cl.get('/socket.io/{protocol}', {headers: {origin: 'http://foo.bar.com'}}, function (res, data) {
var sid = data.split(':')[0];
var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
cl.get(url, {headers: headers}, function (res, data) {});
});
},
'hybi-16 origin filter accepts implicit port 80 for sec-websocket-origin': function (done) {
var cl = client(++ports)
, io = create(cl)
io.set('transports', ['websocket']);
io.set('origins', 'foo.bar.com:80');
var headers = {
'sec-websocket-version': 13,
'upgrade': 'websocket',
'origin': 'http://foo.bar.com',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
}
io.sockets.on('connection', function() {
cl.end();
io.server.close();
done();
});
// handshake uses correct origin -- we want to block the actual websocket call
cl.get('/socket.io/{protocol}', {headers: {origin: 'http://foo.bar.com'}}, function (res, data) {
var sid = data.split(':')[0];
var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
cl.get(url, {headers: headers}, function (res, data) {});
});
},
'test that not responding to a heartbeat drops client': function (done) {
var cl = client(++ports)
, io = create(cl)
, messages = 0;
, messages = 0
, ws;
io.configure(function () {
io.set('heartbeat interval', .05);
@@ -103,13 +258,14 @@ module.exports = {
reason.should.eql('heartbeat timeout');
cl.end();
ws.finishClose();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
var ws = websocket(cl, sid);
ws = websocket(cl, sid);
ws.on('message', function (packet) {
if (++messages == 1) {
packet.type.should.eql('connect');
@@ -125,7 +281,8 @@ module.exports = {
var cl = client(++ports)
, io = create(cl)
, messages = 0
, heartbeats = 0;
, heartbeats = 0
, ws;
io.configure(function () {
io.set('heartbeat interval', .05);
@@ -139,13 +296,14 @@ module.exports = {
reason.should.eql('heartbeat timeout');
cl.end();
ws.finishClose();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
var ws = websocket(cl, sid);
ws = websocket(cl, sid);
ws.on('message', function (packet) {
if (++messages == 1) {
packet.type.should.eql('connect');
@@ -161,134 +319,6 @@ module.exports = {
});
},
'test sending undeliverable volatile messages': function (done) {
var cl = client(++ports)
, io = create(cl)
, messages = 0
, messaged = false
, s;
io.configure(function () {
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
s = socket;
socket.on('disconnect', function () {
messaged.should.be.false;
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
var ws = websocket(cl, sid);
ws.on('message', function (msg) {
msg.type.should.eql('connect');
ws.finishClose();
setTimeout(function () {
s.volatile.send('ah wha wha');
ws = websocket(cl, sid);
ws.on('message', function () {
messaged = true;
});
setTimeout(function () {
ws.finishClose();
}, 10);
}, 10);
});
});
},
'test sending undeliverable volatile json': function (done) {
var cl = client(++ports)
, io = create(cl)
, messaged = false
, s;
io.configure(function () {
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
s = socket;
socket.on('disconnect', function () {
messaged.should.be.false;
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
var ws = websocket(cl, sid);
ws.on('message', function () {
ws.finishClose();
setTimeout(function () {
s.volatile.json.send({ a: 'b' });
ws = websocket(cl, sid);
ws.on('message', function () {
messaged = true;
});
setTimeout(function () {
ws.finishClose();
}, 10);
}, 10);
});
});
},
'test sending undeliverable volatile events': function (done) {
var cl = client(++ports)
, io = create(cl)
, messaged = false
, s;
io.configure(function () {
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
s = socket;
socket.on('disconnect', function () {
messaged.should.be.false;
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
var ws = websocket(cl, sid);
ws.on('message', function () {
ws.finishClose();
setTimeout(function () {
s.volatile.emit({ a: 'b' });
ws = websocket(cl, sid);
ws.on('message', function () {
messaged = true;
});
setTimeout(function () {
ws.finishClose();
}, 10);
}, 10);
});
});
},
'test sending deliverable volatile messages': function (done) {
var cl = client(++ports)
, io = create(cl)
@@ -644,16 +674,15 @@ module.exports = {
connections++;
if (connections != 3) {
socket.join('woot', function () {
joins++;
socket.join('woot');
joins++;
if (joins == 2) {
setTimeout(function () {
connections.should.eql(3);
io.sockets.in('woot').send('hahaha');
}, 20);
}
});
if (joins == 2) {
setTimeout(function () {
connections.should.eql(3);
io.sockets.in('woot').send('hahaha');
}, 20);
}
}
socket.on('disconnect', function () {
@@ -756,16 +785,15 @@ module.exports = {
connections++;
if (connections != 3) {
socket.join('woot', function () {
joins++;
socket.join('woot');
joins++;
if (joins == 2) {
setTimeout(function () {
connections.should.eql(3);
io.sockets.in('woot').json.send(123);
}, 20);
}
});
if (joins == 2) {
setTimeout(function () {
connections.should.eql(3);
io.sockets.in('woot').json.send(123);
}, 20);
}
}
socket.on('disconnect', function () {
@@ -868,16 +896,15 @@ module.exports = {
connections++;
if (connections != 3) {
socket.join('woot', function () {
joins++;
socket.join('woot');
joins++;
if (joins == 2) {
setTimeout(function () {
connections.should.eql(3);
io.sockets.in('woot').emit('locki');
}, 20);
}
});
if (joins == 2) {
setTimeout(function () {
connections.should.eql(3);
io.sockets.in('woot').emit('locki');
}, 20);
}
}
socket.on('disconnect', function () {
@@ -964,6 +991,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)
@@ -1268,8 +1348,9 @@ module.exports = {
io.sockets.on('connection', function (socket) {
connections++;
if (connections == 1)
if (connections == 1) {
socket.join('losers');
}
socket.on('trigger broadcast', function () {
socket.broadcast.to('losers').send('boom');
@@ -1367,8 +1448,9 @@ module.exports = {
io.sockets.on('connection', function (socket) {
connections++;
if (connections == 1)
if (connections == 1) {
socket.join('losers');
}
socket.on('trigger broadcast', function () {
socket.broadcast.json.to('losers').send({ hello: 'world' });
@@ -1548,6 +1630,265 @@ module.exports = {
});
});
});
},
'test accessing handshake data from sockets': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.sockets.on('connection', function (socket) {
(!!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/);
socket.on('disconnect', function () {
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 accessing the array of clients': function (done) {
var port = ++ports
, cl1 = client(port)
, cl2 = client(port)
, io = create(cl1)
, total = 2
, ws1, ws2;
io.sockets.on('connection', function (socket) {
socket.on('join ferrets', function () {
socket.join('ferrets');
socket.send('done');
});
});
function check() {
io.sockets.clients('ferrets').should.have.length(1);
io.sockets.clients('ferrets')[0].should.be.an.instanceof(sio.Socket);
io.sockets.clients('ferrets')[0].id.should.equal(ws1.sid);
io.sockets.clients().should.have.length(2);
io.sockets.clients()[0].should.be.an.instanceof(sio.Socket);
io.sockets.clients()[0].id.should.equal(ws1.sid);
io.sockets.clients()[1].should.be.an.instanceof(sio.Socket);
io.sockets.clients()[1].id.should.equal(ws2.sid);
ws1.finishClose();
ws2.finishClose();
cl1.end();
cl2.end();
io.server.close();
done();
};
cl1.handshake(function (sid) {
ws1 = websocket(cl1, sid);
ws1.sid = sid;
ws1.on('message', function (msg) {
if (!ws1.connected) {
msg.type.should.eql('connect');
ws1.connected = true;
ws1.packet({
type: 'event'
, name: 'join ferrets'
, endpoint: ''
});
} else {
cl2.handshake(function (sid) {
ws2 = websocket(cl2, sid);
ws2.sid = sid;
ws2.on('message', function (msg) {
if (!ws2.connected) {
msg.type.should.eql('connect');
ws2.connected = true;
check();
}
});
});
}
});
});
},
'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)
, calls = 0
, ws;
function close () {
cl.end();
io.server.close();
ws.finishClose();
done();
}
io.configure(function () {
io.set('heartbeat interval', .05);
io.set('heartbeat timeout', .05);
io.set('close timeout', 0);
});
io.of('/foo').on('connection', function (socket) {
socket.on('disconnect', function (reason) {
reason.should.equal('packet');
if (++calls == 2) close();
});
});
io.of('/bar').on('connection', function (socket) {
socket.on('disconnect', function (reason) {
reason.should.equal('socket end');
if (++calls == 2) close();
});
});
cl.handshake(function (sid) {
var messages = 0;
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/foo'
});
ws.packet({
type: 'connect'
, endpoint: '/bar'
});
});
ws.on('message', function (packet) {
if (packet.type == 'connect') {
if (++messages === 3) {
ws.packet({ type: 'disconnect', endpoint:'/foo' });
ws.finishClose();
}
}
});
});
},
'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;
}
});
});
},
'accessing the transport type': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.sockets.on('connection', function (socket) {
socket.transport.should.equal('websocket');
socket.on('disconnect', function () {
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;
}
});
});
}
};

View File

@@ -9,7 +9,7 @@
* Test dependencies.
*/
var sio = require('socket.io')
var sio = require('../')
, should = require('./common')
, HTTPClient = should.HTTPClient
, parser = sio.parser
@@ -113,10 +113,10 @@ module.exports = {
--total || finish();
});
// we rely on a small poll duration to close this request quickly
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
data.should.eql('');
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
--total || finish();
});
});
@@ -179,7 +179,6 @@ module.exports = {
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
// here we close the request instead of relying on a small poll timeout
setTimeout(function () {
cl.end();
}, 10);
@@ -221,7 +220,12 @@ module.exports = {
done();
});
cl.get('/socket.io/{protocol}/xhr-polling/' + sid + '/?disconnect');
// with the new http bits in node 0.5, there's no guarantee that
// the previous request is actually dispatched (and received) before the following
// reset call is sent. to not waste more time on a workaround, a timeout is added.
setTimeout(function() {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid + '/?disconnect');
}, 500);
});
});
},
@@ -263,7 +267,7 @@ module.exports = {
, parser.encodePacket({ type: 'disconnect' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
@@ -285,6 +289,40 @@ module.exports = {
});
},
'test that connection close does not mean disconnect': function (done) {
var cl = client(++ports)
, io = create(cl)
, sid
, end
, disconnected = false
io.configure(function () {
io.set('polling duration', .2);
io.set('close timeout', .5);
});
io.sockets.on('connection', function (client) {
end = function () {
cl.end();
console.log('ending');
client.on('disconnect', function () {
disconnected = true;
});
}
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid);
setTimeout(end, 30);
setTimeout(function () {
console.log('finished');
disconnected.should.be.false;
io.server.close();
done();
}, 100);
});
},
'test sending back data': function (done) {
var cl = client(++ports)
, io = create(cl);
@@ -334,7 +372,7 @@ module.exports = {
])
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
@@ -395,8 +433,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
data.should.eql('');
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: '' });
tobi();
@@ -443,7 +483,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/woot' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
});
@@ -524,21 +564,21 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/woot' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
, parser.encodePacket({ type: 'disconnect', endpoint: '/woot' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
, parser.encodePacket({ type: 'message', data: 'ferret' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
}
@@ -585,16 +625,17 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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: 'connect', endpoint: '/a' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
@@ -603,7 +644,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/b' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
});
@@ -659,7 +700,7 @@ module.exports = {
, parser.encodePacket({ type: 'message', data: '' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
@@ -668,14 +709,14 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/a' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
, parser.encodePacket({ type: 'message', endpoint: '/a', data: 'a' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
}
@@ -686,14 +727,14 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/b' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
, parser.encodePacket({ type: 'message', endpoint: '/b', data: 'b' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
}
@@ -724,9 +765,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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: '' });
s.json.send(['a', 'b', 'c']);
s.json.send({
@@ -760,7 +802,7 @@ module.exports = {
var cl = client(++ports)
, io = create(cl)
, messages = 0;
io.configure(function () {
io.set('polling duration', 0);
io.set('close timeout', .1);
@@ -785,9 +827,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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
@@ -795,8 +838,9 @@ module.exports = {
type: 'json'
, data: { tobi: 'rocks' }
})
, function (res) {
, function (res, data) {
res.statusCode.should.eql(200);
data.should.equal('1');
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -804,8 +848,9 @@ module.exports = {
type: 'json'
, data: 5000
})
, function (res) {
, function (res, data) {
res.statusCode.should.eql(200);
data.should.equal('1');
}
);
}
@@ -835,9 +880,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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: '' });
s.emit('tobi is playing');
@@ -877,9 +923,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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: '' });
s.emit('edwald', { woot: 'woot' }, [1, 2, 3]);
@@ -922,9 +969,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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
@@ -932,8 +980,47 @@ module.exports = {
type: 'event'
, name: 'jane'
})
, function (res) {
, function (res, data) {
res.statusCode.should.eql(200);
data.should.equal('1');
}
);
});
});
},
'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');
}
);
});
@@ -968,9 +1055,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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
@@ -979,8 +1067,9 @@ module.exports = {
, name: 'woot'
, args: ['a', 2, [1, 2]]
})
, function (res) {
, function (res, data) {
res.statusCode.should.eql(200);
data.should.equal('1');
}
);
});
@@ -1008,15 +1097,17 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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: '' });
s.volatile.send('woooot');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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: '' });
});
});
});
@@ -1043,15 +1134,17 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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: '' });
s.volatile.json.send('woooot');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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: '' });
});
});
});
@@ -1078,15 +1171,17 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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: '' });
s.volatile.emit('woooot');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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: '' });
});
});
});
@@ -1232,9 +1327,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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
@@ -1246,7 +1342,7 @@ module.exports = {
})
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.get(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -1255,7 +1351,7 @@ module.exports = {
msgs.should.have.length(1);
msgs[0].should.eql({
type: 'ack'
, ackId: 1
, ackId: '1'
, endpoint: ''
, args: []
});
@@ -1315,7 +1411,7 @@ module.exports = {
})
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
});
@@ -1364,7 +1460,7 @@ module.exports = {
})
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
});
@@ -1500,7 +1596,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.get(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -1566,7 +1662,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/a' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -1577,7 +1673,7 @@ module.exports = {
})
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
}
@@ -1616,7 +1712,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.get(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -1674,7 +1770,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.get(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -1740,7 +1836,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/a' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -1751,7 +1847,7 @@ module.exports = {
})
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
}
@@ -1800,7 +1896,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/a' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -1812,7 +1908,7 @@ module.exports = {
})
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
}
@@ -1850,7 +1946,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
s.volatile.send('woot');
@@ -1902,7 +1998,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
s.volatile.json.send(15);
@@ -1954,7 +2050,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
s.volatile.json.emit('woot');
@@ -2006,7 +2102,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.get(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -2073,7 +2169,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.get(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -2140,7 +2236,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.get(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -2211,7 +2307,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/tobi' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -2223,7 +2319,7 @@ module.exports = {
})
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.get(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -2286,7 +2382,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/tobi' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -2299,7 +2395,7 @@ module.exports = {
})
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.get(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -2362,7 +2458,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/tobi' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -2376,7 +2472,7 @@ module.exports = {
})
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.get(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -2438,7 +2534,7 @@ module.exports = {
, parser.encodePacket({ type: 'connect', endpoint: '/woot' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.get(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -2466,7 +2562,7 @@ module.exports = {
})
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
}
@@ -2505,16 +2601,17 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
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: 'connect', endpoint: '/rapture' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
cl.get(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -2545,7 +2642,7 @@ module.exports = {
})
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
data.should.eql('1');
}
);
}
@@ -2554,6 +2651,143 @@ module.exports = {
);
});
});
},
'test CORS': function (done) {
var cl = client(++ports)
, io = create(cl)
, messaged = false;
io.configure(function () {
io.set('polling duration', .05);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
socket.send('woot');
socket.on('message', function (msg) {
msg.should.equal('woot');
messaged = true;
});
socket.on('disconnect', function () {
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, {
headers: {
Origin: 'http://localhost:3500'
}
}, function (res, packs) {
var headers = res.headers;
headers['access-control-allow-origin'].should.equal('http://localhost:3500');
headers['access-control-allow-credentials'].should.equal('true');
packs.should.have.length(1);
packs[0].type.should.eql('message');
packs[0].data.should.eql('woot');
cl.post('/socket.io/{protocol}/xhr-polling/' + sid, parser.encodePacket({
type: 'message'
, data: 'woot'
}), {
headers: {
Origin: 'http://localhost:3500'
, Cookie: 'woot=woot'
}
}, function (res, data) {
var headers = res.headers;
headers['access-control-allow-origin'].should.equal('http://localhost:3500');
headers['access-control-allow-credentials'].should.equal('true');
data.should.equal('1');
});
});
});
},
'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();
};
}
);
});
});
});
}
};