Compare commits

..

227 Commits
0.8.4 ... 1

Author SHA1 Message Date
Guillermo Rauch
7926d96739 Added missing options. 2012-02-26 16:11:22 -03:00
Guillermo Rauch
54871bcc53 Corrected engine option. 2012-02-26 16:11:08 -03:00
Guillermo Rauch
bf114ddf2e Refactored Makefile. 2012-02-26 16:03:04 -03:00
Guillermo Rauch
300ee104bf Added superagent dep 2012-02-13 20:43:44 -03:00
Guillermo Rauch
d742345480 Updated server 2012-01-31 10:42:02 -08:00
Guillermo Rauch
d9e60e334d New client 2012-01-31 09:49:51 -08:00
Guillermo Rauch
2cd0dd7080 Removed old tests. 2012-01-31 09:49:43 -08:00
Guillermo Rauch
9e467381f3 Started cleanup 2012-01-25 15:18:37 -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
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
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
Daniel Shaw
dd30de3c5a Merge remote-tracking branch 'learnboost/master' 2011-08-16 15:26:30 -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
Daniel Shaw
30284944b1 Merge remote-tracking branch 'learnboost/master' 2011-08-08 11:08:28 -07:00
Daniel Shaw
559d36601d Merge remote-tracking branch 'learnboost/master' 2011-08-06 19:07:27 -07: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
Daniel Shaw
831f1baa4a Merge remote-tracking branch 'learnboost/master' 2011-07-28 16:38:48 -07: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
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
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
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
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
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
67 changed files with 368 additions and 14306 deletions

1
.gitignore vendored
View File

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

7
.travis.yml Normal file
View File

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

View File

@@ -1,4 +1,51 @@
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
==================

View File

@@ -1,22 +1,18 @@
ALL_TESTS = $(shell find test/ -name '*.test.js')
run-tests:
@./node_modules/.bin/expresso \
-t 3000 \
-I support \
-I lib \
--serial \
$(TESTFLAGS) \
$(TESTS)
TESTS = test/*.js
BENCHMARKS = $(shell find bench -type f ! -name 'runner.js')
REPORTER = dot
test:
@$(MAKE) TESTS="$(ALL_TESTS)" run-tests
@./node_modules/.bin/mocha \
--require test/common \
--reporter $(REPORTER) \
--slow 500ms \
--bail \
--growl \
$(TESTS)
test-cov:
@TESTFLAGS=--cov $(MAKE) test
bench:
@node $(PROFILEFLAGS) bench/runner.js $(BENCHMARKS)
test-leaks:
@ls test/leaks/* | xargs node --expose_debug_as=debug --expose_gc
.PHONY: test
.PHONY: test bench

162
Readme.md
View File

@@ -1,31 +1,28 @@
# Socket.IO
Socket.IO is a Node.JS project that makes WebSockets and realtime possible in
all browsers. It also enhances WebSockets by providing built-in multiplexing,
horizontal scalability, automatic JSON encoding/decoding, and more.
Socket.IO is a Node.JS framework for making realtime applications.
It brings the best of WebSockets and other data transport mechanisms for
blazing fast realtime data exchange in web browsers, mobile devices and
servers.
## How to Install
npm install socket.io
```
npm install socket.io
```
## How to use
First, require `socket.io`:
```js
var io = require('socket.io');
```
Next, attach it to a HTTP/HTTPS server. If you're using the fantastic `express`
web framework:
First, require `socket.io` and attach it to a HTTP/HTTPS server:
```js
var app = express.createServer()
, io = io.listen(app);
, io = require('socket.io')(app);
app.listen(80);
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
@@ -56,18 +53,18 @@ Socket.IO allows you to emit and receive custom events.
Besides `connect`, `message` and `disconnect`, you can emit custom events:
```js
// note, io.listen(<port>) will create a http server for you
var io = require('socket.io').listen(80);
// note, io(<port>) will create a http server for you
var io = require('socket.io')(80);
io.sockets.on('connection', function (socket) {
io.sockets.emit('this', { will: 'be received by everyone' });
io.on('connection', function (socket) {
io.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.emit('user disconnected');
});
});
```
@@ -80,9 +77,9 @@ necessary for the duration of the session.
#### Server side
```js
var io = require('socket.io').listen(80);
var io = require('socket.io')(80);
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.on('set nickname', function (name) {
socket.set('nickname', name, function () { socket.emit('ready'); });
});
@@ -111,86 +108,6 @@ io.sockets.on('connection', function (socket) {
</script>
```
### Restricting yourself to a namespace
If you have control over all the messages and events emitted for a particular
application, using the default `/` namespace works.
If you want to leverage 3rd-party code, or produce code to share with others,
socket.io provides a way of namespacing a `socket`.
This has the benefit of `multiplexing` a single connection. Instead of
socket.io using two `WebSocket` connections, it'll use one.
The following example defines a socket that listens on '/chat' and one for
'/news':
#### Server side
```js
var io = require('socket.io').listen(80);
var chat = io
.of('/chat');
.on('connection', function (socket) {
socket.emit('a message', { that: 'only', '/chat': 'will get' });
chat.emit('a message', { everyone: 'in', '/chat': 'will get' });
});
var news = io
.of('/news');
.on('connection', function (socket) {
socket.emit('item', { news: 'item' });
});
```
#### Client side:
```html
<script>
var chat = io.connect('http://localhost/chat')
, news = io.connect('http://localhost/news');
chat.on('connect', function () {
chat.emit('hi!');
});
news.on('news', function () {
news.emit('woot');
});
</script>
```
### Sending volatile messages.
Sometimes certain messages can be dropped. Let's say you have an app that
shows realtime tweets for the keyword `bieber`.
If a certain client is not ready to receive messages (because of network slowness
or other issues, or because he's connected through long polling and is in the
middle of a request-response cycle), if he doesn't receive ALL the tweets related
to bieber your application won't suffer.
In that case, you might want to send those messages as volatile messages.
#### Server side
```js
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
var tweets = setInterval(function () {
getBieberTweet(function (tweet) {
socket.volatile.emit('bieber tweet', tweet);
});
}, 100);
socket.on('disconnect', function () {
clearInterval(tweets);
});
});
```
#### Client side
In the client side, messages are received the same way whether they're volatile
@@ -201,16 +118,16 @@ or not.
Sometimes, you might want to get a callback when the client confirmed the message
reception.
To do this, simply pass a function as the last parameter of `.send` or `.emit`.
To do this, simply pass a function as the last parameter of `.emit`.
What's more, when you use `.emit`, the acknowledgement is done by you, which
means you can also pass data along:
#### Server side
```js
var io = require('socket.io').listen(80);
var io = require('socket.io')(80);
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.on('ferret', function (name, fn) {
fn('woot');
});
@@ -222,7 +139,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'
});
@@ -239,11 +156,10 @@ that starts it.
#### Server side
```js
var io = require('socket.io').listen(80);
var io = require('socket.io')(80);
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.broadcast.emit('user connected');
socket.broadcast.json.send({ a: 'message' });
});
```
@@ -258,12 +174,12 @@ rooms in each socket.
#### Server side
```js
var io = require('socket.io').listen(80);
var io = require('socket.io')(80);
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.join('justin bieber fans');
socket.broadcast.to('justin bieber fans').emit('new fan');
io.sockets.in('rammstein fans').emit('new non-fan');
io.to('rammstein fans').emit('new non-fan');
});
```
@@ -275,9 +191,9 @@ Simply leverage `send` and listen on the `message` event:
#### Server side
```js
var io = require('socket.io').listen(80);
var io = require('socket.io')(80);
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.on('message', function () { });
socket.on('disconnect', function () { });
});
@@ -298,6 +214,24 @@ io.sockets.on('connection', function (socket) {
</script>
```
### Messaging between clients
Each client joins its own group, identified by the client id, which means you
can leverage `to` to message a particular client:
```js
var io = require('socket.io')(80);
io.on('connection', function (socket) {
socket.on('salutate', function (id) {
io.to(id)
.emit('hi!')
.on('disconnect', function () {
socket.emit('friend disconnected');
});
});
});
```
### Changing configuration
Configuration in socket.io is TJ-style:
@@ -305,7 +239,7 @@ Configuration in socket.io is TJ-style:
#### Server side
```js
var io = require('socket.io').listen(80);
var io = require('socket.io')(80);
io.configure(function () {
io.set('transports', ['websocket', 'flashsocket', 'xhr-polling']);

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');
@@ -58,10 +51,10 @@ app.listen(3000, function () {
* Socket.IO server (single process only)
*/
var io = sio.listen(app)
var io = sio(app)
, nicknames = {};
io.sockets.on('connection', function (socket) {
io.on('connection', function (socket) {
socket.on('user message', function (msg) {
socket.broadcast.emit('user message', socket.nickname, msg);
});
@@ -73,7 +66,7 @@ io.sockets.on('connection', function (socket) {
fn(false);
nicknames[nick] = socket.nickname = nick;
socket.broadcast.emit('announcement', nick + ' connected');
io.sockets.emit('nicknames', nicknames);
io.emit('nicknames', nicknames);
}
});

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');
/**
@@ -59,7 +52,7 @@ app.listen(3000, function () {
* Socket.IO server
*/
var io = sio.listen(app)
var io = sio(app)
/**
* Connect to IRC.
@@ -71,11 +64,11 @@ client.on('001', function () {
this.send('JOIN', '#node.js');
});
client.on('PART', function (prefix) {
io.sockets.emit('announcement', irc.user(prefix) + ' left the channel');
io.emit('announcement', irc.user(prefix) + ' left the channel');
});
client.on('JOIN', function (prefix) {
io.sockets.emit('announcement', irc.user(prefix) + ' joined the channel');
io.emit('announcement', irc.user(prefix) + ' joined the channel');
});
client.on('PRIVMSG', function (prefix, channel, text) {
io.sockets.emit('irc message', irc.user(prefix), text);
io.emit('irc message', irc.user(prefix), text);
});

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;

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

@@ -1,8 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
module.exports = require('./lib/socket.io');

View File

@@ -1,96 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var util = require('./util')
, toArray = util.toArray;
/**
* Log levels.
*/
var levels = [
'error'
, 'warn'
, 'info'
, 'debug'
];
/**
* Colors for log levels.
*/
var colors = [
31
, 33
, 36
, 90
];
/**
* Pads the nice output to the longest log level.
*/
function pad (str) {
var max = 0;
for (var i = 0, l = levels.length; i < l; i++)
max = Math.max(max, levels[i].length);
if (str.length < max)
return str + new Array(max - str.length + 1).join(' ');
return str;
};
/**
* Logger (console).
*
* @api public
*/
var Logger = module.exports = function (opts) {
opts = opts || {}
this.colors = false !== opts.colors;
this.level = 3;
};
/**
* Log method.
*
* @api public
*/
Logger.prototype.log = function (type) {
var index = levels.indexOf(type);
if (index > this.level)
return this;
console.log.apply(
console
, [this.colors
? ' \033[' + colors[index] + 'm' + pad(type) + ' -\033[39m'
: type + ':'
].concat(toArray(arguments).slice(1))
);
return this;
};
/**
* Generate methods.
*/
levels.forEach(function (name) {
Logger.prototype[name] = function () {
this.log.apply(this, [name].concat(toArray(arguments)));
};
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,350 +0,0 @@
/**
* Module dependencies.
*/
var Socket = require('./socket')
, EventEmitter = process.EventEmitter
, parser = require('./parser')
, util = require('./util');
/**
* Exports the constructor.
*/
exports = module.exports = SocketNamespace;
/**
* Constructor.
*
* @api public.
*/
function SocketNamespace (mgr, name) {
this.manager = mgr;
this.name = name || '';
this.sockets = {};
this.auth = false;
this.setFlags();
};
/**
* Inherits from EventEmitter.
*/
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.
*
* @api public
*/
SocketNamespace.prototype.__defineGetter__('log', function () {
return this.manager.log;
});
/**
* Access store.
*
* @api public
*/
SocketNamespace.prototype.__defineGetter__('store', function () {
return this.manager.store;
});
/**
* JSON message flag.
*
* @api public
*/
SocketNamespace.prototype.__defineGetter__('json', function () {
this.flags.json = true;
return this;
});
/**
* Volatile message flag.
*
* @api public
*/
SocketNamespace.prototype.__defineGetter__('volatile', function () {
this.flags.volatile = true;
return this;
});
/**
* Overrides the room to relay messages to (flag)
*
* @api public
*/
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)
*
* @api public
*/
SocketNamespace.prototype.except = function (id) {
this.flags.exceptions.push(id);
return this;
};
/**
* Sets the default flags.
*
* @api private
*/
SocketNamespace.prototype.setFlags = function () {
this.flags = {
endpoint: this.name
, exceptions: []
};
return this;
};
/**
* Sends out a packet
*
* @api private
*/
SocketNamespace.prototype.packet = function (packet) {
packet.endpoint = this.name;
var store = this.store
, log = this.log
, volatile = this.flags.volatile
, exceptions = this.flags.exceptions
, packet = parser.encodePacket(packet);
this.manager.onDispatch(this.flags.endpoint, packet, volatile, exceptions);
this.store.publish('dispatch', this.flags.endpoint, packet, volatile, exceptions);
this.setFlags();
return this;
};
/**
* Sends to everyone.
*
* @api public
*/
SocketNamespace.prototype.send = function (data) {
return this.packet({
type: this.flags.json ? 'json' : 'message'
, data: data
});
};
/**
* Emits to everyone (override)
*
* @api private
*/
SocketNamespace.prototype.emit = function (name) {
if (name == 'newListener') {
return this.$emit.apply(this, arguments);
}
return this.packet({
type: 'event'
, name: name
, args: util.toArray(arguments).slice(1)
});
};
/**
* Retrieves or creates a write-only socket for a client, unless specified.
*
* @param {Boolean} whether the socket will be readable when initialized
* @api private
*/
SocketNamespace.prototype.socket = function (sid, readable) {
if (!this.sockets[sid]) {
this.sockets[sid] = new Socket(this.manager, sid, this, 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) {
if (this.sockets[sid] && this.sockets[sid].readable) {
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.
*
* @api private
*/
SocketNamespace.prototype.handlePacket = function (sessid, packet) {
var socket = this.socket(sessid)
, dataAck = packet.ack == 'data'
, self = this;
function ack () {
self.log.debug('sending data ack packet');
socket.packet({
type: 'ack'
, args: util.toArray(arguments)
, ackId: packet.id
});
};
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':
if (packet.endpoint == '') {
connect();
} else {
var manager = this.manager
, 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':
if (socket.acks[packet.ackId]) {
socket.acks[packet.ackId].apply(socket, packet.args);
} else {
this.log.info('unknown ack packet');
}
break;
case 'event':
var params = [packet.name].concat(packet.args);
if (dataAck) {
params.push(ack);
}
socket.$emit.apply(socket, params);
break;
case 'disconnect':
this.manager.onLeave(sessid, this.name);
this.store.publish('leave', sessid, this.name);
socket.$emit('disconnect', packet.reason || 'packet');
break;
case 'json':
case 'message':
var params = ['message', packet.data];
if (dataAck)
params.push(ack);
socket.$emit.apply(socket, params);
};
};

View File

@@ -1,243 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
/**
* Packet types.
*/
var packets = exports.packets = [
'disconnect'
, 'connect'
, 'heartbeat'
, 'message'
, 'json'
, 'event'
, 'ack'
, 'error'
, 'noop'
];
/**
* Errors reasons.
*/
var reasons = exports.reasons = [
'transport not supported'
, 'client not handshaken'
, 'unauthorized'
];
/**
* Errors advice.
*/
var advice = exports.advice = [
'reconnect'
];
/**
* Encodes a packet.
*
* @api private
*/
exports.encodePacket = function (packet) {
var type = packets.indexOf(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;
break;
case 'event':
var ev = { name: packet.name };
if (packet.args && packet.args.length) {
ev.args = packet.args;
}
data = JSON.stringify(ev);
break;
case 'json':
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;
}
// construct packet with required fragments
var encoded = [
type
, id + (ack == 'data' ? '+' : '')
, endpoint
];
// data fragment is optional
if (data !== null && data !== undefined)
encoded.push(data);
return encoded.join(':');
};
/**
* Encodes multiple messages (payload).
*
* @param {Array} messages
* @api private
*/
exports.encodePayload = function (packets) {
var decoded = '';
if (packets.length == 1)
return packets[0];
for (var i = 0, l = packets.length; i < l; i++) {
var packet = packets[i];
decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]
}
return decoded;
};
/**
* Decodes a packet
*
* @api private
*/
var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;
exports.decodePacket = function (data) {
var pieces = data.match(regexp);
if (!pieces) return {};
var id = pieces[2] || ''
, data = pieces[5] || ''
, packet = {
type: packets[pieces[1]]
, endpoint: pieces[4] || ''
};
// whether we need to acknowledge the packet
if (id) {
packet.id = id;
if (pieces[3])
packet.ack = 'data';
else
packet.ack = true;
}
// 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) { }
packet.args = packet.args || [];
break;
case 'json':
try {
packet.data = JSON.parse(data);
} catch (e) { }
break;
case 'connect':
packet.qs = data || '';
break;
case 'ack':
var 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) { }
}
}
break;
case 'disconnect':
case 'heartbeat':
break;
};
return packet;
};
/**
* Decodes data payload. Detects multiple messages
*
* @return {Array} messages
* @api public
*/
exports.decodePayload = function (data) {
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)));
i += Number(length) + 1;
length = '';
} else {
length += data[i];
}
}
return ret;
} else {
return [exports.decodePacket(data)];
}
};

76
lib/server.js Normal file
View File

@@ -0,0 +1,76 @@
/**
* Socket.IO server.
*
* @param {Object} options
* @api public
*/
function Server (opts) {
this.clients = {};
this.clientsCount = 0;
this.flags = {};
// legacy
this.sockets = this;
}
/**
* Broadcast flag.
*
* @api public
*/
Server.prototype.__defineGetter__('broadcast', function () {
this.flags.broadcast = true;
});
/**
* Called with a websocket.io-compatible connection.
*
* @param {engine.Socket|wsio.Socket} connection
* @api public
*/
Server.prototype.onConnection = function (conn) {
var socket = new Socket(conn, this)
, self = this
socket.once('ready', function () {
self.clients[socket.id] = socket;
self.emit('connection', socket):
});
};
/**
* Gets a client.
*
* @api public
*/
Server.prototype.socket =
Server.prototype.client = function (id) {
return this.clients[id];
};
/**
* Emits to all clients.
*
* @api private
*/
Server.prototype.emit = function () {
};
/**
* Sets the room to broadcast to.
*
* @param {String} room name
* @api public
*/
Server.prototype.to =
Server.prototype.in = function () {
};

View File

@@ -1,128 +1,116 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var client = require('socket.io-client');
var engine = require('engine.io')
, Server = require('http').Server
/**
* Version.
* Module exports.
*/
exports.version = '0.8.4';
module.exports = exports = create;
/**
* Supported protocol version.
*/
exports.protocol = 1;
/**
* Client that we serve.
*/
exports.clientVersion = client.version;
/**
* Attaches a manager
* Creates a Socket.IO server.
*
* @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 options) {
fn = options;
options = {};
function create (arg, options, fn) {
if ('number' == typeof arg) {
return exports.listen(arg, options, fn);
} else {
return exports.attach(arg, options, fn);
}
if ('undefined' == typeof server) {
// create a server that listens on port 80
server = 80;
}
if ('number' == typeof server) {
// if a port number is passed
var port = server;
if (options && options.key)
server = require('https').createServer(options);
else
server = require('http').createServer();
// default response
server.on('request', function (req, res) {
res.writeHead(200);
res.end('Welcome to socket.io.');
});
server.listen(port, fn);
}
// otherwise assume a http/s server
return new exports.Manager(server, options);
};
/**
* Manager constructor.
* Version
*
* @api public
*/
exports.Manager = require('./manager');
exports.version = '1.0.0-alpha1';
/**
* Transport constructor.
* Server constructor.
*
* @api public
* @api private
*/
exports.Transport = require('./transport');
exports.Server = Server;
/**
* Socket constructor.
* Listen shortcut.
*
* @api public
*/
exports.Socket = require('./socket');
exports.create = create;
/**
* Store constructor.
* Makes socket.io listen on a port.
*
* @param {Number} port
* @param {Object|Function} (optional) options or callback
* @param {Function} (optional) callback
* @return {Server} io
* @api public
*/
exports.Store = require('./store');
exports.listen = function (port, fn, options) {
// legacy
if (port instanceof Server) return attach(port, fn, opts);
if ('object' == typeof fn) {
options = fn;
fn = null;
}
var server = http.createServer(function (req, res) {
res.writeHead(501);
res.end('Not Implemented');
});
server.listen(port, fn);
// create socket.io server
var io = exports.attach(server, options);
// keep ref to http server
io.httpServer = server;
return io;
};
/**
* Memory Store constructor.
* Attaches socket.io to a http server.
*
* @param {http.Server} server
* @param {Object} (optional) options
* @return {Server} io server
* @api public
*/
exports.MemoryStore = require('./stores/memory');
exports.attach = function (server, options) {
var opts = options || {}
, engineOpts = opts.engine || {}
/**
* Redis Store constructor.
*
* @api public
*/
// use default socket.io base path
engineOpts.path = engineOpts.path || '/socket.io';
exports.RedisStore = require('./stores/redis');
// spawn engine server
var server = engine.attach(server, engineOpts);
/**
* Parser.
*
* @api public
*/
// spawn socket.io
var io = new exports.Server(options);
exports.parser = require('./parser');
// capture connections
server.on('connection', function (conn) {
io.onConnection(conn);
});
return io;
};

View File

@@ -1,52 +1,31 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var parser = require('./parser')
, util = require('./util')
, EventEmitter = process.EventEmitter
var EventEmitter = require('events').EventEmitter;
/**
* Export the constructor.
*/
exports = module.exports = Socket;
/**
* Default error event listener to prevent uncaught exceptions.
*/
var defaultError = function () {};
/**
* Socket constructor.
* Socket.
*
* @param {Manager} manager instance
* @param {String} session id
* @param {Namespace} namespace the socket belongs to
* @param {Boolean} whether the
* @api public
* @api private
*/
function Socket (manager, id, nsp, readable) {
this.id = id;
this.namespace = nsp;
this.manager = manager;
this.disconnected = false;
this.ackPackets = 0;
this.acks = {};
this.setFlags();
this.readable = readable;
this.store = this.manager.store.client(this.id);
this.on('error', defaultError);
};
function Socket (connection, server) {
this.connection = connection;
this.id = this.sid = connection.id;
this.server = server;
this.store = this.server.store;
// group subscriptions
this.subscriptions = [];
// join to group for itself
var self = this;
this.join(sid, function () {
self.emit('ready');
});
}
/**
* Inherits from EventEmitter.
@@ -55,298 +34,58 @@ function Socket (manager, id, nsp, readable) {
Socket.prototype.__proto__ = EventEmitter.prototype;
/**
* Accessor shortcut for the handshake data
* Save reference to original `emit`.
*
* @api private
*/
Socket.prototype.__defineGetter__('handshake', function () {
return this.manager.handshaken[this.id];
});
Socket.prototype._emit = Socket.prototype.emit;
/**
* Accessor shortcut for the logger.
*
* @api private
*/
Socket.prototype.__defineGetter__('log', function () {
return this.manager.log;
});
/**
* JSON message flag.
* Joins a group.
*
* @param {String} group
* @return {Socket} for chaining
* @api public
*/
Socket.prototype.__defineGetter__('json', function () {
this.flags.json = true;
return this;
});
/**
* Volatile message flag.
*
* @api public
*/
Socket.prototype.__defineGetter__('volatile', function () {
this.flags.volatile = true;
return this;
});
/**
* Broadcast message flag.
*
* @api public
*/
Socket.prototype.__defineGetter__('broadcast', function () {
this.flags.broadcast = true;
return this;
});
/**
* Overrides the room to broadcast messages to (flag)
*
* @api public
*/
Socket.prototype.to = Socket.prototype.in = function (room) {
this.flags.room = room;
return this;
};
/**
* Resets flags
*
* @api private
*/
Socket.prototype.setFlags = function () {
this.flags = {
endpoint: this.namespace.name
, room: ''
};
return this;
};
/**
* Triggered on disconnect
*
* @api private
*/
Socket.prototype.onDisconnect = function (reason) {
if (!this.disconnected) {
this.$emit('disconnect', reason);
this.disconnected = true;
}
};
/**
* Joins a user to a room.
*
* @api public
*/
Socket.prototype.join = function (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;
};
/**
* Un-joins a user from a room.
*
* @api public
*/
Socket.prototype.leave = function (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;
};
/**
* Transmits a packet.
*
* @api private
*/
Socket.prototype.packet = function (packet) {
if (this.flags.broadcast) {
this.log.debug('broadcasting packet');
this.namespace.in(this.flags.room).except(this.id).packet(packet);
Socket.prototype.join = function (group, fn) {
if (!~this.subscriptions.indexOf(group)) {
var self = this;
this.subscriptions.push(group);
this.store.addToGroup(group, this.sid, function (ev, args) {
self.onGroupEvent(ev, args);
}, fn);
} else {
packet.endpoint = this.flags.endpoint;
packet = parser.encodePacket(packet);
this.dispatch(packet, this.flags.volatile);
fn && fn();
}
this.setFlags();
return this;
};
/**
* Dispatches a packet
* Leaves a group.
*
* @return {Socket} for chaining
* @api public
*/
Socket.prototype.leave = function (group) {
var index = this.subscriptions.indexOf(group);
if (~index) {
this.subscriptions.splice(index, 1);
}
return this;
};
/**
* Called upon disconnect.
*
* @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:' + this.id, packet, volatile);
Socket.prototype.onDisconnect = function () {
for (var i = 0, l = this.subscriptions; i < l; i++) {
this.store.removeFromGroup(id, group, fn);
}
};
/**
* Stores data for the client.
*
* @api public
*/
Socket.prototype.set = function (key, value, fn) {
this.store.set(key, value, fn);
return this;
};
/**
* Retrieves data for the client
*
* @api public
*/
Socket.prototype.get = function (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;
};
/**
* Kicks client
*
* @api public
*/
Socket.prototype.disconnect = function () {
if (!this.disconnected) {
this.log.info('booting client');
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:' + this.id);
}
}
return this;
};
/**
* Send a message.
*
* @api public
*/
Socket.prototype.send = function (data, fn) {
var packet = {
type: this.flags.json ? 'json' : 'message'
, data: data
};
if (fn) {
packet.id = ++this.ackPackets;
packet.ack = true;
this.acks[packet.id] = fn;
}
return this.packet(packet);
};
/**
* Original emit function.
*
* @api private
*/
Socket.prototype.$emit = EventEmitter.prototype.emit;
/**
* Emit override for custom events.
*
* @api public
*/
Socket.prototype.emit = function (ev) {
if (ev == 'newListener') {
return this.$emit.apply(this, arguments);
}
var args = util.toArray(arguments).slice(1)
, lastArg = args[args.length - 1]
, packet = {
type: 'event'
, name: ev
};
if ('function' == typeof lastArg) {
packet.id = ++this.ackPackets;
packet.ack = lastArg.length ? 'data' : true;
this.acks[packet.id] = lastArg;
args = args.slice(0, args.length - 1);
}
packet.args = args;
return this.packet(packet);
};

View File

@@ -1,98 +1,53 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Expose the constructor.
*/
exports = module.exports = Store;
/**
* Module dependencies.
*/
var EventEmitter = process.EventEmitter;
/**
* Store interface
* Store. In memory by default.
*
* @api public
*/
function Store (options) {
this.options = options;
this.clients = {};
};
function Store () {
this.groups = {};
}
/**
* Inherit from EventEmitter.
*/
Store.prototype.__proto__ = EventEmitter.prototype;
/**
* Initializes a client store
* Adds id to group.
*
* @param {String} id
* @api public
*/
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
* @param {String} client id
* @param {String} group name
* @param {Function} event listener
* @param {Function} callback
* @api private
*/
Store.prototype.destroyClient = function (id, expiration) {
if (this.clients[id]) {
this.clients[id].destroy(expiration);
delete this.clients[id];
Store.prototype.addToGroup = function (id, group, listener, fn) {
if (!this.groups[group]) {
this.groups[group] = [];
this.listeners[group] = {};
}
return this;
if (!this.listeners[group][id]) {
this.on('group:' + group, listener);
this.listeners[group][id] = listener;
this.groups[group].push(id);
}
fn && fn();
};
/**
* Destroys the store
* Removes id from group.
*
* @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);
Store.prototype.removeFromGroup = function (id, group, fn) {
if (this.groups[group]) {
var i = this.groups[group].indexOf(id);
if (~i) {
this.groups[group].splice(i, 1);
this.removeListener('group:' + group, this.listeners[group][id]);
}
}
this.clients = {};
return this;
};
/**
* Client.
*
* @api public
*/
Store.Client = function (store, id) {
this.store = store;
this.id = id;
fn && fn();
};

View File

@@ -1,143 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var crypto = require('crypto')
, Store = require('../store');
/**
* Exports the constructor.
*/
exports = module.exports = Memory;
Memory.Client = Client;
/**
* Memory store
*
* @api public
*/
function Memory (opts) {
Store.call(this, opts);
};
/**
* Inherits from Store.
*/
Memory.prototype.__proto__ = Store.prototype;
/**
* Publishes a message.
*
* @api private
*/
Memory.prototype.publish = function () { };
/**
* Subscribes to a channel
*
* @api private
*/
Memory.prototype.subscribe = function () { };
/**
* Unsubscribes
*
* @api private
*/
Memory.prototype.unsubscribe = function () { };
/**
* Client constructor
*
* @api private
*/
function Client () {
Store.Client.apply(this, arguments);
this.data = {};
};
/**
* Inherits from Store.Client
*/
Client.prototype.__proto__ = Store.Client;
/**
* Gets a key
*
* @api public
*/
Client.prototype.get = function (key, fn) {
fn(null, this.data[key] === undefined ? null : this.data[key]);
return this;
};
/**
* Sets a key
*
* @api public
*/
Client.prototype.set = function (key, value, fn) {
this.data[key] = value;
fn && fn(null);
return this;
};
/**
* 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.destroy = function (expiration) {
if ('number' != typeof expiration) {
this.data = {};
} else {
var self = this;
setTimeout(function () {
self.data = {};
}, expiration * 1000);
}
return this;
};

View File

@@ -1,250 +0,0 @@
/*!
* 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');
// initialize a pubsub client and a regular client
this.pub = redis.createClient(opts.redisPub);
this.sub = redis.createClient(opts.redisSub);
this.cmd = redis.createClient(opts.redisClient);
Store.call(this, opts);
};
/**
* 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.removeEvent('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

@@ -1,534 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var parser = require('./parser');
/**
* Expose the constructor.
*/
exports = module.exports = Transport;
/**
* Transport constructor.
*
* @api public
*/
function Transport (mng, data, req) {
this.manager = mng;
this.id = data.id;
this.disconnected = false;
this.drained = true;
this.handleRequest(req);
};
/**
* Access the logger.
*
* @api public
*/
Transport.prototype.__defineGetter__('log', function () {
return this.manager.log;
});
/**
* Access the store.
*
* @api public
*/
Transport.prototype.__defineGetter__('store', function () {
return this.manager.store;
});
/**
* Handles a request when it's set.
*
* @api private
*/
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.setHeartbeatInterval();
this.setHandlers();
this.onSocketConnect();
}
};
/**
* Called when a connection is first set.
*
* @api private
*/
Transport.prototype.onSocketConnect = function () { };
/**
* Sets transport handlers
*
* @api private
*/
Transport.prototype.setHandlers = function () {
var self = this;
// 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)
this.store.subscribe('heartbeat-clear:' + this.id, function () {
self.onHeartbeatClear();
});
this.store.subscribe('disconnect-force:' + this.id, function () {
self.onForcedDisconnect();
});
this.store.subscribe('dispatch:' + this.id, function (packet, volatile) {
self.onDispatch(packet, volatile);
});
this.bound = {
end: this.onSocketEnd.bind(this)
, close: this.onSocketClose.bind(this)
, error: this.onSocketError.bind(this)
, drain: this.onSocketDrain.bind(this)
};
this.socket.on('end', this.bound.end);
this.socket.on('close', this.bound.close);
this.socket.on('error', this.bound.error);
this.socket.on('drain', this.bound.drain);
this.handlersSet = true;
};
/**
* Removes transport handlers
*
* @api private
*/
Transport.prototype.clearHandlers = function () {
if (this.handlersSet) {
this.store.unsubscribe('disconnect-force:' + this.id);
this.store.unsubscribe('heartbeat-clear:' + this.id);
this.store.unsubscribe('dispatch:' + 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);
}
};
/**
* Called when the connection dies
*
* @api private
*/
Transport.prototype.onSocketEnd = function () {
this.end('socket end');
};
/**
* Called when the connection dies
*
* @api private
*/
Transport.prototype.onSocketClose = function (error) {
this.end(error ? 'socket error' : 'socket close');
};
/**
* Called when the connection has an error.
*
* @api private
*/
Transport.prototype.onSocketError = function (err) {
if (this.open) {
this.socket.destroy();
this.onClose();
}
this.log.info('socket error ' + err.stack);
};
/**
* Called when the connection is drained.
*
* @api private
*/
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.
*
* @api private
*/
Transport.prototype.onForcedDisconnect = function () {
if (!this.disconnected) {
this.log.info('transport end by forced client disconnection');
if (this.open) {
this.packet({ type: 'disconnect' });
}
this.end('booted');
}
};
/**
* Dispatches a packet.
*
* @api private
*/
Transport.prototype.onDispatch = function (packet, volatile) {
if (volatile) {
this.writeVolatile(packet);
} else {
this.write(packet);
}
};
/**
* Sets the close timeout.
*/
Transport.prototype.setCloseTimeout = function () {
if (!this.closeTimeout) {
var self = this;
this.closeTimeout = setTimeout(function () {
self.log.debug('fired close timeout for client', self.id);
self.closeTimeout = null;
self.end('close timeout');
}, this.manager.get('close timeout') * 1000);
this.log.debug('set close timeout for client', this.id);
}
};
/**
* Clears the close timeout.
*/
Transport.prototype.clearCloseTimeout = function () {
if (this.closeTimeout) {
clearTimeout(this.closeTimeout);
this.closeTimeout = null;
this.log.debug('cleared close timeout for client', this.id);
}
};
/**
* Sets the heartbeat timeout
*/
Transport.prototype.setHeartbeatTimeout = function () {
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('heartbeat timeout');
}, this.manager.get('heartbeat timeout') * 1000);
this.log.debug('set heartbeat timeout for client', this.id);
}
};
/**
* Clears the heartbeat timeout
*
* @param text
*/
Transport.prototype.clearHeartbeatTimeout = function () {
if (this.heartbeatTimeout && this.manager.enabled('heartbeats')) {
clearTimeout(this.heartbeatTimeout);
this.heartbeatTimeout = null;
this.log.debug('cleared heartbeat timeout for client', this.id);
}
};
/**
* Sets the heartbeat interval. To be called when a connection opens and when
* a heartbeat is received.
*
* @api private
*/
Transport.prototype.setHeartbeatInterval = function () {
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);
}
};
/**
* Clears all timeouts.
*
* @api private
*/
Transport.prototype.clearTimeouts = function () {
this.clearCloseTimeout();
this.clearHeartbeatTimeout();
this.clearHeartbeatInterval();
};
/**
* Sends a heartbeat
*
* @api private
*/
Transport.prototype.heartbeat = function () {
if (this.open) {
this.log.debug('emitting heartbeat for client', this.id);
this.packet({ type: 'heartbeat' });
this.setHeartbeatTimeout();
}
return this;
};
/**
* Handles a message.
*
* @param {Object} packet object
* @api private
*/
Transport.prototype.onMessage = function (packet) {
var current = this.manager.transports[this.id];
if ('heartbeat' == packet.type) {
this.log.debug('got heartbeat packet');
if (current && current.open) {
current.onHeartbeatClear();
} else {
this.store.publish('heartbeat-clear:' + this.id);
}
} else {
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');
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:' + this.id, ack);
}
}
// handle packet locally or publish it
if (current) {
this.manager.onClientMessage(this.id, packet);
} else {
this.store.publish('message:' + this.id, packet);
}
}
};
/**
* Clears the heartbeat interval
*
* @api private
*/
Transport.prototype.clearHeartbeatInterval = function () {
if (this.heartbeatInterval && this.manager.enabled('heartbeats')) {
clearTimeout(this.heartbeatInterval);
this.heartbeatInterval = null;
this.log.debug('cleared heartbeat interval for client', this.id);
}
};
/**
* Finishes the connection and makes sure client doesn't reopen
*
* @api private
*/
Transport.prototype.disconnect = function (reason) {
this.packet({ type: 'disconnect' });
this.end(reason);
return this;
};
/**
* Closes the connection.
*
* @api private
*/
Transport.prototype.close = function () {
if (this.open) {
this.doClose();
this.onClose();
}
};
/**
* Called upon a connection close.
*
* @api private
*/
Transport.prototype.onClose = function () {
if (this.open) {
this.setCloseTimeout();
this.clearHandlers();
this.open = false;
this.manager.onClose(this.id);
this.store.publish('close', this.id);
}
};
/**
* Cleans up the connection, considers the client disconnected.
*
* @api private
*/
Transport.prototype.end = function (reason) {
if (!this.disconnected) {
this.log.info('transport end');
var local = this.manager.transports[this.id];
this.close();
this.clearTimeouts();
this.disconnected = true;
if (local) {
this.manager.onClientDisconnect(this.id, reason, true);
} else {
this.store.publish('disconnect:' + this.id, reason);
}
}
};
/**
* Signals that the transport should pause and buffer data.
*
* @api public
*/
Transport.prototype.discard = function () {
this.log.debug('discarding transport');
this.discarded = true;
this.clearTimeouts();
this.clearHandlers();
return this;
};
/**
* Writes an error packet with the specified reason and advice.
*
* @param {Number} advice
* @param {Number} reason
* @api public
*/
Transport.prototype.error = function (reason, advice) {
this.packet({
type: 'error'
, reason: reason
, advice: advice
});
this.log.warn(reason, advice ? ('client should ' + advice) : '');
this.end('error');
};
/**
* Write a packet.
*
* @api public
*/
Transport.prototype.packet = function (obj) {
return this.write(parser.encodePacket(obj));
};
/**
* Writes a volatile message.
*
* @api private
*/
Transport.prototype.writeVolatile = function (msg) {
if (this.open) {
if (this.drained) {
this.write(msg);
} else {
this.log.debug('ignoring volatile packet, buffer not drained');
}
} else {
this.log.debug('ignoring volatile packet, transport not open');
}
};

View File

@@ -1,102 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var WebSocket = require('./websocket');
/**
* Export the constructor.
*/
exports = module.exports = FlashSocket;
/**
* The FlashSocket transport is just a proxy
* for WebSocket connections.
*
* @api public
*/
function FlashSocket (mng, data, req) {
return WebSocket.call(this, mng, data, req);
}
/**
* Inherits from WebSocket.
*/
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.
*
* @param {Manager} Manager instance.
* @api private
*/
var server;
FlashSocket.init = function (manager) {
function create () {
server = require('policyfile').createServer({
log: function(msg){
manager.log.info(msg.toLowerCase());
}
}, manager.get('origins'));
server.on('close', function (e) {
server = null;
});
server.listen(manager.get('flash policy port'), manager.server);
manager.flashPolicyServer = server;
}
// listen for origin changes, so we can update the server
manager.on('set:origins', function (value, key) {
if (!server) return;
// update the origins and compile a new response buffer
server.origins = Array.isArray(value) ? value : [value];
server.compile();
});
// destory the server and create a new server
manager.on('set:flash policy port', function (value, key) {
var transports = manager.get('transports');
if (server && server.port !== value && ~transports.indexOf('flashsocket')) {
// destroy the server and rebuild it on a new port
server.close();
create();
}
});
// only start the server
manager.on('set:transports', function (value, key){
if (!server && ~manager.get('transports').indexOf('flashsocket')) {
create();
}
});
// check if we need to initialize at start
if (~manager.get('transports').indexOf('flashsocket')){
create();
}
};

View File

@@ -1,82 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var HTTPTransport = require('./http');
/**
* Export the constructor.
*/
exports = module.exports = HTMLFile;
/**
* HTMLFile transport constructor.
*
* @api public
*/
function HTMLFile (mng, data, req) {
HTTPTransport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
HTMLFile.prototype.__proto__ = HTTPTransport.prototype;
/**
* Transport name
*
* @api public
*/
HTMLFile.prototype.name = 'htmlfile';
/**
* Handles the request.
*
* @api private
*/
HTMLFile.prototype.handleRequest = function (req) {
HTTPTransport.prototype.handleRequest.call(this, req);
if (req.method == 'GET') {
req.res.writeHead(200, {
'Content-Type': 'text/html'
, 'Connection': 'keep-alive'
, 'Transfer-Encoding': 'chunked'
});
req.res.write(
'<html><body>'
+ '<script>var _ = function (msg) { parent.s._(msg, document); };</script>'
+ new Array(174).join(' ')
);
}
};
/**
* Performs the write.
*
* @api private
*/
HTMLFile.prototype.write = function (data) {
data = '<script>_(' + JSON.stringify(data) + ');</script>';
if (this.response.write(data)) {
this.drained = true;
}
this.log.debug(this.name + ' writing', data);
};

View File

@@ -1,135 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var HTTPTransport = require('./http');
/**
* Exports the constructor.
*/
exports = module.exports = HTTPPolling;
/**
* HTTP polling constructor.
*
* @api public.
*/
function HTTPPolling (mng, data, req) {
HTTPTransport.call(this, mng, data, req);
};
/**
* Inherits from HTTPTransport.
*
* @api public.
*/
HTTPPolling.prototype.__proto__ = HTTPTransport.prototype;
/**
* Transport name
*
* @api public
*/
HTTPPolling.prototype.name = 'httppolling';
/**
* Removes heartbeat timeouts for polling.
*/
HTTPPolling.prototype.setHeartbeatInterval = function () {
return this;
};
/**
* Handles a request
*
* @api private
*/
HTTPPolling.prototype.handleRequest = function (req) {
HTTPTransport.prototype.handleRequest.call(this, req);
if (req.method == 'GET') {
var self = this;
this.pollTimeout = setTimeout(function () {
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');
}
};
/**
* Clears polling timeout
*
* @api private
*/
HTTPPolling.prototype.clearPollTimeout = function () {
if (this.pollTimeout) {
clearTimeout(this.pollTimeout);
this.pollTimeout = null;
this.log.debug('clearing poll timeout');
}
return this;
};
/**
* Override clear timeouts to clear the poll timeout
*
* @api private
*/
HTTPPolling.prototype.clearTimeouts = function () {
HTTPTransport.prototype.clearTimeouts.call(this);
this.clearPollTimeout();
};
/**
* doWrite to clear poll timeout
*
* @api private
*/
HTTPPolling.prototype.doWrite = function () {
this.clearPollTimeout();
};
/**
* Performs a write.
*
* @api private.
*/
HTTPPolling.prototype.write = function (data, close) {
this.doWrite(data);
this.response.end();
this.onClose();
};
/**
* Override end.
*
* @api private
*/
HTTPPolling.prototype.end = function () {
this.clearPollTimeout();
return HTTPTransport.prototype.end.call(this);
};

View File

@@ -1,111 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var Transport = require('../transport')
, parser = require('../parser')
, qs = require('querystring');
/**
* Export the constructor.
*/
exports = module.exports = HTTPTransport;
/**
* HTTP interface constructor. For all non-websocket transports.
*
* @api public
*/
function HTTPTransport (mng, data, req) {
Transport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
HTTPTransport.prototype.__proto__ = Transport.prototype;
/**
* Handles a request.
*
* @api private
*/
HTTPTransport.prototype.handleRequest = function (req) {
if (req.method == 'POST') {
var buffer = ''
, res = req.res
, origin = req.headers.origin
, headers = { 'Content-Length': 1 }
, self = this;
req.on('data', function (data) {
buffer += data;
});
req.on('end', function () {
res.writeHead(200, headers);
res.end('1');
self.onData(self.postEncoded ? qs.parse(buffer).d : buffer);
});
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';
}
}
} else {
this.response = req.res;
Transport.prototype.handleRequest.call(this, req);
}
};
/**
* Handles data payload.
*
* @api private
*/
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.onMessage(messages[i]);
}
};
/**
* Closes the request-response cycle
*
* @api private
*/
HTTPTransport.prototype.doClose = function () {
this.response.end();
};
/**
* Writes a payload of messages
*
* @api private
*/
HTTPTransport.prototype.payload = function (msgs) {
this.write(parser.encodePayload(msgs));
};

View File

@@ -1,12 +0,0 @@
/**
* Export transports.
*/
module.exports = {
websocket: require('./websocket')
, flashsocket: require('./flashsocket')
, htmlfile: require('./htmlfile')
, 'xhr-polling': require('./xhr-polling')
, 'jsonp-polling': require('./jsonp-polling')
};

View File

@@ -1,78 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var HTTPPolling = require('./http-polling');
/**
* Export the constructor.
*/
exports = module.exports = JSONPPolling;
/**
* JSON-P polling transport.
*
* @api public
*/
function JSONPPolling (mng, data, req) {
HTTPPolling.call(this, mng, data, req);
this.head = 'io.j[0](';
this.foot = ');';
if (data.query.i) {
this.head = 'io.j[' + data.query.i + '](';
}
};
/**
* Inherits from Transport.
*/
JSONPPolling.prototype.__proto__ = HTTPPolling.prototype;
/**
* Transport name
*
* @api public
*/
JSONPPolling.prototype.name = 'jsonppolling';
/**
* Make sure POST are decoded.
*/
JSONPPolling.prototype.postEncoded = true;
/**
* Performs the write.
*
* @api private
*/
JSONPPolling.prototype.doWrite = function (data) {
HTTPPolling.prototype.doWrite.call(this);
var data = data === undefined
? '' : this.head + JSON.stringify(data) + this.foot;
this.response.writeHead(200, {
'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(this.name + ' writing', data);
};

View File

@@ -1,33 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var protocolVersions = require('./websocket/');
/**
* 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) {
var version = req.headers['sec-websocket-version'];
if (typeof version !== 'undefined' && typeof protocolVersions[version] !== 'undefined') {
return new protocolVersions[version](mng, data, req);
}
return new protocolVersions['default'](mng, data, req);
};

View File

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

View File

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

View File

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

View File

@@ -1,72 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var HTTPPolling = require('./http-polling');
/**
* Export the constructor.
*/
exports = module.exports = XHRPolling;
/**
* Ajax polling transport.
*
* @api public
*/
function XHRPolling (mng, data, req) {
HTTPPolling.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
XHRPolling.prototype.__proto__ = HTTPPolling.prototype;
/**
* Transport name
*
* @api public
*/
XHRPolling.prototype.name = 'xhr-polling';
/**
* Frames data prior to write.
*
* @api private
*/
XHRPolling.prototype.doWrite = function (data) {
HTTPPolling.prototype.doWrite.call(this);
var origin = this.req.headers.origin
, headers = {
'Content-Type': 'text/plain; charset=UTF-8'
, 'Content-Length': data === undefined ? 0 : Buffer.byteLength(data)
, 'Connection': 'Keep-Alive'
};
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';
}
}
this.response.writeHead(200, headers);
this.response.write(data);
this.log.debug(this.name + ' writing', data);
};

View File

@@ -1,50 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
/**
* Converts an enumerable to an array.
*
* @api public
*/
exports.toArray = function (enu) {
var arr = [];
for (var i = 0, l = enu.length; i < l; i++)
arr.push(enu[i]);
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,6 +1,6 @@
{
"name": "socket.io"
, "version": "0.8.4"
, "version": "1.0.0-alpha1"
, "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"]
@@ -11,20 +11,21 @@
, { "name": "Vladimir Dronnikov", "email": "dronnikov@gmail.com" }
, { "name": "Einar Otto Stangvik", "email": "einaros@gmail.com" }
]
, "repository":{
, "repository": {
"type": "git"
, "url": "https://github.com/LearnBoost/socket.io.git"
, "url": "https://github.com/learnboost/socket.io.git"
}
, "main": "./lib/socket.io"
, "dependencies": {
"socket.io-client": "0.8.4"
, "policyfile": "0.0.4"
, "redis": "0.6.6"
"engine.io": "0.1.0"
, "socket.io-client": "1.0.0-alpha1"
}
, "devDependencies": {
"expresso": "0.7.7"
, "should": "0.0.4"
, "assertvanish": "0.0.3-1"
"mocha": "*"
, "expect.js": "*"
, "superagent": "*"
}
, "scripts": {
"test": "make test"
}
, "main": "index"
, "engines": { "node": ">= 0.4.0" }
}

View File

@@ -1,27 +0,0 @@
Copyright (c) 2010, Peter Griess <pg@std.in>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of node-websocket-client nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,22 +0,0 @@
# This makefile exists to help run tests.
#
# If TEST_UNIX is a non-empty value, runs tests for UNIX sockets. This
# functionality is not in node-websocket-server at the moment.
.PHONY: test
all: test test-unix
test:
for f in `ls -1 test/test-*.js | grep -v unix` ; do \
echo $$f ; \
node $$f ; \
done
test-unix:
if [[ -n "$$TEST_UNIX" ]] ; then \
for f in `ls -1 test/test-*.js | grep unix` ; do \
echo $$f ; \
node $$f ; \
done \
fi

View File

@@ -1,41 +0,0 @@
A prototype [Web Socket](http://www.whatwg.org/specs/web-socket-protocol/)
client implementation for [node.js](http://nodejs.org).
Tested with
[miksago/node-websocket-server](http://github.com/miksago/node-websocket-server)
v1.2.00.
Requires [nodejs](http://nodejs.org) 0.1.98 or later.
## Installation
Install this using `npm` as follows
npm install websocket-client
... or just dump `lib/websocket.js` in your `$NODE_PATH`.
## Usage
var sys = require('sys');
var WebSocket = require('websocket').WebSocket;
var ws = new WebSocket('ws://localhost:8000/biff', 'borf');
ws.addListener('data', function(buf) {
sys.debug('Got data: ' + sys.inspect(buf));
});
ws.onmessage = function(m) {
sys.debug('Got message: ' + m);
}
## API
This supports the `send()` and `onmessage()` APIs. The `WebSocket` object will
also emit `data` events that are node `Buffer` objects, in case you want to
work with something lower-level than strings.
## Transports
Multiple transports are supported, indicated by the scheme provided to the
`WebSocket` constructor. `ws://` is a standard TCP-based Web Socket;
`ws+unix://` allows connection to a UNIX socket at the given path.

View File

@@ -1,12 +0,0 @@
var sys = require('sys');
var WebSocket = require('../lib/websocket').WebSocket;
var ws = new WebSocket('ws+unix://' + process.argv[2], 'boffo');
ws.addListener('message', function(d) {
sys.debug('Received message: ' + d.toString('utf8'));
});
ws.addListener('open', function() {
ws.send('This is a message', 1);
});

View File

@@ -1,10 +0,0 @@
var sys = require('sys');
var WebSocket = require('../lib/websocket').WebSocket;
var ws = new WebSocket('ws://localhost:8000/biff', 'borf');
ws.addListener('data', function(buf) {
sys.debug('Got data: ' + sys.inspect(buf));
});
ws.onmessage = function(m) {
sys.debug('Got message: ' + m);
}

View File

@@ -1,13 +0,0 @@
var sys = require('sys');
var ws = require('websocket-server/ws');
var srv = ws.createServer({ debug : true});
srv.addListener('connection', function(s) {
sys.debug('Got a connection!');
s._req.socket.addListener('fd', function(fd) {
sys.debug('Got an fd: ' + fd);
});
});
srv.listen(process.argv[2]);

View File

@@ -1,599 +0,0 @@
var assert = require('assert');
var buffer = require('buffer');
var crypto = require('crypto');
var events = require('events');
var http = require('http');
var net = require('net');
var urllib = require('url');
var sys = require('sys');
var FRAME_NO = 0;
var FRAME_LO = 1;
var FRAME_HI = 2;
// Values for readyState as per the W3C spec
var CONNECTING = 0;
var OPEN = 1;
var CLOSING = 2;
var CLOSED = 3;
var debugLevel = parseInt(process.env.NODE_DEBUG, 16);
var debug = (debugLevel & 0x4) ?
function() { sys.error.apply(this, arguments); } :
function() { };
// Generate a Sec-WebSocket-* value
var createSecretKey = function() {
// How many spaces will we be inserting?
var numSpaces = 1 + Math.floor(Math.random() * 12);
assert.ok(1 <= numSpaces && numSpaces <= 12);
// What is the numerical value of our key?
var keyVal = (Math.floor(
Math.random() * (4294967295 / numSpaces)
) * numSpaces);
// Our string starts with a string representation of our key
var s = keyVal.toString();
// Insert 'numChars' worth of noise in the character ranges
// [0x21, 0x2f] (14 characters) and [0x3a, 0x7e] (68 characters)
var numChars = 1 + Math.floor(Math.random() * 12);
assert.ok(1 <= numChars && numChars <= 12);
for (var i = 0; i < numChars; i++) {
var pos = Math.floor(Math.random() * s.length + 1);
var c = Math.floor(Math.random() * (14 + 68));
c = (c <= 14) ?
String.fromCharCode(c + 0x21) :
String.fromCharCode((c - 14) + 0x3a);
s = s.substring(0, pos) + c + s.substring(pos, s.length);
}
// We shoudln't have any spaces in our value until we insert them
assert.equal(s.indexOf(' '), -1);
// Insert 'numSpaces' worth of spaces
for (var i = 0; i < numSpaces; i++) {
var pos = Math.floor(Math.random() * (s.length - 1)) + 1;
s = s.substring(0, pos) + ' ' + s.substring(pos, s.length);
}
assert.notEqual(s.charAt(0), ' ');
assert.notEqual(s.charAt(s.length), ' ');
return s;
};
// Generate a challenge sequence
var createChallenge = function() {
var c = '';
for (var i = 0; i < 8; i++) {
c += String.fromCharCode(Math.floor(Math.random() * 255));
}
return c;
};
// Get the value of a secret key string
//
// This strips non-digit values and divides the result by the number of
// spaces found.
var secretKeyValue = function(sk) {
var ns = 0;
var v = 0;
for (var i = 0; i < sk.length; i++) {
var cc = sk.charCodeAt(i);
if (cc == 0x20) {
ns++;
} else if (0x30 <= cc && cc <= 0x39) {
v = v * 10 + cc - 0x30;
}
}
return Math.floor(v / ns);
}
// Get the to-be-hashed value of a secret key string
//
// This takes the result of secretKeyValue() and encodes it in a big-endian
// byte string
var secretKeyHashValue = function(sk) {
var skv = secretKeyValue(sk);
var hv = '';
hv += String.fromCharCode((skv >> 24) & 0xff);
hv += String.fromCharCode((skv >> 16) & 0xff);
hv += String.fromCharCode((skv >> 8) & 0xff);
hv += String.fromCharCode((skv >> 0) & 0xff);
return hv;
};
// Compute the secret key signature based on two secret key strings and some
// handshaking data.
var computeSecretKeySignature = function(s1, s2, hs) {
assert.equal(hs.length, 8);
var hash = crypto.createHash('md5');
hash.update(secretKeyHashValue(s1));
hash.update(secretKeyHashValue(s2));
hash.update(hs);
return hash.digest('binary');
};
// Return a hex representation of the given binary string; used for debugging
var str2hex = function(str) {
var hexChars = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f'
];
var out = '';
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
out += hexChars[(c & 0xf0) >>> 4];
out += hexChars[c & 0x0f];
out += ' ';
}
return out.trim();
};
// Get the scheme for a URL, undefined if none is found
var getUrlScheme = function(url) {
var i = url.indexOf(':');
if (i == -1) {
return undefined;
}
return url.substring(0, i);
};
// Set a constant on the given object
var setConstant = function(obj, name, value) {
Object.defineProperty(obj, name, {
get : function() {
return value;
}
});
};
// WebSocket object
//
// This is intended to conform (mostly) to http://dev.w3.org/html5/websockets/
//
// N.B. Arguments are parsed in the anonymous function at the bottom of the
// constructor.
var WebSocket = function(url, proto, opts) {
events.EventEmitter.call(this);
// Retain a reference to our object
var self = this;
// State of our end of the connection
var readyState = CONNECTING;
// Whether or not the server has sent a close handshake
var serverClosed = false;
// Our underlying net.Stream instance
var stream = undefined;
opts = opts || {
origin : 'http://www.example.com'
};
// Frame parsing functions
//
// These read data from the given buffer starting at the given offset,
// looking for the end of the current frame. If found, the current frame is
// emitted and the function returns. Only a single frame is processed at a
// time.
//
// The number of bytes read to complete a frame is returned, which the
// caller is to use to advance along its buffer. If 0 is returned, no
// completed frame bytes were found, and the caller should probably enqueue
// the buffer as a continuation of the current message. If a complete frame
// is read, the function is responsible for resting 'frameType'.
// Framing data
var frameType = FRAME_NO;
var bufs = [];
var bufsBytes = 0;
// Frame-parsing functions
var frameFuncs = [
// FRAME_NO
function(buf, off) {
if (buf[off] & 0x80) {
frameType = FRAME_HI;
} else {
frameType = FRAME_LO;
}
return 1;
},
// FRAME_LO
function(buf, off) {
debug('frame_lo(' + sys.inspect(buf) + ', ' + off + ')');
// Find the first instance of 0xff, our terminating byte
for (var i = off; i < buf.length && buf[i] != 0xff; i++)
;
// We didn't find a terminating byte
if (i >= buf.length) {
return 0;
}
// We found a terminating byte; collect all bytes into a single buffer
// and emit it
var mb = null;
if (bufs.length == 0) {
mb = buf.slice(off, i);
} else {
mb = new buffer.Buffer(bufsBytes + i);
var mbOff = 0;
bufs.forEach(function(b) {
b.copy(mb, mbOff, 0, b.length);
mbOff += b.length;
});
assert.equal(mbOff, bufsBytes);
// Don't call Buffer.copy() if we're coping 0 bytes. Rather
// than being a no-op, this will trigger a range violation on
// the destination.
if (i > 0) {
buf.copy(mb, mbOff, off, i);
}
// We consumed all of the buffers that we'd been saving; clear
// things out
bufs = [];
bufsBytes = 0;
}
process.nextTick(function() {
var b = mb;
return function() {
var m = b.toString('utf8');
self.emit('data', b);
self.emit('message', m); // wss compat
if (self.onmessage) {
self.onmessage({data: m});
}
};
}());
frameType = FRAME_NO;
return i - off + 1;
},
// FRAME_HI
function(buf, off) {
debug('frame_hi(' + sys.inspect(buf) + ', ' + off + ')');
if (buf[off] !== 0) {
throw new Error('High-byte framing not supported.');
}
serverClosed = true;
return 1;
}
];
// Handle data coming from our socket
var dataListener = function(buf) {
if (buf.length <= 0 || serverClosed) {
return;
}
debug('dataListener(' + sys.inspect(buf) + ')');
var off = 0;
var consumed = 0;
do {
if (frameType < 0 || frameFuncs.length <= frameType) {
throw new Error('Unexpected frame type: ' + frameType);
}
assert.equal(bufs.length === 0, bufsBytes === 0);
assert.ok(off < buf.length);
consumed = frameFuncs[frameType](buf, off);
off += consumed;
} while (!serverClosed && consumed > 0 && off < buf.length);
if (serverClosed) {
serverCloseHandler();
}
if (consumed == 0) {
bufs.push(buf.slice(off, buf.length));
bufsBytes += buf.length - off;
}
};
// Handle incoming file descriptors
var fdListener = function(fd) {
self.emit('fd', fd);
};
// Handle errors from any source (HTTP client, stream, etc)
var errorListener = function(e) {
process.nextTick(function() {
self.emit('wserror', e);
if (self.onerror) {
self.onerror(e);
}
});
};
// Finish the closing process; destroy the socket and tell the application
// that we've closed.
var finishClose = self.finishClose = function() {
readyState = CLOSED;
if (stream) {
stream.end();
stream.destroy();
stream = undefined;
}
process.nextTick(function() {
self.emit('close');
if (self.onclose) {
self.onclose();
}
});
};
// Send a close frame to the server
var sendClose = function() {
assert.equal(OPEN, readyState);
readyState = CLOSING;
stream.write('\xff\x00', 'binary');
};
// Handle a close packet sent from the server
var serverCloseHandler = function() {
assert.ok(serverClosed);
assert.ok(readyState === OPEN || readyState === CLOSING);
bufs = [];
bufsBytes = 0;
// Handle state transitions asynchronously so that we don't change
// readyState before the application has had a chance to process data
// events which are already in the delivery pipeline. For example, a
// 'data' event could be delivered with a readyState of CLOSING if we
// received both frames in the same packet.
process.nextTick(function() {
if (readyState === OPEN) {
sendClose();
}
finishClose();
});
};
// External API
self.close = function(timeout) {
if (readyState === CONNECTING) {
// If we're still in the process of connecting, the server is not
// in a position to understand our close frame. Just nuke the
// connection and call it a day.
finishClose();
} else if (readyState === OPEN) {
sendClose();
if (timeout) {
setTimeout(finishClose, timeout * 1000);
}
}
};
self.send = function(str, fd) {
if (readyState != OPEN) {
return;
}
stream.write('\x00', 'binary');
stream.write(str, 'utf8', fd);
stream.write('\xff', 'binary');
};
// wss compat
self.write = self.send;
setConstant(self, 'url', url);
Object.defineProperty(self, 'readyState', {
get : function() {
return readyState;
}
});
// Connect and perform handshaking with the server
(function() {
// Parse constructor arguments
if (!url) {
throw new Error('Url and must be specified.');
}
// Secrets used for handshaking
var key1 = createSecretKey();
var key2 = createSecretKey();
var challenge = createChallenge();
debug(
'key1=\'' + str2hex(key1) + '\'; ' +
'key2=\'' + str2hex(key2) + '\'; ' +
'challenge=\'' + str2hex(challenge) + '\''
);
var httpHeaders = {
'Connection' : 'Upgrade',
'Upgrade' : 'WebSocket',
'Sec-WebSocket-Key1' : key1,
'Sec-WebSocket-Key2' : key2
};
if (opts.origin) {
httpHeaders['Origin'] = opts.origin;
}
if (proto) {
httpHeaders['Sec-WebSocket-Protocol'] = proto;
}
var httpPath = '/';
// Create the HTTP client that we'll use for handshaking. We'll cannabalize
// its socket via the 'upgrade' event and leave it to rot.
//
// N.B. The ws+unix:// scheme makes use of the implementation detail
// 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;
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) : "");
break;
case 'ws+unix':
var sockPath = url.substring('ws+unix://'.length, url.length);
httpClient = http.createClient(sockPath);
httpHeaders.Host = 'localhost';
break;
default:
throw new Error('Invalid URL scheme \'' + urlScheme + '\' specified.');
}
httpClient.on('upgrade', (function() {
var data = undefined;
return function(req, s, head) {
stream = s;
stream.on('data', function(d) {
if (d.length <= 0) {
return;
}
if (!data) {
data = d;
} else {
var data2 = new buffer.Buffer(data.length + d.length);
data.copy(data2, 0, 0, data.length);
d.copy(data2, data.length, 0, d.length);
data = data2;
}
if (data.length >= 16) {
var expected = computeSecretKeySignature(key1, key2, challenge);
var actual = data.slice(0, 16).toString('binary');
// Handshaking fails; we're donezo
if (actual != expected) {
debug(
'expected=\'' + str2hex(expected) + '\'; ' +
'actual=\'' + str2hex(actual) + '\''
);
process.nextTick(function() {
// N.B. Emit 'wserror' here, as 'error' is a reserved word in the
// EventEmitter world, and gets thrown.
self.emit(
'wserror',
new Error('Invalid handshake from server:' +
'expected \'' + str2hex(expected) + '\', ' +
'actual \'' + str2hex(actual) + '\''
)
);
if (self.onerror) {
self.onerror();
}
finishClose();
});
}
// Un-register our data handler and add the one to be used
// for the normal, non-handshaking case. If we have extra
// data left over, manually fire off the handler on
// whatever remains.
//
// XXX: This is lame. We should only remove the listeners
// that we added.
httpClient.removeAllListeners('upgrade');
stream.removeAllListeners('data');
stream.on('data', dataListener);
readyState = OPEN;
process.nextTick(function() {
self.emit('open');
if (self.onopen) {
self.onopen();
}
});
// Consume any leftover data
if (data.length > 16) {
stream.emit('data', data.slice(16, data.length));
}
}
});
stream.on('fd', fdListener);
stream.on('error', errorListener);
stream.on('close', function() {
errorListener(new Error('Stream closed unexpectedly.'));
});
stream.emit('data', head);
};
})());
httpClient.on('error', function(e) {
httpClient.end();
errorListener(e);
});
var httpReq = httpClient.request(httpPath, httpHeaders);
httpReq.write(challenge, 'binary');
httpReq.end();
})();
};
sys.inherits(WebSocket, events.EventEmitter);
exports.WebSocket = WebSocket;
// Add some constants to the WebSocket object
setConstant(WebSocket.prototype, 'CONNECTING', CONNECTING);
setConstant(WebSocket.prototype, 'OPEN', OPEN);
setConstant(WebSocket.prototype, 'CLOSING', CLOSING);
setConstant(WebSocket.prototype, 'CLOSED', CLOSED);
// vim:ts=4 sw=4 et

View File

@@ -1,22 +0,0 @@
{
"name" : "websocket-client",
"version" : "1.0.0",
"description" : "An HTML5 Web Sockets client",
"author" : "Peter Griess <pg@std.in>",
"engines" : {
"node" : ">=0.1.98"
},
"repositories" : [
{
"type" : "git",
"url" : "http://github.com/pgriess/node-websocket-client.git"
}
],
"licenses" : [
{
"type" : "BSD",
"url" : "http://github.com/pgriess/node-websocket-client/blob/master/LICENSE"
}
],
"main" : "./lib/websocket"
}

View File

@@ -1,68 +0,0 @@
// Verify that we can connect to a WebSocket server, exchange messages, and
// shut down cleanly.
var assert = require('assert');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PORT = 1024 + Math.floor(Math.random() * 4096);
var C_MSG = 'Client test: ' + (Math.random() * 100);
var S_MSG = 'Server test: ' + (Math.random() * 100);
var serverGotConnection = false;
var clientGotOpen = false;
var clientGotData = false;
var clientGotMessage = false;
var serverGotMessage = false;
var serverGotClose = false;
var clientGotClose = false;
var wss = new WebSocketServer();
wss.listen(PORT, 'localhost');
wss.on('connection', function(c) {
serverGotConnection = true;
c.on('message', function(m) {
assert.equal(m, C_MSG);
serverGotMessage = true;
c.close();
});
c.on('close', function() {
serverGotClose = true;
wss.close();
});
c.write(S_MSG);
});
var ws = new WebSocket('ws://localhost:' + PORT + '/', 'biff');
ws.on('open', function() {
clientGotOpen = true;
});
ws.on('data', function(buf) {
assert.equal(typeof buf, 'object');
assert.equal(buf.toString('utf8'), S_MSG);
clientGotData = true;
ws.send(C_MSG);
});
ws.onmessage = function(m) {
assert.deepEqual(m, {data : S_MSG});
clientGotMessage = true;
};
ws.onclose = function() {
clientGotClose = true;
};
process.on('exit', function() {
assert.ok(serverGotConnection);
assert.ok(clientGotOpen);
assert.ok(clientGotData);
assert.ok(clientGotMessage);
assert.ok(serverGotMessage);
assert.ok(serverGotClose);
assert.ok(clientGotClose);
});

View File

@@ -1,43 +0,0 @@
// Verify that a connection can be closed gracefully from the client.
var assert = require('assert');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PORT = 1024 + Math.floor(Math.random() * 4096);
var C_MSG = 'Client test: ' + (Math.random() * 100);
var serverGotClientMessage = false;
var clientGotServerClose = false;
var serverGotClientClose = false;
var wss = new WebSocketServer();
wss.listen(PORT, 'localhost');
wss.on('connection', function(c) {
c.on('message', function(m) {
assert.equal(m, C_MSG);
serverGotClientMessage = true;
});
c.on('close', function() {
serverGotClientClose = true;
wss.close();
});
});
var ws = new WebSocket('ws://localhost:' + PORT);
ws.onopen = function() {
ws.send(C_MSG);
// XXX: Add a timeout here
ws.close(5);
};
ws.onclose = function() {
assert.equal(ws.CLOSED, ws.readyState);
clientGotServerClose = true;
};
process.on('exit', function() {
assert.ok(serverGotClientMessage);
assert.ok(clientGotServerClose);
assert.ok(serverGotClientClose);
});

View File

@@ -1,43 +0,0 @@
// Verify that some attributes of a WebSocket object are read-only.
var assert = require('assert');
var sys = require('sys');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PORT = 1024 + Math.floor(Math.random() * 4096);
var wss = new WebSocketServer();
wss.listen(PORT, 'localhost');
wss.on('connection', function(c) {
c.close();
wss.close();
});
var ws = new WebSocket('ws://localhost:' + PORT + '/', 'biff');
ws.on('open', function() {
assert.equal(ws.CONNECTING, 0);
try {
ws.CONNECTING = 13;
assert.equal(
ws.CONNECTING, 0,
'Should not have been able to set read-only CONNECTING attribute'
);
} catch (e) {
assert.equal(e.type, 'no_setter_in_callback');
}
assert.equal(ws.OPEN, 1);
assert.equal(ws.CLOSING, 2);
assert.equal(ws.CLOSED, 3);
assert.equal(ws.url, 'ws://localhost:' + PORT + '/');
try {
ws.url = 'foobar';
assert.equal(
ws.url, 'ws://localhost:' + PORT + '/',
'Should not have been able to set read-only url attribute'
);
} catch (e) {
assert.equal(e.type, 'no_setter_in_callback');
}
});

View File

@@ -1,26 +0,0 @@
// Verify that readyState transitions are implemented correctly
var assert = require('assert');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PORT = 1024 + Math.floor(Math.random() * 4096);
var wss = new WebSocketServer();
wss.listen(PORT, 'localhost');
wss.on('connection', function(c) {
c.close();
});
var ws = new WebSocket('ws://localhost:' + PORT);
assert.equal(ws.readyState, ws.CONNECTING);
ws.onopen = function() {
assert.equal(ws.readyState, ws.OPEN);
ws.close();
assert.ok(ws.readyState == ws.CLOSING);
};
ws.onclose = function() {
assert.equal(ws.readyState, ws.CLOSED);
wss.close();
};

View File

@@ -1,41 +0,0 @@
// Verify that a connection can be closed gracefully from the server.
var assert = require('assert');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PORT = 1024 + Math.floor(Math.random() * 4096);
var S_MSG = 'Server test: ' + (Math.random() * 100);
var clientGotServerMessage = false;
var clientGotServerClose = false;
var serverGotClientClose = false;
var wss = new WebSocketServer();
wss.listen(PORT, 'localhost');
wss.on('connection', function(c) {
c.on('close', function() {
serverGotClientClose = true;
wss.close();
});
c.write(S_MSG);
c.close();
});
var ws = new WebSocket('ws://localhost:' + PORT);
ws.onmessage = function(m) {
assert.deepEqual(m, {data: S_MSG});
clientGotServerMessage = true;
};
ws.onclose = function() {
assert.equal(ws.CLOSED, ws.readyState);
clientGotServerClose = true;
};
process.on('exit', function() {
assert.ok(clientGotServerMessage);
assert.ok(clientGotServerClose);
assert.ok(serverGotClientClose);
});

View File

@@ -1,63 +0,0 @@
// Verify that both sides of the WS connection can both send and receive file
// descriptors.
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var sys = require('sys');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PATH = path.join(__dirname, 'sock.' + process.pid);
var C_MSG = 'Client test: ' + (Math.random() * 100);
var S_MSG = 'Server test: ' + (Math.random() * 100);
var clientReceivedData = false;
var clientReceivedFD = false;
var serverReceivedData = false;
var serverReceivedFD = false;
var wss = new WebSocketServer();
wss.on('listening', function() {
var ws = new WebSocket('ws+unix://' + PATH);
ws.on('data', function(d) {
assert.equal(d.toString('utf8'), S_MSG);
clientReceivedData = true;
ws.send(C_MSG, 1);
ws.close();
});
ws.on('fd', function(fd) {
assert.ok(fd >= 0);
clientReceivedFD = true;
});
});
wss.on('connection', function(c) {
c.write(S_MSG, 0);
c._req.socket.on('fd', function(fd) {
assert.ok(fd >= 0);
serverReceivedFD = true;
});
c.on('message', function(d) {
assert.equal(d, C_MSG);
serverReceivedData = true;
wss.close();
});
});
wss.listen(PATH);
process.on('exit', function() {
assert.ok(clientReceivedFD);
assert.ok(clientReceivedData);
assert.ok(serverReceivedFD);
assert.ok(serverReceivedData);
try {
fs.unlinkSync(PATH);
} catch (e) { }
});

View File

@@ -1,46 +0,0 @@
// Verify that we can connect to a server over UNIX domain sockets.
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var sys = require('sys');
var WebSocket = require('../lib/websocket').WebSocket;
var WebSocketServer = require('websocket-server/ws/server').Server;
var PATH = path.join(__dirname, 'sock.' + process.pid);
var S_MSG = 'Server test: ' + (Math.random() * 100);
var serverGotConnection = false;
var clientGotOpen = false;
var clientGotData = false;
var wss = new WebSocketServer();
wss.on('listening', function() {
var ws = new WebSocket('ws+unix://' + PATH);
ws.on('open', function() {
clientGotOpen = true;
ws.close();
});
ws.on('data', function(d) {
assert.equal(d.toString('utf8'), S_MSG);
clientGotData = true;
});
});
wss.on('connection', function(c) {
serverGotConnection = true;
c.write(S_MSG);
wss.close();
});
wss.listen(PATH);
process.on('exit', function() {
assert.ok(serverGotConnection);
assert.ok(clientGotOpen);
assert.ok(clientGotData);
try {
fs.unlinkSync(PATH);
} catch(e) { }
});

View File

@@ -1,244 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var io = require('socket.io')
, parser = io.parser
, http = require('http')
, https = require('https')
, WebSocket = require('../support/node-websocket-client/lib/websocket').WebSocket;
/**
* Exports.
*/
var should = module.exports = require('should');
should.HTTPClient = HTTPClient;
/**
* Client utility.
*
* @api publiC
*/
function HTTPClient (port) {
this.port = port;
this.agent = new http.Agent({
host: 'localhost'
, port: port
});
};
/**
* Issue a request
*
* @api private
*/
HTTPClient.prototype.request = function (path, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.agent = this.agent;
opts.host = 'localhost';
opts.port = this.port;
opts.path = path.replace(/{protocol}/g, io.protocol);
opts.headers = opts.headers || {};
opts.headers.Host = 'localhost';
opts.headers.Connection = 'keep-alive';
var req = http.request(opts, function (res) {
if (false === opts.buffer)
return fn && fn(res);
var buf = '';
res.on('data', function (chunk) {
buf += chunk;
});
res.on('end', function () {
fn && fn(res, opts.parse ? opts.parse(buf) : buf);
});
});
req.on('error', function (err) { });
if (undefined !== opts.data)
req.write(opts.data);
req.end();
return req;
};
/**
* Terminates the client and associated connections.
*
* @api public
*/
HTTPClient.prototype.end = function () {
this.agent.sockets.forEach(function (socket) {
socket.end();
});
};
/**
* Issue a GET request
*
* @api public
*/
HTTPClient.prototype.get = function (path, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.method = 'GET';
// override the parser for transport requests
if (/\/(xhr-polling|htmlfile|jsonp-polling)\//.test(path)) {
// parser that might be necessary for transport-specific framing
var transportParse = opts.parse;
opts.parse = function (data) {
if (data === '') return data;
data = transportParse ? transportParse(data) : data;
return parser.decodePayload(data);
};
} else {
opts.parse = undefined;
}
return this.request(path, opts, fn);
};
/**
* Issue a POST request
*
* @api private
*/
HTTPClient.prototype.post = function (path, data, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.method = 'POST';
opts.data = data;
return this.request(path, opts, fn);
};
/**
* Performs a handshake (GET) request
*
* @api private
*/
HTTPClient.prototype.handshake = function (opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
return this.get('/socket.io/{protocol}', opts, function (res, data) {
fn && fn.apply(null, data.split(':'));
});
};
/**
* Generates a new client for the given port.
*
* @api private
*/
client = function (port) {
return new HTTPClient(port);
};
/**
* Create a socket.io server.
*/
create = function (cl) {
console.log('');
var manager = io.listen(cl.port);
manager.set('client store expiration', 0);
return manager;
};
/**
* WebSocket socket.io client.
*
* @api private
*/
function WSClient (port, sid) {
this.sid = sid;
this.port = port;
WebSocket.call(
this
, 'ws://localhost:' + port + '/socket.io/'
+ io.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
*/
websocket = function (cl, sid) {
return new WSClient(cl.port, sid);
};

View File

@@ -1,21 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAMUSOvlaeyQHMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTAxMTE2MDkzMjQ5WhcNMTMxMTE1MDkzMjQ5WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAz+LXZOjcQCJq3+ZKUFabj71oo/ex/XsBcFqtBThjjTw9CVEVwfPQQp4X
wtPiB204vnYXwQ1/R2NdTQqCZu47l79LssL/u2a5Y9+0NEU3nQA5qdt+1FAE0c5o
exPimXOrR3GWfKz7PmZ2O0117IeCUUXPG5U8umhDe/4mDF4ZNJiKc404WthquTqg
S7rLQZHhZ6D0EnGnOkzlmxJMYPNHSOY1/6ivdNUUcC87awNEA3lgfhy25IyBK3QJ
c+aYKNTbt70Lery3bu2wWLFGtmNiGlQTS4JsxImRsECTI727ObS7/FWAQsqW+COL
0Sa5BuMFrFIpjPrEe0ih7vRRbdmXRwIDAQABo1AwTjAdBgNVHQ4EFgQUDnV4d6mD
tOnluLoCjkUHTX/n4agwHwYDVR0jBBgwFoAUDnV4d6mDtOnluLoCjkUHTX/n4agw
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAFwV4MQfTo+qMv9JMiyno
IEiqfOz4RgtmBqRnXUffcjS2dhc7/z+FPZnM79Kej8eLHoVfxCyWRHFlzm93vEdv
wxOCrD13EDOi08OOZfxWyIlCa6Bg8cMAKqQzd2OvQOWqlRWBTThBJIhWflU33izX
Qn5GdmYqhfpc+9ZHHGhvXNydtRQkdxVK2dZNzLBvBlLlRmtoClU7xm3A+/5dddeP
AQHEPtyFlUw49VYtZ3ru6KqPms7MKvcRhYLsy9rwSfuuniMlx4d0bDR7TOkw0QQS
A0N8MGQRQpzl4mw4jLzyM5d5QtuGBh2P6hPGa0YQxtI3RPT/p6ENzzBiAKXiSfzo
xw==
-----END CERTIFICATE-----

27
test/fixtures/key.key vendored
View File

@@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAz+LXZOjcQCJq3+ZKUFabj71oo/ex/XsBcFqtBThjjTw9CVEV
wfPQQp4XwtPiB204vnYXwQ1/R2NdTQqCZu47l79LssL/u2a5Y9+0NEU3nQA5qdt+
1FAE0c5oexPimXOrR3GWfKz7PmZ2O0117IeCUUXPG5U8umhDe/4mDF4ZNJiKc404
WthquTqgS7rLQZHhZ6D0EnGnOkzlmxJMYPNHSOY1/6ivdNUUcC87awNEA3lgfhy2
5IyBK3QJc+aYKNTbt70Lery3bu2wWLFGtmNiGlQTS4JsxImRsECTI727ObS7/FWA
QsqW+COL0Sa5BuMFrFIpjPrEe0ih7vRRbdmXRwIDAQABAoIBAGe4+9VqZfJN+dsq
8Osyuz01uQ8OmC0sAWTIqUlQgENIyf9rCJsUBlYmwR5BT6Z69XP6QhHdpSK+TiAR
XUz0EqG9HYzcxHIBaACP7j6iRoQ8R4kbbiWKo0z3WqQGIOqFjvD/mKEuQdE5mEYw
eOUCG6BnX1WY2Yr8WKd2AA/tp0/Y4d8z04u9eodMpSTbHTzYMJb5SbBN1vo6FY7q
8zSuO0BMzXlAxUsCwHsk1GQHFr8Oh3zIR7bQGtMBouI+6Lhh7sjFYsfxJboqMTBV
IKaA216M6ggHG7MU1/jeKcMGDmEfqQLQoyWp29rMK6TklUgipME2L3UD7vTyAVzz
xbVOpZkCgYEA8CXW4sZBBrSSrLR5SB+Ubu9qNTggLowOsC/kVKB2WJ4+xooc5HQo
mFhq1v/WxPQoWIxdYsfg2odlL+JclK5Qcy6vXmRSdAQ5lK9gBDKxZSYc3NwAw2HA
zyHCTK+I0n8PBYQ+yGcrxu0WqTGnlLW+Otk4CejO34WlgHwbH9bbY5UCgYEA3ZvT
C4+OoMHXlmICSt29zUrYiL33IWsR3/MaONxTEDuvgkOSXXQOl/8Ebd6Nu+3WbsSN
bjiPC/JyL1YCVmijdvFpl4gjtgvfJifs4G+QHvO6YfsYoVANk4u6g6rUuBIOwNK4
RwYxwDc0oysp+g7tPxoSgDHReEVKJNzGBe9NGGsCgYEA4O4QP4gCEA3B9BF2J5+s
n9uPVxmiyvZUK6Iv8zP4pThTBBMIzNIf09G9AHPQ7djikU2nioY8jXKTzC3xGTHM
GJZ5m6fLsu7iH+nDvSreDSeNkTBfZqGAvoGYQ8uGE+L+ZuRfCcXYsxIOT5s6o4c3
Dle2rVFpsuKzCY00urW796ECgYBn3go75+xEwrYGQSer6WR1nTgCV29GVYXKPooy
zmmMOT1Yw80NSkEw0pFD4cTyqVYREsTrPU0mn1sPfrOXxnGfZSVFpcR/Je9QVfQ7
eW7GYxwfom335aqHVj10SxRqteP+UoWWnHujCPz94VRKZMakBddYCIGSan+G6YdS
7sdmwwKBgBc2qj0wvGXDF2kCLwSGfWoMf8CS1+5fIiUIdT1e/+7MfDdbmLMIFVjF
QKS3zVViXCbrG5SY6wS9hxoc57f6E2A8vcaX6zy2xkZlGHQCpWRtEM5R01OWJQaH
HsHMmQZGUQVoDm1oRkDhrTFK4K3ukc3rAxzeTZ96utOQN8/KJsTv
-----END RSA PRIVATE KEY-----

View File

@@ -1,125 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, fs = require('fs')
, http = require('http')
, https = require('https')
, should = require('./common')
, ports = 15000;
/**
* Test.
*/
module.exports = {
'test that protocol version is present': function (done) {
sio.protocol.should.be.a('number');
done();
},
'test that default transports are present': function (done) {
sio.Manager.defaultTransports.should.be.an.instanceof(Array);
done();
},
'test that version is present': function (done) {
sio.version.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
done();
},
'test listening with a port': function (done) {
var cl = client(++ports)
, io = create(cl);
io.server.should.be.an.instanceof(http.Server);
cl.get('/', function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('Welcome to socket.io.');
cl.end();
io.server.close();
done();
});
},
'test listening with a server': function (done) {
var server = http.createServer()
, io = sio.listen(server)
, port = ++ports
, cl = client(port);
server.listen(port);
cl.get('/socket.io', function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('Welcome to socket.io.');
cl.end();
server.close();
done();
});
},
'test listening with a https server': function (done) {
var server = https.createServer({
key: fs.readFileSync(__dirname + '/fixtures/key.key')
, cert: fs.readFileSync(__dirname + '/fixtures/cert.crt')
}, function () { })
, io = sio.listen(server)
, port = ++ports;
server.listen(port);
var req = require('https').get({
host: 'localhost'
, port: port
, path: '/socket.io'
}, function (res) {
res.statusCode.should.eql(200);
var buf = '';
res.on('data', function (data) {
buf += data;
});
res.on('end', function () {
buf.should.eql('Welcome to socket.io.');
res.socket.end();
server.close();
done();
});
});
},
'test listening with no arguments listens on 80': function (done) {
try {
var io = sio.listen()
, cl = client(80);
cl.get('/socket.io', function (res) {
res.statusCode.should.eql(200);
cl.end();
io.server.close();
done();
});
} catch (e) {
e.should.match(/EACCES/);
done();
}
}
};

View File

@@ -1,54 +0,0 @@
/*!
* 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,731 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, http = require('http')
, should = require('./common')
, ports = 15100;
/**
* Test.
*/
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();
},
'test enabling and disabling a configuration flag': function (done) {
var port = ++ports
, io = sio.listen(http.createServer());
io.enable('flag');
io.enabled('flag').should.be.true;
io.disabled('flag').should.be.false;
io.disable('flag');
var port = ++ports
, io = sio.listen(http.createServer());
io.configure(function () {
io.enable('tobi');
});
io.enabled('tobi').should.be.true;
done();
},
'test configuration callbacks with envs': function (done) {
var port = ++ports
, io = sio.listen(http.createServer());
process.env.NODE_ENV = 'development';
io.configure('production', function () {
io.set('ferret', 'tobi');
});
io.configure('development', function () {
io.set('ferret', 'jane');
});
io.get('ferret').should.eql('jane');
done();
},
'test configuration callbacks conserve scope': function (done) {
var port = ++ports
, io = sio.listen(http.createServer())
, calls = 0;
process.env.NODE_ENV = 'development';
io.configure(function () {
this.should.eql(io);
calls++;
});
io.configure('development', function () {
this.should.eql(io);
calls++;
});
calls.should.eql(2);
done();
},
'test configuration update notifications': function (done) {
var port = ++ports
, io = sio.listen(http.createServer())
, calls = 0;
io.on('set:foo', function () {
calls++;
});
io.set('foo', 'bar');
io.set('baz', 'bar');
calls.should.eql(1);
io.enable('foo');
io.disable('foo');
calls.should.eql(3);
done();
},
'test that normal requests are still served': function (done) {
var server = http.createServer(function (req, res) {
res.writeHead(200);
res.end('woot');
});
var io = sio.listen(server)
, port = ++ports
, cl = client(port);
server.listen(ports);
cl.get('/socket.io', function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('Welcome to socket.io.');
cl.get('/woot', function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('woot');
cl.end();
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 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 the cached client sends a 304 header': 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) {
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.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();
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)
, cl = client(port);
io.configure(function () {
io.disable('browser client');
});
cl.get('/socket.io/socket.io.js', function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('Welcome to socket.io.');
cl.end();
io.server.close();
done();
});
},
'test handshake': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(200);
data.should.match(/([^:]+):([0-9]+)?:([0-9]+)?:(.+)/);
cl.end();
io.server.close();
done();
});
},
'test handshake with unsupported protocol version': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/-1/', function (res, data) {
res.statusCode.should.eql(500);
data.should.match(/Protocol version not supported/);
cl.end();
io.server.close();
done();
});
},
'test authorization failure in handshake': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
function auth (data, fn) {
fn(null, false);
};
io.set('authorization', auth);
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(403);
data.should.match(/handshake unauthorized/);
cl.end();
io.server.close();
done();
});
},
'test a handshake error': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
function auth (data, fn) {
fn(new Error);
};
io.set('authorization', auth);
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(500);
data.should.match(/handshake error/);
cl.end();
io.server.close();
done();
});
},
'test that a referer is accepted for *:* origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', '*:*');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com:82/something' } }, function (res, data) {
res.statusCode.should.eql(200);
cl.end();
io.server.close();
done();
});
},
'test that valid referer is accepted for addr:* origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:*');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
res.statusCode.should.eql(200);
cl.end();
io.server.close();
done();
});
},
'test that erroneous referer is denied for addr:* origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:*');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://baz.bar.com/something' } }, function (res, data) {
res.statusCode.should.eql(403);
cl.end();
io.server.close();
done();
});
},
'test that valid referer port is accepted for addr:port origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:81');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com:81/something' } }, function (res, data) {
res.statusCode.should.eql(200);
cl.end();
io.server.close();
done();
});
},
'test that erroneous referer port is denied for addr:port origin': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('origins', 'foo.bar.com:81');
});
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
res.statusCode.should.eql(403);
cl.end();
io.server.close();
done();
});
},
'test limiting the supported transports for a manager': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('transports', ['tobi', 'jane']);
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(200);
data.should.match(/([^:]+):([0-9]+)?:([0-9]+)?:tobi,jane/);
cl.end();
io.server.close();
done();
});
},
'test setting a custom close timeout': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('close timeout', 66);
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(200);
data.should.match(/([^:]+):([0-9]+)?:66?:(.*)/);
cl.end();
io.server.close();
done();
});
},
'test setting a custom heartbeat timeout': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('heartbeat timeout', 33);
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(200);
data.should.match(/([^:]+):33:([0-9]+)?:(.*)/);
cl.end();
io.server.close();
done();
});
},
'test disabling timeouts': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('heartbeat timeout', null);
io.set('close timeout', '');
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(200);
data.should.match(/([^:]+)::?:(.*)/);
cl.end();
io.server.close();
done();
});
},
'test disabling heartbeats': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port)
, messages = 0
, beat = false
, ws;
io.configure(function () {
io.disable('heartbeats');
io.set('heartbeat interval', .05);
io.set('heartbeat timeout', .05);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
setTimeout(function () {
socket.disconnect();
}, io.get('heartbeat timeout') * 1000 + 100);
socket.on('disconnect', function (reason) {
beat.should.be.false;
cl.end();
ws.finishClose();
io.server.close();
done();
});
});
cl.get('/socket.io/{protocol}/', function (res, data) {
res.statusCode.should.eql(200);
data.should.match(/([^:]+)::[\.0-9]+:(.*)/);
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('message', function (packet) {
if (++messages == 1) {
packet.type.should.eql('connect');
} else if (packet.type == 'heartbeat'){
beat = true;
}
});
});
});
},
'no duplicate room members': function (done) {
var port = ++ports
, io = sio.listen(port);
Object.keys(io.rooms).length.should.equal(0);
io.onJoin(123, 'foo');
io.rooms.foo.length.should.equal(1);
io.onJoin(123, 'foo');
io.rooms.foo.length.should.equal(1);
io.onJoin(124, 'foo');
io.rooms.foo.length.should.equal(2);
io.onJoin(124, 'foo');
io.rooms.foo.length.should.equal(2);
io.onJoin(123, 'bar');
io.rooms.foo.length.should.equal(2);
io.rooms.bar.length.should.equal(1);
io.onJoin(123, 'bar');
io.rooms.foo.length.should.equal(2);
io.rooms.bar.length.should.equal(1);
io.onJoin(124, 'bar');
io.rooms.foo.length.should.equal(2);
io.rooms.bar.length.should.equal(2);
io.onJoin(124, 'bar');
io.rooms.foo.length.should.equal(2);
io.rooms.bar.length.should.equal(2);
io.server.close();
done();
},
'test passing options directly to the Manager through listen': function (done) {
var port = ++ports
, io = sio.listen(port, { resource: '/my resource', custom: 'opt' });
io.get('resource').should.equal('/my resource');
io.get('custom').should.equal('opt');
io.server.close();
done();
}
};

View File

@@ -1,247 +0,0 @@
/*!
* 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){
socket.broadcast.emit('b', 'test');
socket.broadcast.json.emit('json', {foo:'bar'});
socket.broadcast.send('foo');
});
function finish () {
connect.should.equal(2);
message.should.equal(1);
events.should.equal(2);
cl.end();
ws1.finishClose();
ws2.finishClose();
io.server.close();
done();
}
cl.handshake(function (sid) {
ws1 = websocket(cl, sid);
ws1.on('open', function() {
ws1.packet({
type: 'connect'
, endpoint: 'a'
});
});
ws1.on('message', function (data) {
if (data.type === 'connect') {
++connect;
if (++calls === expected) finish();
}
if (data.type === 'message') {
++message;
if (++calls === expected) finish();
}
if (data.type === 'event') {
if (data.name === 'b' || data.name === 'json') ++events;
if (++calls === expected) finish();
}
});
cl.handshake(function (sid) {
ws2 = websocket(cl, sid);
ws2.on('open', function () {
ws2.packet({
type: 'connect'
, endpoint: 'a'
});
});
})
})
},
'joining rooms inside a namespace': function (done) {
var cl = client(++ports)
, io = create(cl)
, calls = 0
, ws;
io.of('/foo').on('connection', function (socket) {
socket.join('foo.bar');
this.in('foo.bar').emit('baz', 'pewpew');
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function (){
ws.packet({
type: 'connect'
, endpoint: '/foo'
});
});
ws.on('message', function (data) {
if (data.type === 'event') {
data.name.should.equal('baz');
cl.end();
ws.finishClose();
io.server.close();
done();
}
});
})
}
};

View File

@@ -1,356 +0,0 @@
/**
* Test dependencies.
*/
var parser = require('socket.io').parser
, decode = parser.decode
, should = require('./common');
/**
* Test.
*/
module.exports = {
'decoding error packet': function () {
parser.decodePacket('7:::').should.eql({
type: 'error'
, reason: ''
, advice: ''
, endpoint: ''
});
},
'decoding error packet with reason': function () {
parser.decodePacket('7:::0').should.eql({
type: 'error'
, reason: 'transport not supported'
, advice: ''
, endpoint: ''
});
},
'decoding error packet with reason and advice': function () {
parser.decodePacket('7:::2+0').should.eql({
type: 'error'
, reason: 'unauthorized'
, advice: 'reconnect'
, endpoint: ''
});
},
'decoding error packet with endpoint': function () {
parser.decodePacket('7::/woot').should.eql({
type: 'error'
, reason: ''
, advice: ''
, endpoint: '/woot'
});
},
'decoding ack packet': function () {
parser.decodePacket('6:::140').should.eql({
type: 'ack'
, ackId: '140'
, endpoint: ''
, args: []
});
},
'decoding ack packet with args': function () {
parser.decodePacket('6:::12+["woot","wa"]').should.eql({
type: 'ack'
, ackId: '12'
, endpoint: ''
, args: ['woot', 'wa']
});
},
'decoding ack packet with bad json': function () {
var thrown = false;
try {
parser.decodePacket('6:::1+{"++]').should.eql({
type: 'ack'
, ackId: '1'
, endpoint: ''
, args: []
});
} catch (e) {
thrown = true;
}
thrown.should.be.false;
},
'decoding json packet': function () {
parser.decodePacket('4:::"2"').should.eql({
type: 'json'
, endpoint: ''
, data: '2'
});
},
'decoding json packet with message id and ack data': function () {
parser.decodePacket('4:1+::{"a":"b"}').should.eql({
type: 'json'
, id: 1
, ack: 'data'
, endpoint: ''
, data: { a: 'b' }
});
},
'decoding an event packet': function () {
parser.decodePacket('5:::{"name":"woot"}').should.eql({
type: 'event'
, name: 'woot'
, endpoint: ''
, args: []
});
},
'decoding an event packet with message id and ack': function () {
parser.decodePacket('5:1+::{"name":"tobi"}').should.eql({
type: 'event'
, id: 1
, ack: 'data'
, endpoint: ''
, name: 'tobi'
, args: []
});
},
'decoding an event packet with data': function () {
parser.decodePacket('5:::{"name":"edwald","args":[{"a": "b"},2,"3"]}')
.should.eql({
type: 'event'
, name: 'edwald'
, endpoint: ''
, args: [{a: 'b'}, 2, '3']
});
},
'decoding a message packet': function () {
parser.decodePacket('3:::woot').should.eql({
type: 'message'
, endpoint: ''
, data: 'woot'
});
},
'decoding a message packet with id and endpoint': function () {
parser.decodePacket('3:5:/tobi').should.eql({
type: 'message'
, id: 5
, ack: true
, endpoint: '/tobi'
, data: ''
});
},
'decoding a heartbeat packet': function () {
parser.decodePacket('2:::').should.eql({
type: 'heartbeat'
, endpoint: ''
});
},
'decoding a connection packet': function () {
parser.decodePacket('1::/tobi').should.eql({
type: 'connect'
, endpoint: '/tobi'
, qs: ''
});
},
'decoding a connection packet with query string': function () {
parser.decodePacket('1::/test:?test=1').should.eql({
type: 'connect'
, endpoint: '/test'
, qs: '?test=1'
});
},
'decoding a disconnection packet': function () {
parser.decodePacket('0::/woot').should.eql({
type: 'disconnect'
, endpoint: '/woot'
});
},
'encoding error packet': function () {
parser.encodePacket({
type: 'error'
, reason: ''
, advice: ''
, endpoint: ''
}).should.eql('7::');
},
'encoding error packet with reason': function () {
parser.encodePacket({
type: 'error'
, reason: 'transport not supported'
, advice: ''
, endpoint: ''
}).should.eql('7:::0');
},
'encoding error packet with reason and advice': function () {
parser.encodePacket({
type: 'error'
, reason: 'unauthorized'
, advice: 'reconnect'
, endpoint: ''
}).should.eql('7:::2+0');
},
'encoding error packet with endpoint': function () {
parser.encodePacket({
type: 'error'
, reason: ''
, advice: ''
, endpoint: '/woot'
}).should.eql('7::/woot');
},
'encoding ack packet': function () {
parser.encodePacket({
type: 'ack'
, ackId: '140'
, endpoint: ''
, args: []
}).should.eql('6:::140');
},
'encoding ack packet with args': function () {
parser.encodePacket({
type: 'ack'
, ackId: '12'
, endpoint: ''
, args: ['woot', 'wa']
}).should.eql('6:::12+["woot","wa"]');
},
'encoding json packet': function () {
parser.encodePacket({
type: 'json'
, endpoint: ''
, data: '2'
}).should.eql('4:::"2"');
},
'encoding json packet with message id and ack data': function () {
parser.encodePacket({
type: 'json'
, id: 1
, ack: 'data'
, endpoint: ''
, data: { a: 'b' }
}).should.eql('4:1+::{"a":"b"}');
},
'encoding an event packet': function () {
parser.encodePacket({
type: 'event'
, name: 'woot'
, endpoint: ''
, args: []
}).should.eql('5:::{"name":"woot"}');
},
'encoding an event packet with message id and ack': function () {
parser.encodePacket({
type: 'event'
, id: 1
, ack: 'data'
, endpoint: ''
, name: 'tobi'
, args: []
}).should.eql('5:1+::{"name":"tobi"}');
},
'encoding an event packet with data': function () {
parser.encodePacket({
type: 'event'
, name: 'edwald'
, endpoint: ''
, args: [{a: 'b'}, 2, '3']
}).should.eql('5:::{"name":"edwald","args":[{"a":"b"},2,"3"]}');
},
'encoding a message packet': function () {
parser.encodePacket({
type: 'message'
, endpoint: ''
, data: 'woot'
}).should.eql('3:::woot');
},
'encoding a message packet with id and endpoint': function () {
parser.encodePacket({
type: 'message'
, id: 5
, ack: true
, endpoint: '/tobi'
, data: ''
}).should.eql('3:5:/tobi');
},
'encoding a heartbeat packet': function () {
parser.encodePacket({
type: 'heartbeat'
, endpoint: ''
}).should.eql('2::');
},
'encoding a connection packet': function () {
parser.encodePacket({
type: 'connect'
, endpoint: '/tobi'
, qs: ''
}).should.eql('1::/tobi');
},
'encoding a connection packet with query string': function () {
parser.encodePacket({
type: 'connect'
, endpoint: '/test'
, qs: '?test=1'
}).should.eql('1::/test:?test=1');
},
'encoding a disconnection packet': function () {
parser.encodePacket({
type: 'disconnect'
, endpoint: '/woot'
}).should.eql('0::/woot');
},
'test decoding a payload': function () {
parser.decodePayload('\ufffd5\ufffd3:::5\ufffd7\ufffd3:::53d'
+ '\ufffd3\ufffd0::').should.eql([
{ type: 'message', data: '5', endpoint: '' }
, { type: 'message', data: '53d', endpoint: '' }
, { type: 'disconnect', endpoint: '' }
]);
},
'test encoding a payload': function () {
parser.encodePayload([
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 = {
};

View File

@@ -1,190 +0,0 @@
/**
* Test dependencies
*
* @api private
*/
var sio = require('socket.io')
, 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);
});
}
};

View File

@@ -1,240 +0,0 @@
/**
* Test dependencies
*
* @api private
*/
var sio = require('socket.io')
, 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

@@ -1,168 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, net = require('net')
, http = require('http')
, should = require('./common')
, WebSocket = require('../support/node-websocket-client/lib/websocket').WebSocket
, WSClient = require('./transports.websocket.test')
, parser = sio.parser
, ports = 15600;
/**
* FlashSocket client constructor.
*
* @api private
*/
function FlashSocket (port, sid) {
this.sid = sid;
this.port = port;
WebSocket.call(
this
, 'ws://localhost:' + port + '/socket.io/'
+ sio.protocol + '/flashsocket/' + sid
);
};
/**
* Inherits from WSClient.
*/
FlashSocket.prototype.__proto__ = WebSocket.prototype;
/**
* Creates a TCP connection to a port.
*
* @api public
*/
function netConnection (port, callback){
var nclient = net.createConnection(port);
nclient.on('data', function (data) {
callback.call(nclient, null, data);
});
nclient.on('error', function (e){
callback.call(nclient, e);
});
nclient.write('<policy-file-request/>\0');
}
/**
* Tests.
*/
module.exports = {
'flashsocket disabled by default': function (done) {
var io = sio.listen(http.createServer());
io.get('transports').should.not.contain('flashsocket');
done();
},
'flash policy port': function (done) {
var io = sio.listen(http.createServer())
, port = ++ports;
io.get('flash policy port').should.eql(10843);
io.set('flash policy port', port);
io.get('flash policy port').should.eql(port);
should.strictEqual(io.flashPolicyServer, undefined);
netConnection(port, function (err, data){
err.should.be.an.instanceof(Error);
err.code.should.eql('ECONNREFUSED');
this.destroy();
done();
})
},
'start flash policy': function (done) {
var io = sio.listen(http.createServer())
, port = ++ports;
io.set('flash policy port', port);
io.set('transports', ['flashsocket']);
io.flashPolicyServer.should.be.a('object');
netConnection(port, function (err, data){
should.strictEqual(err, null);
data.toString().should.include.string('<cross-domain-policy>');
this.destroy();
io.flashPolicyServer.close();
done();
})
},
'change running flash server port': function (done) {
var io = sio.listen(http.createServer())
, port = ++ports
, next = ++ports;
io.set('flash policy port', port);
io.set('transports', ['flashsocket']);
io.set('flash policy port', next);
io.flashPolicyServer.port.should.eql(next);
netConnection(port, function (err, data){
err.should.be.an.instanceof(Error);
err.code.should.eql('ECONNREFUSED');
this.destroy();
// should work
netConnection(next, function (err, data){
should.strictEqual(err, null);
data.toString().should.include.string('<cross-domain-policy>');
this.destroy();
io.flashPolicyServer.close();
done();
});
});
},
'different origins': function(done) {
var io = sio.listen(http.createServer())
, port = ++ports;
io.set('flash policy port', port);
io.set('transports', ['flashsocket']);
io.set('origins', 'google.com:80');
var server = io.flashPolicyServer;
server.origins.should.contain('google.com:80');
server.origins.should.not.contain('*.*');
io.set('origins', ['foo.bar:80', 'socket.io:1337']);
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');
io.flashPolicyServer.close();
done();
}
};

View File

@@ -1,458 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, should = require('./common')
, HTTPClient = should.HTTPClient
, parser = sio.parser
, ports = 15300;
/**
* HTTPClient for htmlfile transport.
*/
function HTMLFile (port) {
HTTPClient.call(this, port);
};
/**
* Inhertis from HTTPClient.
*/
HTMLFile.prototype.__proto__ = HTTPClient.prototype;
/**
* Override GET request with streaming parser.
*
* @api public
*/
var head = '<script>_('
, foot = ');</script>'
, initial = '<html><body>'
+ '<script>var _ = function (msg) { parent.s._(msg, document); };</script>'
+ new Array(174).join(' ')
HTMLFile.prototype.data = function (path, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts.buffer = false;
return this.request(path, opts, function (res) {
var buf = ''
, messages = 0
, state = 0;
res.on('data', function (chunk) {
buf += chunk;
function parse () {
switch (state) {
case 0:
if (buf.indexOf(initial) === 0) {
buf = buf.substr(initial.length);
state = 1;
} else {
break;
}
case 1:
if (buf.indexOf(head) === 0) {
buf = buf.substr(head.length);
state = 2;
} else {
break;
}
case 2:
if (buf.indexOf(foot) != -1) {
var data = buf.slice(0, buf.indexOf(foot))
, obj = JSON.parse(data);
fn(obj === '' ? obj : parser.decodePayload(obj), ++messages);
buf = buf.substr(data.length + foot.length);
state = 1;
parse();
}
};
};
parse();
});
});
};
/**
* Create client for this transport.
*
* @api public
*/
function client (port) {
return new HTMLFile(port);
};
/**
* Tests.
*/
module.exports = {
'test that not responding to a heartbeat drops client': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, beat = false;
io.configure(function () {
io.set('heartbeat interval', .05);
io.set('heartbeat timeout', .05);
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function (reason) {
beat.should.be.true;
reason.should.eql('heartbeat timeout');
cl.end();
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].type.should.eql('heartbeat');
beat = true;
};
});
});
},
'test that responding to a heartbeat maintains session': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, heartbeats = 0;
io.configure(function () {
io.set('heartbeat interval', .05);
io.set('heartbeat timeout', .05);
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function (reason) {
heartbeats.should.eql(2);
reason.should.eql('heartbeat timeout');
cl.end();
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;
default:
msgs.should.have.length(1);
msgs[0].type.should.eql('heartbeat');
heartbeats++;
if (heartbeats == 1) {
cl.post('/socket.io/{protocol}/htmlfile/' + sid, parser.encodePacket({
type: 'heartbeat'
}));
}
}
});
});
},
'test sending undeliverable volatile messages': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, messaged = false
, s;
io.configure(function () {
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
s = socket;
socket.on('disconnect', function () {
messaged.should.be.false;
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function () { });
setTimeout(function () {
cl.end();
setTimeout(function () {
s.volatile.send('wooooot');
cl = client(port);
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs) {
if (msgs && msgs.length)
messaged = true;
});
setTimeout(function () {
cl.end();
}, 20);
}, 20);
}, 20);
});
},
'test sending undeliverable volatile json': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, messaged = false
, s;
io.configure(function () {
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
s = socket;
socket.on('disconnect', function () {
messaged.should.be.false;
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function () { });
setTimeout(function () {
cl.end();
setTimeout(function () {
s.volatile.json.send(123);
cl = client(port);
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs) {
if (msgs && msgs.length)
messaged = true;
});
setTimeout(function () {
cl.end();
}, 20);
}, 20);
}, 20);
});
},
'test sending undeliverable volatile events': function (done) {
var port = ++ports
, cl = client(port)
, io = create(cl)
, messaged = false
, s;
io.configure(function () {
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
s = socket;
socket.on('disconnect', function () {
messaged.should.be.false;
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function () { });
setTimeout(function () {
cl.end();
setTimeout(function () {
s.volatile.emit('tobi');
cl = client(port);
cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs) {
if (msgs && msgs.length)
messaged = true;
});
setTimeout(function () {
cl.end();
}, 20);
}, 20);
}, 20);
});
},
'test sending deliverable volatile messages': 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.volatile.send('woot');
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: 'message'
, data: 'woot'
, endpoint: ''
});
cl.end();
}
});
});
},
'test sending deliverable volatile json': 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.volatile.json.send(['woot']);
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: 'json'
, data: ['woot']
, endpoint: ''
});
cl.end();
}
});
});
},
'test sending deliverable volatile events': 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.volatile.emit('aaa');
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: 'aaa'
, endpoint: ''
, args: []
});
cl.end();
}
});
});
}
};

View File

@@ -1,770 +0,0 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, should = require('./common')
, qs = require('querystring')
, HTTPClient = should.HTTPClient
, parser = sio.parser
, ports = 15500;
/**
* HTTPClient for jsonp-polling transport.
*/
function JSONPPolling (port) {
HTTPClient.call(this, port);
};
/**
* Inhertis from HTTPClient.
*/
JSONPPolling.prototype.__proto__ = HTTPClient.prototype;
/**
* Performs a json-p (cross domain) handshake
*
* @api public
*/
JSONPPolling.prototype.handshake = function (opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
var self = this;
return this.get(
'/socket.io/{protocol}?jsonp=0'
, opts
, function (res, data) {
var head = 'io.j[0]('
, foot = ');';
data.substr(0, head.length).should.eql(head);
data.substr(-foot.length).should.eql(foot);
data = data.slice(head.length, data.length - foot.length);
var parts = JSON.parse(data).split(':');
if (opts.ignoreConnect) {
return fn && fn.apply(null, parts);
}
// expect connect packet right after handshake
self.get(
'/socket.io/{protocol}/jsonp-polling/' + parts[0]
, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'connect', endpoint: '', qs: '' });
fn && fn.apply(null, parts);
}
);
}
);
};
/**
* Override GET requests.
*
* @api public
*/
JSONPPolling.prototype.get = function (path, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.parse = function (data) {
var head = 'io.j[0]('
, foot = ');';
if (~path.indexOf('?i=1')) {
head = 'io.j[1](';
}
data.substr(0, head.length).should.eql(head);
data.substr(-foot.length).should.eql(foot);
data = data.substr(head.length, data.length - head.length - foot.length);
return JSON.parse(data);
};
return HTTPClient.prototype.get.call(this, path, opts, fn);
};
/**
* Issue an encoded POST request
*
* @api private
*/
JSONPPolling.prototype.post = function (path, data, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.method = 'POST';
opts.data = qs.stringify({ d: data });
return this.request(path, opts, fn);
};
/**
* Create client for this transport.
*
* @api public
*/
function client (port) {
return new JSONPPolling(port);
};
/**
* Test.
*/
module.exports = {
'test jsonp handshake': function (done) {
var cl = client(++ports)
, io = create(cl);
io.configure(function () {
io.set('close timeout', .05);
io.set('polling duration', 0);
});
function finish () {
cl.end();
io.server.close();
done();
};
cl.handshake(function (sid) {
var total = 2;
cl.get('/socket.io/{protocol}/jsonp-polling/tobi', function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].should.eql({
type: 'error'
, reason: 'client not handshaken'
, endpoint: ''
, advice: 'reconnect'
});
--total || finish();
});
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
--total || finish();
});
});
},
'test the connection event': function (done) {
var cl = client(++ports)
, io = create(cl)
, sid;
io.configure(function () {
io.set('polling duration', 0);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
socket.id.should.eql(sid);
socket.on('disconnect', function () {
cl.end();
io.server.close();
done();
});
});
cl.handshake({ ignoreConnect: true }, function (sessid) {
sid = sessid;
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
});
});
},
'test the disconnection event after a close timeout': function (done) {
var cl = client(++ports)
, io = create(cl)
, sid;
io.configure(function () {
io.set('close timeout', 0);
});
io.sockets.on('connection', function (socket) {
socket.id.should.eql(sid);
socket.on('disconnect', function () {
io.server.close();
done();
});
});
cl.handshake({ ignoreConnect: true }, function (sessid) {
sid = sessid;
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
setTimeout(function () {
cl.end();
}, 10);
});
});
},
'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.sockets.on('connection', function (socket) {
socket.on('disconnect', function () {
disconnected = true;
});
});
cl.handshake({ ignoreConnect: true }, function (sessid) {
sid = sessid;
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'disconnect', endpoint: '' });
disconnected.should.be.true;
cl.end();
io.server.close();
done();
});
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid + '/?disconnect');
});
});
},
'test the disconnection event booting a client': function (done) {
var cl = client(++ports)
, io = create(cl)
, forced = false;
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function () {
io.server.close();
done();
});
cl.end();
socket.disconnect();
forced = true;
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'disconnect', endpoint: '' });
forced.should.be.true;
});
});
},
'test the disconnection event with client disconnect packet': function (done) {
var cl = client(++ports)
, io = create(cl)
, sid;
io.sockets.on('connection', function (client) {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'disconnect' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
client.on('disconnect', function () {
cl.end();
io.server.close();
done();
});
});
cl.handshake({ ignoreConnect: true }, function (sessid) {
sid = sessid;
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
});
});
},
'test sending back data': 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.send('woot');
socket.on('disconnect', function () {
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, packs) {
packs.should.have.length(1);
packs[0].type.should.eql('message');
packs[0].data.should.eql('woot');
});
});
},
'test sending a batch of messages': function (done) {
var cl = client(++ports)
, io = create(cl)
, sid;
io.configure(function () {
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
var messages = 0;
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, 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('1');
}
);
socket.on('message', function (data) {
messages++;
if (messages == 1)
data.should.eql('a');
if (messages == 2)
data.should.eql('b');
});
socket.on('disconnect', function () {
messages.should.eql(2);
cl.end();
io.server.close();
done();
});
});
cl.handshake({ ignoreConnect: true }, function (sessid) {
sid = sessid;
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
});
});
},
'test message buffering between a response and a request': function (done) {
var cl = client(++ports)
, io = create(cl)
, messages = false
, tobi;
io.configure(function () {
io.set('polling duration', .1);
io.set('close timeout', .2);
});
io.sockets.on('connection', function (socket) {
tobi = function () {
socket.send('a');
socket.send('b');
socket.send('c');
};
socket.on('disconnect', function () {
messages.should.be.true;
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
tobi();
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
msgs.should.have.length(3);
msgs[0].should.eql({ type: 'message', endpoint: '', data: 'a' });
msgs[1].should.eql({ type: 'message', endpoint: '', data: 'b' });
msgs[2].should.eql({ type: 'message', endpoint: '', data: 'c' });
messages = true;
});
})
});
},
'test connecting to a specific endpoint': function (done) {
var cl = client(++ports)
, io = create(cl)
, connectMessage = false
, sid;
io.configure(function () {
io.set('polling duration', 0);
io.set('close timeout', .05);
});
io.of('/woot').on('connection', function (socket) {
connectMessage.should.be.true;
socket.on('disconnect', function () {
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid);
connectMessage = true;
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/woot' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
});
});
},
'test that connecting doesnt connect to defined endpoints': function (done) {
var cl = client(++ports)
, io = create(cl)
, tobiConnected = false
, mainConnected = false
, sid;
io.configure(function () {
io.set('polling duration', .05);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
mainConnected = true;
socket.on('disconnect', function () {
tobiConnected.should.be.false;
cl.end();
io.server.close();
done();
});
});
io.of('/tobi').on('connection', function () {
tobiConnected = true;
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid);
});
},
'test disconnecting a specific endpoint': function (done) {
var cl = client(++ports)
, io = create(cl)
, wootDisconnected = false
, mainDisconnected = false
, checked = false;
io.configure(function () {
io.set('polling duration', 0);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
socket.on('message', function (data) {
data.should.eql('ferret');
mainDisconnected.should.be.false;
wootDisconnected.should.be.true;
checked = true;
});
socket.on('disconnect', function () {
mainDisconnected = true;
checked.should.be.true;
cl.end();
io.server.close();
done();
});
});
io.of('/woot').on('connection', function (socket) {
socket.on('disconnect', function () {
wootDisconnected = true;
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function () {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/woot' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'disconnect', endpoint: '/woot' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', data: 'ferret' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
}
);
}
);
});
});
},
'test that disconnecting disconnects all endpoints': function (done) {
var cl = client(++ports)
, io = create(cl)
, aDisconnected = false
, bDisconnected = false;
io.configure(function () {
io.set('polling duration', .2);
io.set('close timeout', .2);
});
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function () {
setTimeout(function () {
aDisconnected.should.be.true;
bDisconnected.should.be.true;
cl.end();
io.server.close();
done();
}, 50);
});
});
io.of('/a').on('connection', function (socket) {
socket.on('disconnect', function (msg) {
aDisconnected = true;
});
});
io.of('/b').on('connection', function (socket) {
socket.on('disconnect', function (msg) {
bDisconnected = true;
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
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' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/b' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
});
});
},
'test messaging a specific endpoint': function (done) {
var cl = client(++ports)
, io = create(cl)
, messaged = true
, aMessaged = false
, bMessaged = false;
io.configure(function () {
io.set('polling duration', 0);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
socket.on('message', function (msg) {
msg.should.eql('');
messaged = true;
});
socket.on('disconnect', function () {
messaged.should.be.true;
aMessaged.should.be.true;
bMessaged.should.be.true;
cl.end();
io.server.close();
done();
});
});
io.of('/a').on('connection', function (socket) {
socket.on('message', function (msg) {
msg.should.eql('a');
aMessaged = true;
});
});
io.of('/b').on('connection', function (socket) {
socket.on('message', function (msg) {
msg.should.eql('b');
bMessaged = true;
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', data: '' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/a' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', endpoint: '/a', data: 'a' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
}
);
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/b' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', endpoint: '/b', data: 'b' })
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
}
);
}
);
});
});
}
};

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff