mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-11 16:08:24 -05:00
Compare commits
657 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddb3445f3d | ||
|
|
6eb9807f17 | ||
|
|
7b0073c00f | ||
|
|
0313ad0ea3 | ||
|
|
d310d42472 | ||
|
|
709ceba00a | ||
|
|
b29312bc61 | ||
|
|
51901160df | ||
|
|
ab1b36e13f | ||
|
|
f7a2f35590 | ||
|
|
c348737fe6 | ||
|
|
dde3737165 | ||
|
|
8df4c7931b | ||
|
|
1dfacc6647 | ||
|
|
35a0fe0377 | ||
|
|
3b00312fa7 | ||
|
|
7b420295ef | ||
|
|
c504315982 | ||
|
|
520b5c37d5 | ||
|
|
09f446eca0 | ||
|
|
aecf3bf71c | ||
|
|
6c0a79e2b9 | ||
|
|
8683206e31 | ||
|
|
988d9d2346 | ||
|
|
b73d9bea4e | ||
|
|
d4fb6a5904 | ||
|
|
0517c609ba | ||
|
|
aa9438fee2 | ||
|
|
aee466cc71 | ||
|
|
65f75d7732 | ||
|
|
279c52a4c5 | ||
|
|
67d1a67b1c | ||
|
|
aae68d74b1 | ||
|
|
bf5897cedc | ||
|
|
a1a1c6657a | ||
|
|
2a4f7ae161 | ||
|
|
dba0576ca9 | ||
|
|
ba90f0991b | ||
|
|
9a54e9cace | ||
|
|
9df4cb64b8 | ||
|
|
c8f525868a | ||
|
|
a39d7c2ae4 | ||
|
|
4ba266d404 | ||
|
|
6b49779420 | ||
|
|
b1775b1cb8 | ||
|
|
ef52967301 | ||
|
|
fde38b1bd7 | ||
|
|
75da57abb2 | ||
|
|
21ea31a34e | ||
|
|
e2ebd4349b | ||
|
|
b771f74a22 | ||
|
|
8744007dc7 | ||
|
|
3658928ee7 | ||
|
|
210e688732 | ||
|
|
ae5420b727 | ||
|
|
d5b3bb46f8 | ||
|
|
575d35a371 | ||
|
|
e41483be9e | ||
|
|
c79e787671 | ||
|
|
a2f6bb99d6 | ||
|
|
3e4061bfef | ||
|
|
4c1d691fe5 | ||
|
|
514aeb4fcd | ||
|
|
876833198d | ||
|
|
4d36d6c01a | ||
|
|
43fa075ec4 | ||
|
|
91c7bde34b | ||
|
|
874a4a8535 | ||
|
|
93b571406e | ||
|
|
98af793f07 | ||
|
|
3f72dd3322 | ||
|
|
d0d38220fb | ||
|
|
f8aa157d5d | ||
|
|
0e13e9a268 | ||
|
|
916872587f | ||
|
|
a7072845ea | ||
|
|
425409945b | ||
|
|
5826ef0bb5 | ||
|
|
14e32a9bad | ||
|
|
1dde0f3947 | ||
|
|
342faf2197 | ||
|
|
12c6ee132d | ||
|
|
c9d909c993 | ||
|
|
125ab51338 | ||
|
|
b31ac18554 | ||
|
|
53cdd8f1fc | ||
|
|
81aea995ed | ||
|
|
1b2d902f33 | ||
|
|
9536437907 | ||
|
|
56fe26661d | ||
|
|
29974ac777 | ||
|
|
ef23c74bea | ||
|
|
49423d70bc | ||
|
|
715c7f99b4 | ||
|
|
f2ea965c6b | ||
|
|
a93d05a9f3 | ||
|
|
5ce06d3088 | ||
|
|
816bfec783 | ||
|
|
0a17c90d7a | ||
|
|
3645741b86 | ||
|
|
f2a7322b5a | ||
|
|
97c6568f65 | ||
|
|
8814825a35 | ||
|
|
58eaecad27 | ||
|
|
94157e650e | ||
|
|
0935b81da2 | ||
|
|
afa871bb8a | ||
|
|
1b01e16a6c | ||
|
|
bd6f638c8f | ||
|
|
83b36e54ac | ||
|
|
2f0d9d05af | ||
|
|
429eb0cb7c | ||
|
|
ac8e8598d7 | ||
|
|
9ba6d47ec7 | ||
|
|
3d49cafd03 | ||
|
|
77ca2dcbda | ||
|
|
7e4aa4fa64 | ||
|
|
b46e480f65 | ||
|
|
5e92dd8663 | ||
|
|
f981d3f050 | ||
|
|
f57505fee7 | ||
|
|
f8f1b132a1 | ||
|
|
f7f83bc09f | ||
|
|
b8ded0d725 | ||
|
|
086ccd2708 | ||
|
|
864857cb6f | ||
|
|
e5a7e422f9 | ||
|
|
f5b75151bd | ||
|
|
0523b655da | ||
|
|
d9415a38e4 | ||
|
|
ca82c09bf2 | ||
|
|
b9aaa1607c | ||
|
|
d1304c5d82 | ||
|
|
bd479a9cd6 | ||
|
|
a116d7765a | ||
|
|
1c6620d564 | ||
|
|
dba462e6da | ||
|
|
19c4422361 | ||
|
|
8242dd01ef | ||
|
|
17960ed038 | ||
|
|
d9996f0470 | ||
|
|
24d06d76dd | ||
|
|
4e4bbf918e | ||
|
|
b49f5c82f2 | ||
|
|
5bd67195de | ||
|
|
73fe547956 | ||
|
|
973e6cc982 | ||
|
|
4a0091b25a | ||
|
|
a1bf85b57f | ||
|
|
62ad812ee5 | ||
|
|
9ae8ed6929 | ||
|
|
4a11c16cc2 | ||
|
|
b88a758857 | ||
|
|
3352d1d726 | ||
|
|
e68557fab6 | ||
|
|
136fe960b7 | ||
|
|
da358084f0 | ||
|
|
1b4f6a5324 | ||
|
|
99346eddc5 | ||
|
|
051ffa0440 | ||
|
|
fa4fa3365a | ||
|
|
bafa184c6b | ||
|
|
e3149d5833 | ||
|
|
ae9d5277a9 | ||
|
|
2e440722a6 | ||
|
|
3fe6d4e8ec | ||
|
|
3b93c1c562 | ||
|
|
e89f82a9b0 | ||
|
|
1b90ae2587 | ||
|
|
9658e32e7a | ||
|
|
99d76664aa | ||
|
|
23eefa527a | ||
|
|
b49b35b26f | ||
|
|
16483375a7 | ||
|
|
ec305bd8df | ||
|
|
af07a1c649 | ||
|
|
5863903d44 | ||
|
|
f56d4acce6 | ||
|
|
8ea37b7351 | ||
|
|
b3cb18d910 | ||
|
|
0e41561d56 | ||
|
|
83709e9487 | ||
|
|
6f4051aaa7 | ||
|
|
87bf6910b5 | ||
|
|
8946b4ed4c | ||
|
|
a40068b5f3 | ||
|
|
9e29ec9b2b | ||
|
|
624e7cb14f | ||
|
|
b4954d767a | ||
|
|
f6eb53f5e1 | ||
|
|
9c80317574 | ||
|
|
d163d891ef | ||
|
|
54726105cb | ||
|
|
3d9e52cf93 | ||
|
|
ad74f2ff88 | ||
|
|
cf1c1273b3 | ||
|
|
1f43c4abb5 | ||
|
|
d6e6e8a2f8 | ||
|
|
fbd36b613d | ||
|
|
480b1a05bd | ||
|
|
ef729b72b4 | ||
|
|
63e197083b | ||
|
|
968e94e42b | ||
|
|
c18ed5fd4d | ||
|
|
1f7da938bd | ||
|
|
4b89bce182 | ||
|
|
8c19eef07a | ||
|
|
662b30669b | ||
|
|
001373ee17 | ||
|
|
246f3bb5c3 | ||
|
|
68d06ec94c | ||
|
|
a1feca1bd3 | ||
|
|
968105a239 | ||
|
|
2e8e26613a | ||
|
|
fbdb94d146 | ||
|
|
55572122f3 | ||
|
|
a1170a3aa6 | ||
|
|
0954301d7e | ||
|
|
328a9df8eb | ||
|
|
d99e30fca7 | ||
|
|
561dd6fd79 | ||
|
|
e9e2a91cea | ||
|
|
8a3a111a7f | ||
|
|
bd87e4dc35 | ||
|
|
71c253e992 | ||
|
|
a5cf4f57a0 | ||
|
|
a079cbc7f9 | ||
|
|
14a9fdb64f | ||
|
|
55fb100fc0 | ||
|
|
6159df3937 | ||
|
|
6f7bab5dfd | ||
|
|
7d2b44e176 | ||
|
|
0b5fdf995a | ||
|
|
a66bea5b33 | ||
|
|
a1a88aaaaf | ||
|
|
3f817c3a18 | ||
|
|
99bbd74d14 | ||
|
|
398511ddee | ||
|
|
fdf7937815 | ||
|
|
475909e642 | ||
|
|
95acec8b94 | ||
|
|
5812ddf2b0 | ||
|
|
1bbc3951bd | ||
|
|
039eed2c2a | ||
|
|
6de022b180 | ||
|
|
a06331d29f | ||
|
|
e639685370 | ||
|
|
1f2e681ce2 | ||
|
|
397944fcbe | ||
|
|
27dada65b9 | ||
|
|
99f1ac9aae | ||
|
|
ee8d0fbae0 | ||
|
|
8e08a6d419 | ||
|
|
318ae87f50 | ||
|
|
2c1b61148d | ||
|
|
507054c0a8 | ||
|
|
dfceb006d7 | ||
|
|
4d83456f74 | ||
|
|
201aad83b1 | ||
|
|
72eb61d01f | ||
|
|
163a2a696e | ||
|
|
199a479ebc | ||
|
|
d383ff9662 | ||
|
|
416b550189 | ||
|
|
536b338a01 | ||
|
|
a06ae10895 | ||
|
|
35a85084b1 | ||
|
|
0c2b44a846 | ||
|
|
0f6ee2dbd9 | ||
|
|
68ac0d6d24 | ||
|
|
00ef9ca652 | ||
|
|
2febbce87d | ||
|
|
31c45862cb | ||
|
|
809e2bc3f8 | ||
|
|
10adcb089e | ||
|
|
5bc75ea944 | ||
|
|
cd4f6ad5e9 | ||
|
|
54e6c79014 | ||
|
|
237b234cab | ||
|
|
547440605d | ||
|
|
d763c8b6ea | ||
|
|
5d210ac46c | ||
|
|
ea89e520bc | ||
|
|
c8bf064b10 | ||
|
|
08fe191211 | ||
|
|
0842729c1f | ||
|
|
9720c76d10 | ||
|
|
0f127d2b7d | ||
|
|
e87e9e18e1 | ||
|
|
46351d38b8 | ||
|
|
ce68fb3105 | ||
|
|
94905500e4 | ||
|
|
f830899e39 | ||
|
|
3444f017bc | ||
|
|
0588dc0fc4 | ||
|
|
57425ab1dd | ||
|
|
463d7a16a1 | ||
|
|
5d74847123 | ||
|
|
f8b4d87331 | ||
|
|
e9928b2b1d | ||
|
|
d33553a4bd | ||
|
|
45a98970d7 | ||
|
|
0b4e4f6a67 | ||
|
|
656047536a | ||
|
|
97b7ab4fe2 | ||
|
|
6a435ce677 | ||
|
|
6aa553b211 | ||
|
|
024af23b2c | ||
|
|
b50d9f8f5d | ||
|
|
874c194210 | ||
|
|
f537560bcc | ||
|
|
7197d6a29d | ||
|
|
8dba96511d | ||
|
|
f8d1d33dfa | ||
|
|
736106a9e7 | ||
|
|
f5a8ac293a | ||
|
|
afc83ad65a | ||
|
|
b5f90923a2 | ||
|
|
c574f5f959 | ||
|
|
ba339cc460 | ||
|
|
aee4dd9a6d | ||
|
|
ad86129437 | ||
|
|
c8eda77da3 | ||
|
|
a4fabba781 | ||
|
|
c19d2692e2 | ||
|
|
36ad8e87eb | ||
|
|
7d4aa670ad | ||
|
|
620f94a007 | ||
|
|
f808c377b0 | ||
|
|
41c5b37ac7 | ||
|
|
c972153669 | ||
|
|
4823d20cf1 | ||
|
|
44a79f9cee | ||
|
|
81e8a0c3a5 | ||
|
|
adffabeee3 | ||
|
|
79dd06a767 | ||
|
|
93df3c47cc | ||
|
|
2a3a5a01a2 | ||
|
|
dd8745ece2 | ||
|
|
4615e932c1 | ||
|
|
7e96f0a1ff | ||
|
|
8df80ee91c | ||
|
|
612d35194f | ||
|
|
a5ff1d854e | ||
|
|
0c8ed9547c | ||
|
|
8104694fdf | ||
|
|
6be582db3d | ||
|
|
12733a8759 | ||
|
|
5e28161322 | ||
|
|
c2bf0ea700 | ||
|
|
571f3a663c | ||
|
|
d293e3be7c | ||
|
|
e2a5e4b1f2 | ||
|
|
7b4c8c67c2 | ||
|
|
e280e17094 | ||
|
|
947d7b4ea5 | ||
|
|
318d62e5ad | ||
|
|
fc34c7d7a4 | ||
|
|
449c28ce1c | ||
|
|
3faca287a5 | ||
|
|
266278d14c | ||
|
|
d286ba2064 | ||
|
|
d4ec1b6ff0 | ||
|
|
49f11f9cf5 | ||
|
|
05fd23ddda | ||
|
|
d21b1f36a3 | ||
|
|
573b32cc7d | ||
|
|
16d1afc7fc | ||
|
|
675830e726 | ||
|
|
b1c971507b | ||
|
|
8c3ca99e7c | ||
|
|
beab053123 | ||
|
|
745ee03102 | ||
|
|
69de792c03 | ||
|
|
d037d6cb38 | ||
|
|
e06ea11c27 | ||
|
|
daf8df8f5c | ||
|
|
ff2476c7a5 | ||
|
|
fc77c0faca | ||
|
|
e0a85a90aa | ||
|
|
3aa3213b13 | ||
|
|
575ff2ecfc | ||
|
|
852a9d34df | ||
|
|
8a3781499d | ||
|
|
ab60d166ac | ||
|
|
f7838635a7 | ||
|
|
cfb2a26d4f | ||
|
|
09cfb17731 | ||
|
|
4cd4381b80 | ||
|
|
d65ec8d649 | ||
|
|
e16ac61b82 | ||
|
|
d17f5ce914 | ||
|
|
1982afa481 | ||
|
|
1860aa41ee | ||
|
|
598a140ad4 | ||
|
|
b8f6d4054c | ||
|
|
450d408ed6 | ||
|
|
0023aed4c3 | ||
|
|
f99cdc9339 | ||
|
|
999b8bc387 | ||
|
|
95d19e285c | ||
|
|
9f8b1a2e1e | ||
|
|
0c5e3eb5cf | ||
|
|
eca3316620 | ||
|
|
29df46ae07 | ||
|
|
980b6a7c40 | ||
|
|
c0393fce20 | ||
|
|
093f5f7186 | ||
|
|
1f0f209d5e | ||
|
|
fc61a37a6d | ||
|
|
bf8761fb9b | ||
|
|
3dcba2859c | ||
|
|
c73e01485d | ||
|
|
a3e1b0658f | ||
|
|
3cf6a0949b | ||
|
|
a98b8abe97 | ||
|
|
db429864f6 | ||
|
|
7d5612eed7 | ||
|
|
3efcd52674 | ||
|
|
8c0b60791c | ||
|
|
32fe383e70 | ||
|
|
6305ebe8aa | ||
|
|
2b1a5afe6c | ||
|
|
84bf735e4d | ||
|
|
142c73c9a1 | ||
|
|
07cf9395a0 | ||
|
|
7ed789d381 | ||
|
|
48936231ea | ||
|
|
2b5183b369 | ||
|
|
14c71c2e72 | ||
|
|
03db6a62e7 | ||
|
|
83208b6e11 | ||
|
|
036faa7d89 | ||
|
|
7b2a71ac4f | ||
|
|
da844159d9 | ||
|
|
baea55009b | ||
|
|
198efb0957 | ||
|
|
f0e88265d6 | ||
|
|
760e1b5cda | ||
|
|
ecca4ee738 | ||
|
|
d31aafa5fd | ||
|
|
c545e3b963 | ||
|
|
af66e5df4e | ||
|
|
a86994f693 | ||
|
|
430670f136 | ||
|
|
f65966d858 | ||
|
|
2f3f54fb38 | ||
|
|
f0cc5aa834 | ||
|
|
340281152f | ||
|
|
5f247ca38e | ||
|
|
cbabf0719e | ||
|
|
6a11d09590 | ||
|
|
64f6b244b6 | ||
|
|
9ad1c2f3ac | ||
|
|
242d02dee5 | ||
|
|
3bd5455b1a | ||
|
|
7cc56a51a8 | ||
|
|
0d5200ed69 | ||
|
|
abf798e2bb | ||
|
|
2102eb4bb3 | ||
|
|
cef1583a64 | ||
|
|
5f843feb90 | ||
|
|
01a06b2b62 | ||
|
|
46d069b3cc | ||
|
|
708af3f803 | ||
|
|
0881dfbdce | ||
|
|
c54011b6a1 | ||
|
|
f605b365b4 | ||
|
|
1ec766799f | ||
|
|
67f7d5749b | ||
|
|
95d8e7531c | ||
|
|
2e71357221 | ||
|
|
ef96e3b07f | ||
|
|
6046c385b9 | ||
|
|
e8d11924aa | ||
|
|
eb0213882f | ||
|
|
de8e746959 | ||
|
|
dc173330e5 | ||
|
|
d65635d212 | ||
|
|
dd0fd539e6 | ||
|
|
5d4f90de47 | ||
|
|
59c8c34ceb | ||
|
|
bcfec6df76 | ||
|
|
dbba592dfa | ||
|
|
d411dec5a4 | ||
|
|
ab5a226f3e | ||
|
|
fb857bd8b4 | ||
|
|
2e5fdb65bd | ||
|
|
b4f12ea2f9 | ||
|
|
902373196e | ||
|
|
ae516a3f06 | ||
|
|
ecf937c775 | ||
|
|
d1def44858 | ||
|
|
381bc4260a | ||
|
|
8a5cb03fcb | ||
|
|
22c91d8e49 | ||
|
|
bd37a9a34b | ||
|
|
2fa98fb61a | ||
|
|
6b3cdd7c9c | ||
|
|
3038ce5526 | ||
|
|
a0020bcb2c | ||
|
|
c929f8c7fb | ||
|
|
1d8a747773 | ||
|
|
56ed3fbe75 | ||
|
|
169046e026 | ||
|
|
56221bc093 | ||
|
|
5da04cd1a9 | ||
|
|
342845d5c2 | ||
|
|
97b63b0860 | ||
|
|
12d1dca7ca | ||
|
|
3a259d420c | ||
|
|
19837a0f86 | ||
|
|
3feb98a346 | ||
|
|
a8ea0f4fb0 | ||
|
|
1609eaa4e5 | ||
|
|
e724e7bdf6 | ||
|
|
06e7df4eb2 | ||
|
|
28a19bfb43 | ||
|
|
deefa7bbb9 | ||
|
|
bfd019c2f3 | ||
|
|
755cead37c | ||
|
|
01e73669f9 | ||
|
|
5022b06242 | ||
|
|
3afe5e3032 | ||
|
|
4315732220 | ||
|
|
fcd969b1be | ||
|
|
c67d2d5494 | ||
|
|
4f0fce34f5 | ||
|
|
31adf5e45e | ||
|
|
3063241eba | ||
|
|
3b65a9f011 | ||
|
|
f1e2af3d91 | ||
|
|
70ce641d85 | ||
|
|
c87a530b08 | ||
|
|
fc65ed630a | ||
|
|
98c4bb6955 | ||
|
|
6d0b1a5117 | ||
|
|
cb806c0d4e | ||
|
|
eb029808f5 | ||
|
|
2615aa45e7 | ||
|
|
f6370c1635 | ||
|
|
4ecbf2a46e | ||
|
|
c3d5024496 | ||
|
|
9c2224cca0 | ||
|
|
c695065c7a | ||
|
|
44da490467 | ||
|
|
8901efd085 | ||
|
|
1e68a83bd3 | ||
|
|
4ea334899f | ||
|
|
5f397464a9 | ||
|
|
373c66f1ee | ||
|
|
bec38c2ce1 | ||
|
|
b6a4899689 | ||
|
|
3af1ad982c | ||
|
|
8daa71302d | ||
|
|
b2a8ed1421 | ||
|
|
0d3313f536 | ||
|
|
fcd4f6acfc | ||
|
|
80036cc50f | ||
|
|
373eadf7e1 | ||
|
|
ef54570313 | ||
|
|
6256f569b3 | ||
|
|
cd4dee7257 | ||
|
|
3b7224c7e0 | ||
|
|
2030cf1432 | ||
|
|
7537e2543f | ||
|
|
e00913aae0 | ||
|
|
dbabf20faa | ||
|
|
4de65523d9 | ||
|
|
7cca186bb1 | ||
|
|
ec07fa8308 | ||
|
|
f5c32c1490 | ||
|
|
c3ba8a722b | ||
|
|
70b328f844 | ||
|
|
32a44a3b5d | ||
|
|
c23ddb2bff | ||
|
|
f7f219d6dd | ||
|
|
f7d40d5f7b | ||
|
|
da91c89147 | ||
|
|
a10e963858 | ||
|
|
21a3733dec | ||
|
|
4a6890898a | ||
|
|
ab2a0b6a8b | ||
|
|
c797d2a9fb | ||
|
|
d558c97089 | ||
|
|
b483dfcce9 | ||
|
|
f59e8aceaa | ||
|
|
de9e8dffe1 | ||
|
|
3a3044ebba | ||
|
|
d10b4dd1bd | ||
|
|
12beee2d63 | ||
|
|
875f14d16b | ||
|
|
8ca8990a0c | ||
|
|
c218468f67 | ||
|
|
4164e3bd7e | ||
|
|
46227e7ac9 | ||
|
|
d723d363b2 | ||
|
|
fa1c1b2ada | ||
|
|
d32a848c3f | ||
|
|
48ad0d3d1d | ||
|
|
281a467960 | ||
|
|
1fa74a46a3 | ||
|
|
ca4e3f32a3 | ||
|
|
9dd8134e6a | ||
|
|
180f1c91b9 | ||
|
|
bddf652c25 | ||
|
|
c795e4cf1a | ||
|
|
6afbb34581 | ||
|
|
ac39dbc721 | ||
|
|
a5c5c20438 | ||
|
|
f48b40e134 | ||
|
|
1679fd564c | ||
|
|
bb900d445a | ||
|
|
c6fed55f53 | ||
|
|
6adebc85fc | ||
|
|
7a087bcc94 | ||
|
|
18422183c8 | ||
|
|
aeb904f58b | ||
|
|
9c0b9de7f0 | ||
|
|
81552c11ca | ||
|
|
e1fe76aebe | ||
|
|
8197a0c854 | ||
|
|
2b91f1407f | ||
|
|
3b9715e8e7 | ||
|
|
4e13cfb03e | ||
|
|
39671e81a5 | ||
|
|
ffa8994a23 | ||
|
|
de1afe1317 | ||
|
|
aaad106b90 | ||
|
|
f850ddccd0 | ||
|
|
8d269aae4c | ||
|
|
67b4eb9abd | ||
|
|
fe6dd87443 | ||
|
|
d9aeaa494f | ||
|
|
2024d45383 | ||
|
|
e1884859bc | ||
|
|
0242a2ddf3 | ||
|
|
dbe6d5f740 | ||
|
|
e98fc7bc86 | ||
|
|
9bbf17f31e | ||
|
|
1a5a87af13 | ||
|
|
a4e53a642b | ||
|
|
6f36d8c2ff | ||
|
|
09fb16b443 | ||
|
|
330407cc9d | ||
|
|
2075307f23 | ||
|
|
d7b06edaca | ||
|
|
46fdcf00b3 | ||
|
|
147b9bb941 | ||
|
|
02a3da487c | ||
|
|
087c686ad0 | ||
|
|
16205fc522 | ||
|
|
a232159ce8 | ||
|
|
b9c3255b7c | ||
|
|
d80010dcf0 | ||
|
|
00694a8a98 | ||
|
|
da95094998 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ lib-cov
|
||||
*.pid
|
||||
benchmarks/*.png
|
||||
node_modules
|
||||
coverage
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
support
|
||||
test
|
||||
examples
|
||||
.gitignore
|
||||
|
||||
16
.travis.yml
16
.travis.yml
@@ -1,6 +1,20 @@
|
||||
sudo: false
|
||||
before_install:
|
||||
- npm install -g npm@'>=1.4.3'
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.6
|
||||
- "0.8"
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
- "4"
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
#matrix:
|
||||
#fast_finish: true
|
||||
#allow_failures:
|
||||
#- node_js: "0.11"
|
||||
|
||||
notifications:
|
||||
irc: "irc.freenode.org#socket.io"
|
||||
|
||||
295
History.md
295
History.md
@@ -1,4 +1,299 @@
|
||||
|
||||
1.4.0 / 2015-11-28
|
||||
==================
|
||||
|
||||
* socket.io: increase large binary data test timeout
|
||||
* package: bump `engine.io` for release
|
||||
* trigger callback even when joining an already joined room
|
||||
* package: bump parser
|
||||
* namespace: clear rooms flag after a clients call (fixes #1978)
|
||||
* package: bump `socket.io-parser`
|
||||
* fixed tests with large data
|
||||
* fixed a typo in the example code
|
||||
* package: bump mocha
|
||||
* package: bump `has-binary` and `zuul-ngrok`
|
||||
* package: bump `engine.io` and `socket.io-client`
|
||||
* README: clarified documentation of Socket.in
|
||||
* README: fixed up legacy repo links
|
||||
* test: better timeout for stress test
|
||||
* socket: don't set request property which has a getter
|
||||
* removed proxy index file
|
||||
* support flags on namespace
|
||||
* improve Socket#packet and Client#packet
|
||||
* socket: warn node_redis-style about missing `error`
|
||||
* test: added failing test
|
||||
* test: increase timeout for large binary data test
|
||||
* package: bump `has-binary` to work with all objects (fixes #1955)
|
||||
* fix origin verification default https port [evanlucas]
|
||||
* support compression [nkzawa]
|
||||
|
||||
1.3.7 / 2015-09-21
|
||||
==================
|
||||
|
||||
* package: bump `socket.io-client` for node4 compatibility
|
||||
* package: bump `engine.io` for node4 compatibility
|
||||
|
||||
1.3.6 / 2015-07-14
|
||||
==================
|
||||
|
||||
* package: bump `engine.io` to fix build on windows
|
||||
|
||||
1.3.5 / 2015-03-03
|
||||
==================
|
||||
|
||||
* package: bump `socket.io-parser`
|
||||
|
||||
1.3.4 / 2015-02-14
|
||||
==================
|
||||
|
||||
* package: bump `socket.io-client`
|
||||
|
||||
1.3.3 / 2015-02-03
|
||||
==================
|
||||
|
||||
* socket: warn node_redis-style about missing `error`
|
||||
* package: bump parser to better handle bad binary packets
|
||||
|
||||
1.3.2 / 2015-01-19
|
||||
==================
|
||||
|
||||
* no change on this release
|
||||
|
||||
1.3.1 / 2015-01-19
|
||||
==================
|
||||
|
||||
* no change on this release
|
||||
* package: bump `engine.io`
|
||||
|
||||
1.3.0 / 2015-01-19
|
||||
==================
|
||||
|
||||
* package: bump `engine.io`
|
||||
* add test for reconnection after server restarts [rase-]
|
||||
* update license with up-to-date year range [fay-jai]
|
||||
* fix leaving unknown rooms [defunctzombie]
|
||||
* allow null origins when allowed origins is a function [drewblaisdell]
|
||||
* fix tests on node 0.11
|
||||
* package: fix `npm test` to run on windows
|
||||
* package: bump `debug` v2.1.0 [coderaiser]
|
||||
* added tests for volatile [rase-]
|
||||
|
||||
1.2.1 / 2014-11-21
|
||||
==================
|
||||
|
||||
* fix protocol violations and improve error handling (GH-1880)
|
||||
* package: bump `engine.io` for websocket leak fix [3rd-Eden]
|
||||
* style tweaks
|
||||
|
||||
1.2.0 / 2014-10-27
|
||||
==================
|
||||
|
||||
* package: bump `engine.io`
|
||||
* downloads badge
|
||||
* add test to check that empty rooms are autopruned
|
||||
* added Server#origins(v:Function) description for dynamic CORS
|
||||
* added test coverage for Server#origins(function) for dynamic CORS
|
||||
* added optional Server#origins(function) for dynamic CORS
|
||||
* fix usage example for Server#close
|
||||
* package: fix main file for example application 'chat'
|
||||
* package: bump `socket.io-parser`
|
||||
* update README http ctor to createServer()
|
||||
* bump adapter with a lot of fixes for room bookkeeping
|
||||
|
||||
1.1.0 / 2014-09-04
|
||||
==================
|
||||
|
||||
* examples: minor fix of escaping
|
||||
* testing for equivalence of namespaces starting with / or without
|
||||
* update index.js
|
||||
* added relevant tests
|
||||
* take "" and "/" as equivalent namespaces on server
|
||||
* use svg instead of png to get better image quality in readme
|
||||
* make CI build faster
|
||||
* fix splice arguments and `socket.rooms` value update in `socket.leaveAll`.
|
||||
* client cannot connect to non-existing namespaces
|
||||
* bump engine.io version to get the cached IP address
|
||||
* fixed handshake object address property and made the test case more strict.
|
||||
* package: bump `engine.io`
|
||||
* fixed the failing test where server crashes on disconnect involving connectBuffer
|
||||
* npmignore: ignore `.gitignore` (fixes #1607)
|
||||
* test: added failing case for `socket.disconnect` and nsps
|
||||
* fix repo in package.json
|
||||
* improve Close documentation
|
||||
* use ephemeral ports
|
||||
* fix: We should use the standard http protocol to handler the etag header.
|
||||
* override default browser font-family for inputs
|
||||
* update has-binary-data to 1.0.3
|
||||
* add close specs
|
||||
* add ability to stop the http server even if not created inside socket.io
|
||||
* make sure server gets close
|
||||
* Add test case for checking that reconnect_failed is fired only once upon failure
|
||||
* package: bump `socket.io-parser` for `component-emitter` dep fix
|
||||
|
||||
1.0.6 / 2014-06-19
|
||||
==================
|
||||
|
||||
* package: bump `socket.io-client`
|
||||
|
||||
1.0.5 / 2014-06-16
|
||||
==================
|
||||
|
||||
* package: bump `engine.io` to fix jsonp `\n` bug and CORS warnings
|
||||
* index: fix typo [yanatan16]
|
||||
* add `removeListener` to blacklisted events
|
||||
* examples: clearer instructions to install chat example
|
||||
* index: fix namespace `connectBuffer` issue
|
||||
|
||||
1.0.4 / 2014-06-02
|
||||
==================
|
||||
|
||||
* package: bump socket.io-client
|
||||
|
||||
1.0.3 / 2014-05-31
|
||||
==================
|
||||
|
||||
* package: bump `socket.io-client`
|
||||
* package: bump `socket.io-parser` for binary ACK fix
|
||||
* package: bump `engine.io` for binary UTF8 fix
|
||||
* example: fix XSS in chat example
|
||||
|
||||
1.0.2 / 2014-05-28
|
||||
==================
|
||||
|
||||
* package: bump `socket.io-parser` for windows fix
|
||||
|
||||
1.0.1 / 2014-05-28
|
||||
==================
|
||||
|
||||
* bump due to bad npm tag
|
||||
|
||||
1.0.0 / 2014-05-28
|
||||
==================
|
||||
|
||||
* stable release
|
||||
|
||||
1.0.0-pre5 / 2014-05-22
|
||||
=======================
|
||||
|
||||
* package: bump `socket.io-client` for parser fixes
|
||||
* package: bump `engine.io`
|
||||
|
||||
1.0.0-pre4 / 2014-05-19
|
||||
=======================
|
||||
|
||||
* package: bump client
|
||||
|
||||
1.0.0-pre3 / 2014-05-17
|
||||
=======================
|
||||
|
||||
* package: bump parser
|
||||
* package: bump engine.io
|
||||
|
||||
1.0.0-pre2 / 2014-04-27
|
||||
=======================
|
||||
|
||||
* package: bump `engine.io`
|
||||
* added backwards compatible of engine.io maxHttpBufferSize
|
||||
* added test that server and client using same protocol
|
||||
* added support for setting allowed origins
|
||||
* added information about logging
|
||||
* the set function in server can be used to set some attributes for BC
|
||||
* fix error in callback call 'done' instead of 'next' in docs
|
||||
* package: bump `socket.io-parser`
|
||||
* package: bump `expect.js`
|
||||
* added some new tests, including binary with acks
|
||||
|
||||
1.0.0-pre / 2014-03-14
|
||||
======================
|
||||
|
||||
* implemented `engine.io`
|
||||
* implemented `socket.io-adapter`
|
||||
* implemented `socket.io-protocol`
|
||||
* implemented `debug` and improved instrumentation
|
||||
* added binary support
|
||||
* added new `require('io')(srv)` signature
|
||||
* simplified `socket.io-client` serving
|
||||
|
||||
0.9.14 / 2013-03-29
|
||||
===================
|
||||
|
||||
* manager: fix memory leak with SSL [jpallen]
|
||||
|
||||
0.9.13 / 2012-12-13
|
||||
===================
|
||||
|
||||
* package: fixed `base64id` requirement
|
||||
|
||||
0.9.12 / 2012-12-13
|
||||
===================
|
||||
|
||||
* manager: fix for latest node which is returning a clone with `listeners` [viirya]
|
||||
|
||||
0.9.11 / 2012-11-02
|
||||
===================
|
||||
|
||||
* package: move redis to optionalDependenices [3rd-Eden]
|
||||
* bumped client
|
||||
|
||||
0.9.10 / 2012-08-10
|
||||
===================
|
||||
|
||||
* Don't lowercase log messages
|
||||
* Always set the HTTP response in case an error should be returned to the client
|
||||
* Create or destroy the flash policy server on configuration change
|
||||
* Honour configuration to disable flash policy server
|
||||
* Add express 3.0 instructions on Readme.md
|
||||
* Bump client
|
||||
|
||||
0.9.9 / 2012-08-01
|
||||
==================
|
||||
|
||||
* Fixed sync disconnect xhrs handling
|
||||
* Put license text in its own file (#965)
|
||||
* Add warning to .listen() to ease the migration to Express 3.x
|
||||
* Restored compatibility with node 0.4.x
|
||||
|
||||
0.9.8 / 2012-07-24
|
||||
==================
|
||||
|
||||
* Bumped client.
|
||||
|
||||
0.9.7 / 2012-07-24
|
||||
==================
|
||||
|
||||
* Prevent crash when socket leaves a room twice.
|
||||
* Corrects unsafe usage of for..in
|
||||
* Fix for node 0.8 with `gzip compression` [vadimi]
|
||||
* Update redis to support Node 0.8.x
|
||||
* Made ID generation securely random
|
||||
* Fix Redis Store race condition in manager onOpen unsubscribe callback
|
||||
* Fix for EventEmitters always reusing the same Array instance for listeners
|
||||
|
||||
0.9.6 / 2012-04-17
|
||||
==================
|
||||
|
||||
* Fixed XSS in jsonp-polling.
|
||||
|
||||
0.9.5 / 2012-04-05
|
||||
==================
|
||||
|
||||
* Added test for polling and socket close.
|
||||
* Ensure close upon request close.
|
||||
* Fix disconnection reason being lost for polling transports.
|
||||
* Ensure that polling transports work with Connection: close.
|
||||
* Log disconnection reason.
|
||||
|
||||
0.9.4 / 2012-04-01
|
||||
==================
|
||||
|
||||
* Disconnecting from namespace improvement (#795) [DanielBaulig]
|
||||
* Bumped client with polling reconnection loop (#438)
|
||||
|
||||
0.9.3 / 2012-03-28
|
||||
==================
|
||||
|
||||
* Fix "Syntax error" on FF Web Console with XHR Polling [mikito]
|
||||
|
||||
0.9.2 / 2012-03-13
|
||||
==================
|
||||
|
||||
|
||||
22
LICENSE
Normal file
22
LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014-2015 Automattic <dev@cloudup.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
34
Makefile
34
Makefile
@@ -1,31 +1,15 @@
|
||||
|
||||
ALL_TESTS = $(shell find test/ -name '*.test.js')
|
||||
ALL_BENCH = $(shell find benchmarks -name '*.bench.js')
|
||||
|
||||
run-tests:
|
||||
@./node_modules/.bin/expresso \
|
||||
-t 3000 \
|
||||
-I support \
|
||||
--serial \
|
||||
$(TESTFLAGS) \
|
||||
$(TESTS)
|
||||
REPORTER = dot
|
||||
|
||||
test:
|
||||
@$(MAKE) NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests
|
||||
@./node_modules/.bin/mocha \
|
||||
--reporter $(REPORTER) \
|
||||
--slow 200ms \
|
||||
--bail
|
||||
|
||||
test-cov:
|
||||
@TESTFLAGS=--cov $(MAKE) test
|
||||
@./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- \
|
||||
--reporter $(REPORTER) \
|
||||
test/
|
||||
|
||||
test-leaks:
|
||||
@ls test/leaks/* | xargs node --expose_debug_as=debug --expose_gc
|
||||
|
||||
run-bench:
|
||||
@node $(PROFILEFLAGS) benchmarks/runner.js
|
||||
|
||||
bench:
|
||||
@$(MAKE) BENCHMARKS="$(ALL_BENCH)" run-bench
|
||||
|
||||
profile:
|
||||
@PROFILEFLAGS='--prof --trace-opt --trace-bailout --trace-deopt' $(MAKE) bench
|
||||
|
||||
.PHONY: test bench profile
|
||||
.PHONY: test
|
||||
|
||||
657
Readme.md
657
Readme.md
@@ -1,343 +1,424 @@
|
||||
# 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
|
||||
|
||||
## How to Install
|
||||
|
||||
npm install socket.io
|
||||
[](https://travis-ci.org/socketio/socket.io)
|
||||

|
||||

|
||||
[](http://slack.socket.io)
|
||||
|
||||
## How to use
|
||||
|
||||
First, require `socket.io`:
|
||||
The following example attaches socket.io to a plain Node.JS
|
||||
HTTP server listening on port `3000`.
|
||||
|
||||
```js
|
||||
var io = require('socket.io');
|
||||
```
|
||||
|
||||
Next, attach it to a HTTP/HTTPS server. If you're using the fantastic `express`
|
||||
web framework:
|
||||
|
||||
```js
|
||||
var app = express.createServer()
|
||||
, io = io.listen(app);
|
||||
|
||||
app.listen(80);
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.emit('news', { hello: 'world' });
|
||||
socket.on('my other event', function (data) {
|
||||
console.log(data);
|
||||
});
|
||||
var server = require('http').createServer();
|
||||
var io = require('socket.io')(server);
|
||||
io.on('connection', function(socket){
|
||||
socket.on('event', function(data){});
|
||||
socket.on('disconnect', function(){});
|
||||
});
|
||||
server.listen(3000);
|
||||
```
|
||||
|
||||
Finally, load it from the client side code:
|
||||
|
||||
```html
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
var socket = io.connect('http://localhost');
|
||||
socket.on('news', function (data) {
|
||||
console.log(data);
|
||||
socket.emit('my other event', { my: 'data' });
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
For more thorough examples, look at the `examples/` directory.
|
||||
|
||||
## Short recipes
|
||||
|
||||
### Sending and receiving events.
|
||||
|
||||
Socket.IO allows you to emit and receive custom events.
|
||||
Besides `connect`, `message` and `disconnect`, you can emit custom events:
|
||||
### Standalone
|
||||
|
||||
```js
|
||||
// note, io.listen(<port>) will create a http server for you
|
||||
var io = require('socket.io').listen(80);
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
io.sockets.emit('this', { will: 'be received by everyone' });
|
||||
|
||||
socket.on('private message', function (from, msg) {
|
||||
console.log('I received a private message by ', from, ' saying ', msg);
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
io.sockets.emit('user disconnected');
|
||||
});
|
||||
});
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){});
|
||||
io.listen(3000);
|
||||
```
|
||||
|
||||
### Storing data associated to a client
|
||||
### In conjunction with Express
|
||||
|
||||
Sometimes it's necessary to store data associated with a client that's
|
||||
necessary for the duration of the session.
|
||||
|
||||
#### Server side
|
||||
Starting with **3.0**, express applications have become request handler
|
||||
functions that you pass to `http` or `http` `Server` instances. You need
|
||||
to pass the `Server` to `socket.io`, and not the express application
|
||||
function.
|
||||
|
||||
```js
|
||||
var io = require('socket.io').listen(80);
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.on('set nickname', function (name) {
|
||||
socket.set('nickname', name, function () { socket.emit('ready'); });
|
||||
});
|
||||
|
||||
socket.on('msg', function () {
|
||||
socket.get('nickname', function (err, name) {
|
||||
console.log('Chat message by ', name);
|
||||
});
|
||||
});
|
||||
});
|
||||
var app = require('express')();
|
||||
var server = require('http').createServer(app);
|
||||
var io = require('socket.io')(server);
|
||||
io.on('connection', function(){ /* … */ });
|
||||
server.listen(3000);
|
||||
```
|
||||
|
||||
#### Client side
|
||||
### In conjunction with Koa
|
||||
|
||||
```html
|
||||
<script>
|
||||
var socket = io.connect('http://localhost');
|
||||
|
||||
socket.on('connect', function () {
|
||||
socket.emit('set nickname', confirm('What is your nickname?'));
|
||||
socket.on('ready', function () {
|
||||
console.log('Connected !');
|
||||
socket.emit('msg', confirm('What is your message?'));
|
||||
});
|
||||
});
|
||||
</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
|
||||
Like Express.JS, Koa works by exposing an application as a request
|
||||
handler function, but only by calling the `callback` method.
|
||||
|
||||
```js
|
||||
var io = require('socket.io').listen(80);
|
||||
var app = require('koa')();
|
||||
var server = require('http').createServer(app.callback());
|
||||
var io = require('socket.io')(server);
|
||||
io.on('connection', function(){ /* … */ });
|
||||
server.listen(3000);
|
||||
```
|
||||
|
||||
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' });
|
||||
## API
|
||||
|
||||
### Server
|
||||
|
||||
Exposed by `require('socket.io')`.
|
||||
|
||||
### Server()
|
||||
|
||||
Creates a new `Server`. Works with and without `new`:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
// or
|
||||
var Server = require('socket.io');
|
||||
var io = new Server();
|
||||
```
|
||||
|
||||
### Server(opts:Object)
|
||||
|
||||
Optionally, the first or second argument (see below) of the `Server`
|
||||
constructor can be an options object.
|
||||
|
||||
The following options are supported:
|
||||
|
||||
- `serveClient` sets the value for Server#serveClient()
|
||||
- `path` sets the value for Server#path()
|
||||
|
||||
The same options passed to socket.io are always passed to
|
||||
the `engine.io` `Server` that gets created. See engine.io
|
||||
[options](https://github.com/socketio/engine.io#methods-1)
|
||||
as reference.
|
||||
|
||||
### Server(srv:http#Server, opts:Object)
|
||||
|
||||
Creates a new `Server` and attaches it to the given `srv`. Optionally
|
||||
`opts` can be passed.
|
||||
|
||||
### Server(port:Number, opts:Object)
|
||||
|
||||
Binds socket.io to a new `http.Server` that listens on `port`.
|
||||
|
||||
### Server#serveClient(v:Boolean):Server
|
||||
|
||||
If `v` is `true` the attached server (see `Server#attach`) will serve
|
||||
the client files. Defaults to `true`.
|
||||
|
||||
This method has no effect after `attach` is called.
|
||||
|
||||
```js
|
||||
// pass a server and the `serveClient` option
|
||||
var io = require('socket.io')(http, { serveClient: false });
|
||||
|
||||
// or pass no server and then you can call the method
|
||||
var io = require('socket.io')();
|
||||
io.serveClient(false);
|
||||
io.attach(http);
|
||||
```
|
||||
|
||||
If no arguments are supplied this method returns the current value.
|
||||
|
||||
### Server#path(v:String):Server
|
||||
|
||||
Sets the path `v` under which `engine.io` and the static files will be
|
||||
served. Defaults to `/socket.io`.
|
||||
|
||||
If no arguments are supplied this method returns the current value.
|
||||
|
||||
### Server#adapter(v:Adapter):Server
|
||||
|
||||
Sets the adapter `v`. Defaults to an instance of the `Adapter` that
|
||||
ships with socket.io which is memory based. See
|
||||
[socket.io-adapter](https://github.com/socketio/socket.io-adapter).
|
||||
|
||||
If no arguments are supplied this method returns the current value.
|
||||
|
||||
### Server#origins(v:String):Server
|
||||
|
||||
Sets the allowed origins `v`. Defaults to any origins being allowed.
|
||||
|
||||
If no arguments are supplied this method returns the current value.
|
||||
|
||||
### Server#origins(v:Function):Server
|
||||
|
||||
Sets the allowed origins as dynamic function. Function takes two arguments `origin:String` and `callback(error, success)`, where `success` is a boolean value indicating whether origin is allowed or not.
|
||||
|
||||
__Potential drawbacks__:
|
||||
* in some situations, when it is not possible to determine `origin` it may have value of `*`
|
||||
* As this function will be executed for every request, it is advised to make this function work as fast as possible
|
||||
* If `socket.io` is used together with `Express`, the CORS headers will be affected only for `socket.io` requests. For Express can use [cors](https://github.com/expressjs/cors)
|
||||
|
||||
|
||||
### Server#sockets:Namespace
|
||||
|
||||
The default (`/`) namespace.
|
||||
|
||||
### Server#attach(srv:http#Server, opts:Object):Server
|
||||
|
||||
Attaches the `Server` to an engine.io instance on `srv` with the
|
||||
supplied `opts` (optionally).
|
||||
|
||||
### Server#attach(port:Number, opts:Object):Server
|
||||
|
||||
Attaches the `Server` to an engine.io instance that is bound to `port`
|
||||
with the given `opts` (optionally).
|
||||
|
||||
### Server#listen
|
||||
|
||||
Synonym of `Server#attach`.
|
||||
|
||||
### Server#bind(srv:engine#Server):Server
|
||||
|
||||
Advanced use only. Binds the server to a specific engine.io `Server`
|
||||
(or compatible API) instance.
|
||||
|
||||
### Server#onconnection(socket:engine#Socket):Server
|
||||
|
||||
Advanced use only. Creates a new `socket.io` client from the incoming
|
||||
engine.io (or compatible API) `socket`.
|
||||
|
||||
### Server#of(nsp:String):Namespace
|
||||
|
||||
Initializes and retrieves the given `Namespace` by its pathname
|
||||
identifier `nsp`.
|
||||
|
||||
If the namespace was already initialized it returns it right away.
|
||||
|
||||
### Server#emit
|
||||
|
||||
Emits an event to all connected clients. The following two are
|
||||
equivalent:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.sockets.emit('an event sent to all connected clients');
|
||||
io.emit('an event sent to all connected clients');
|
||||
```
|
||||
|
||||
For other available methods, see `Namespace` below.
|
||||
|
||||
### Server#close
|
||||
|
||||
Closes socket server
|
||||
|
||||
```js
|
||||
var Server = require('socket.io');
|
||||
var PORT = 3030;
|
||||
var server = require('http').Server();
|
||||
|
||||
var io = Server(PORT);
|
||||
|
||||
io.close(); // Close current server
|
||||
|
||||
server.listen(PORT); // PORT is free to use
|
||||
|
||||
io = Server(server);
|
||||
```
|
||||
|
||||
### Server#use
|
||||
|
||||
See `Namespace#use` below.
|
||||
|
||||
### Namespace
|
||||
|
||||
Represents a pool of sockets connected under a given scope identified
|
||||
by a pathname (eg: `/chat`).
|
||||
|
||||
By default the client always connects to `/`.
|
||||
|
||||
#### Events
|
||||
|
||||
- `connection` / `connect`. Fired upon a connection.
|
||||
|
||||
Parameters:
|
||||
- `Socket` the incoming socket.
|
||||
|
||||
### Namespace#name:String
|
||||
|
||||
The namespace identifier property.
|
||||
|
||||
### Namespace#connected:Object<Socket>
|
||||
|
||||
Hash of `Socket` objects that are connected to this namespace indexed
|
||||
by `id`.
|
||||
|
||||
### Namespace#clients(fn:Function)
|
||||
|
||||
Gets a list of client IDs connected to this namespace (across all nodes if applicable).
|
||||
|
||||
An example to get all clients in a namespace:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.of('/chat').clients(function(error, clients){
|
||||
if (error) throw error;
|
||||
console.log(clients); // => [PZDoMHjiu8PYfRiKAAAF, Anw2LatarvGVVXEIAAAD]
|
||||
});
|
||||
```
|
||||
|
||||
var news = io
|
||||
.of('/news');
|
||||
.on('connection', function (socket) {
|
||||
socket.emit('item', { news: 'item' });
|
||||
An example to get all clients in namespace's room:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.of('/chat').in('general').clients(function(error, clients){
|
||||
if (error) throw error;
|
||||
console.log(clients); // => [Anw2LatarvGVVXEIAAAD]
|
||||
});
|
||||
```
|
||||
```
|
||||
|
||||
#### Client side:
|
||||
As with broadcasting, the default is all clients from the default namespace ('/'):
|
||||
|
||||
```html
|
||||
<script>
|
||||
var chat = io.connect('http://localhost/chat')
|
||||
, news = io.connect('http://localhost/news');
|
||||
|
||||
chat.on('connect', function () {
|
||||
chat.emit('hi!');
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.clients(function(error, clients){
|
||||
if (error) throw error;
|
||||
console.log(clients); // => [6em3d4TJP8Et9EMNAAAA, G5p55dHhGgUnLUctAAAB]
|
||||
});
|
||||
```
|
||||
|
||||
news.on('news', function () {
|
||||
news.emit('woot');
|
||||
### Namespace#use(fn:Function):Namespace
|
||||
|
||||
Registers a middleware, which is a function that gets executed for
|
||||
every incoming `Socket` and receives as parameter the socket and a
|
||||
function to optionally defer execution to the next registered
|
||||
middleware.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.use(function(socket, next){
|
||||
if (socket.request.headers.cookie) return next();
|
||||
next(new Error('Authentication error'));
|
||||
});
|
||||
</script>
|
||||
```
|
||||
```
|
||||
|
||||
### Sending volatile messages.
|
||||
Errors passed to middleware callbacks are sent as special `error`
|
||||
packets to clients.
|
||||
|
||||
Sometimes certain messages can be dropped. Let's say you have an app that
|
||||
shows realtime tweets for the keyword `bieber`.
|
||||
### Socket
|
||||
|
||||
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.
|
||||
A `Socket` is the fundamental class for interacting with browser
|
||||
clients. A `Socket` belongs to a certain `Namespace` (by default `/`)
|
||||
and uses an underlying `Client` to communicate.
|
||||
|
||||
In that case, you might want to send those messages as volatile messages.
|
||||
### Socket#rooms:Array
|
||||
|
||||
#### Server side
|
||||
A list of strings identifying the rooms this socket is in.
|
||||
|
||||
```js
|
||||
var io = require('socket.io').listen(80);
|
||||
### Socket#client:Client
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
var tweets = setInterval(function () {
|
||||
getBieberTweet(function (tweet) {
|
||||
socket.volatile.emit('bieber tweet', tweet);
|
||||
});
|
||||
}, 100);
|
||||
A reference to the underlying `Client` object.
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
clearInterval(tweets);
|
||||
### Socket#conn:Socket
|
||||
|
||||
A reference to the underlying `Client` transport connection (engine.io
|
||||
`Socket` object).
|
||||
|
||||
### Socket#request:Request
|
||||
|
||||
A getter proxy that returns the reference to the `request` that
|
||||
originated the underlying engine.io `Client`. Useful for accessing
|
||||
request headers such as `Cookie` or `User-Agent`.
|
||||
|
||||
### Socket#id:String
|
||||
|
||||
A unique identifier for the socket session, that comes from the
|
||||
underlying `Client`.
|
||||
|
||||
### Socket#emit(name:String[, …]):Socket
|
||||
|
||||
Emits an event to the socket identified by the string `name`. Any
|
||||
other parameters can be included.
|
||||
|
||||
All datastructures are supported, including `Buffer`. JavaScript
|
||||
functions can't be serialized/deserialized.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.emit('an event', { some: 'data' });
|
||||
});
|
||||
});
|
||||
```
|
||||
```
|
||||
|
||||
#### Client side
|
||||
### Socket#join(name:String[, fn:Function]):Socket
|
||||
|
||||
In the client side, messages are received the same way whether they're volatile
|
||||
or not.
|
||||
Adds the socket to the `room`, and fires optionally a callback `fn`
|
||||
with `err` signature (if any).
|
||||
|
||||
### Getting acknowledgements
|
||||
The socket is automatically a member of a room identified with its
|
||||
session id (see `Socket#id`).
|
||||
|
||||
Sometimes, you might want to get a callback when the client confirmed the message
|
||||
reception.
|
||||
The mechanics of joining rooms are handled by the `Adapter`
|
||||
that has been configured (see `Server#adapter` above), defaulting to
|
||||
[socket.io-adapter](https://github.com/socketio/socket.io-adapter).
|
||||
|
||||
To do this, simply pass a function as the last parameter of `.send` or `.emit`.
|
||||
What's more, when you use `.emit`, the acknowledgement is done by you, which
|
||||
means you can also pass data along:
|
||||
### Socket#leave(name:String[, fn:Function]):Socket
|
||||
|
||||
#### Server side
|
||||
Removes the socket from `room`, and fires optionally a callback `fn`
|
||||
with `err` signature (if any).
|
||||
|
||||
```js
|
||||
var io = require('socket.io').listen(80);
|
||||
**Rooms are left automatically upon disconnection**.
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.on('ferret', function (name, fn) {
|
||||
fn('woot');
|
||||
The mechanics of leaving rooms are handled by the `Adapter`
|
||||
that has been configured (see `Server#adapter` above), defaulting to
|
||||
[socket.io-adapter](https://github.com/socketio/socket.io-adapter).
|
||||
|
||||
### Socket#to(room:String):Socket
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event will
|
||||
only be _broadcasted_ to sockets that have joined the given `room`.
|
||||
|
||||
To emit to multiple rooms, you can call `to` several times.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.to('others').emit('an event', { some: 'data' });
|
||||
});
|
||||
});
|
||||
```
|
||||
```
|
||||
|
||||
#### Client side
|
||||
### Socket#in(room:String):Socket
|
||||
|
||||
```html
|
||||
<script>
|
||||
var socket = io.connect(); // TIP: .connect with no args does auto-discovery
|
||||
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'
|
||||
});
|
||||
Same as `Socket#to`
|
||||
|
||||
### Socket#compress(v:Boolean):Socket
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event data will
|
||||
only be _compressed_ if the value is `true`. Defaults to `true` when you don't call the method.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.compress(false).emit('an event', { some: 'data' });
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
The `Client` class represents an incoming transport (engine.io)
|
||||
connection. A `Client` can be associated with many multiplexed `Socket`
|
||||
that belong to different `Namespace`s.
|
||||
|
||||
### Client#conn
|
||||
|
||||
A reference to the underlying `engine.io` `Socket` connection.
|
||||
|
||||
### Client#request
|
||||
|
||||
A getter proxy that returns the reference to the `request` that
|
||||
originated the engine.io connection. Useful for accessing
|
||||
request headers such as `Cookie` or `User-Agent`.
|
||||
|
||||
## Debug / logging
|
||||
|
||||
Socket.IO is powered by [debug](https://github.com/visionmedia/debug).
|
||||
In order to see all the debug output, run your app with the environment variable
|
||||
`DEBUG` including the desired scope.
|
||||
|
||||
To see the output from all of Socket.IO's debugging scopes you can use:
|
||||
|
||||
```
|
||||
DEBUG=socket.io* node myapp
|
||||
```
|
||||
|
||||
### Broadcasting messages
|
||||
## License
|
||||
|
||||
To broadcast, simply add a `broadcast` flag to `emit` and `send` method calls.
|
||||
Broadcasting means sending a message to everyone else except for the socket
|
||||
that starts it.
|
||||
|
||||
#### Server side
|
||||
|
||||
```js
|
||||
var io = require('socket.io').listen(80);
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.broadcast.emit('user connected');
|
||||
socket.broadcast.json.send({ a: 'message' });
|
||||
});
|
||||
```
|
||||
|
||||
### Rooms
|
||||
|
||||
Sometimes you want to put certain sockets in the same room, so that it's easy
|
||||
to broadcast to all of them together.
|
||||
|
||||
Think of this as built-in channels for sockets. Sockets `join` and `leave`
|
||||
rooms in each socket.
|
||||
|
||||
#### Server side
|
||||
|
||||
```js
|
||||
var io = require('socket.io').listen(80);
|
||||
|
||||
io.sockets.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');
|
||||
});
|
||||
```
|
||||
|
||||
### Using it just as a cross-browser WebSocket
|
||||
|
||||
If you just want the WebSocket semantics, you can do that too.
|
||||
Simply leverage `send` and listen on the `message` event:
|
||||
|
||||
#### Server side
|
||||
|
||||
```js
|
||||
var io = require('socket.io').listen(80);
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.on('message', function () { });
|
||||
socket.on('disconnect', function () { });
|
||||
});
|
||||
```
|
||||
|
||||
#### Client side
|
||||
|
||||
```html
|
||||
<script>
|
||||
var socket = io.connect('http://localhost/');
|
||||
socket.on('connect', function () {
|
||||
socket.send('hi');
|
||||
|
||||
socket.on('message', function (msg) {
|
||||
// my msg
|
||||
});
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Changing configuration
|
||||
|
||||
Configuration in socket.io is TJ-style:
|
||||
|
||||
#### Server side
|
||||
|
||||
```js
|
||||
var io = require('socket.io').listen(80);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('transports', ['websocket', 'flashsocket', 'xhr-polling']);
|
||||
});
|
||||
|
||||
io.configure('development', function () {
|
||||
io.set('transports', ['websocket', 'xhr-polling']);
|
||||
io.enable('log');
|
||||
});
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2011 Guillermo Rauch <guillermo@learnboost.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
MIT
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var benchmark = require('benchmark')
|
||||
, colors = require('colors')
|
||||
, io = require('../')
|
||||
, parser = io.parser
|
||||
, suite = new benchmark.Suite('Decode packet');
|
||||
|
||||
suite.add('string', function () {
|
||||
parser.decodePacket('4:::"2"');
|
||||
});
|
||||
|
||||
suite.add('event', function () {
|
||||
parser.decodePacket('5:::{"name":"woot"}');
|
||||
});
|
||||
|
||||
suite.add('event+ack', function () {
|
||||
parser.decodePacket('5:1+::{"name":"tobi"}');
|
||||
});
|
||||
|
||||
suite.add('event+data', function () {
|
||||
parser.decodePacket('5:::{"name":"edwald","args":[{"a": "b"},2,"3"]}');
|
||||
});
|
||||
|
||||
suite.add('heartbeat', function () {
|
||||
parser.decodePacket('2:::');
|
||||
});
|
||||
|
||||
suite.add('error', function () {
|
||||
parser.decodePacket('7:::2+0');
|
||||
});
|
||||
|
||||
var payload = parser.encodePayload([
|
||||
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
|
||||
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
|
||||
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
|
||||
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
|
||||
, parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' })
|
||||
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
|
||||
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
|
||||
]);
|
||||
|
||||
suite.add('payload', function () {
|
||||
parser.decodePayload(payload);
|
||||
});
|
||||
|
||||
suite.on('cycle', function (bench, details) {
|
||||
console.log('\n' + suite.name.grey, details.name.white.bold);
|
||||
console.log([
|
||||
details.hz.toFixed(2).cyan + ' ops/sec'.grey
|
||||
, details.count.toString().white + ' times executed'.grey
|
||||
, 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey
|
||||
,
|
||||
].join(', '.grey));
|
||||
});
|
||||
|
||||
if (!module.parent) {
|
||||
suite.run();
|
||||
} else {
|
||||
module.exports = suite;
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var benchmark = require('benchmark')
|
||||
, colors = require('colors')
|
||||
, io = require('../')
|
||||
, parser = io.parser
|
||||
, suite = new benchmark.Suite('Encode packet');
|
||||
|
||||
suite.add('string', function () {
|
||||
parser.encodePacket({
|
||||
type: 'json'
|
||||
, endpoint: ''
|
||||
, data: '2'
|
||||
});
|
||||
});
|
||||
|
||||
suite.add('event', function () {
|
||||
parser.encodePacket({
|
||||
type: 'event'
|
||||
, name: 'woot'
|
||||
, endpoint: ''
|
||||
, args: []
|
||||
});
|
||||
});
|
||||
|
||||
suite.add('event+ack', function () {
|
||||
parser.encodePacket({
|
||||
type: 'json'
|
||||
, id: 1
|
||||
, ack: 'data'
|
||||
, endpoint: ''
|
||||
, data: { a: 'b' }
|
||||
});
|
||||
});
|
||||
|
||||
suite.add('event+data', function () {
|
||||
parser.encodePacket({
|
||||
type: 'event'
|
||||
, name: 'edwald'
|
||||
, endpoint: ''
|
||||
, args: [{a: 'b'}, 2, '3']
|
||||
});
|
||||
});
|
||||
|
||||
suite.add('heartbeat', function () {
|
||||
parser.encodePacket({
|
||||
type: 'heartbeat'
|
||||
, endpoint: ''
|
||||
})
|
||||
});
|
||||
|
||||
suite.add('error', function () {
|
||||
parser.encodePacket({
|
||||
type: 'error'
|
||||
, reason: 'unauthorized'
|
||||
, advice: 'reconnect'
|
||||
, endpoint: ''
|
||||
})
|
||||
})
|
||||
|
||||
suite.add('payload', function () {
|
||||
parser.encodePayload([
|
||||
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
|
||||
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
|
||||
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
|
||||
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
|
||||
, parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' })
|
||||
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
|
||||
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
|
||||
]);
|
||||
});
|
||||
|
||||
suite.on('cycle', function (bench, details) {
|
||||
console.log('\n' + suite.name.grey, details.name.white.bold);
|
||||
console.log([
|
||||
details.hz.toFixed(2).cyan + ' ops/sec'.grey
|
||||
, details.count.toString().white + ' times executed'.grey
|
||||
, 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey
|
||||
,
|
||||
].join(', '.grey));
|
||||
});
|
||||
|
||||
if (!module.parent) {
|
||||
suite.run();
|
||||
} else {
|
||||
module.exports = suite;
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Benchmark runner dependencies
|
||||
*/
|
||||
|
||||
var colors = require('colors')
|
||||
, path = require('path');
|
||||
|
||||
/**
|
||||
* Find all the benchmarks
|
||||
*/
|
||||
|
||||
var benchmarks_files = process.env.BENCHMARKS.split(' ')
|
||||
, all = [].concat(benchmarks_files)
|
||||
, first = all.shift()
|
||||
, benchmarks = {};
|
||||
|
||||
// find the benchmarks and load them all in our obj
|
||||
benchmarks_files.forEach(function (file) {
|
||||
benchmarks[file] = require(path.join(__dirname, '..', file));
|
||||
});
|
||||
|
||||
// setup the complete listeners
|
||||
benchmarks_files.forEach(function (file) {
|
||||
var benchmark = benchmarks[file]
|
||||
, next_file = all.shift()
|
||||
, next = benchmarks[next_file];
|
||||
|
||||
/**
|
||||
* Generate a oncomplete function for the tests, either we are done or we
|
||||
* have more benchmarks to process.
|
||||
*/
|
||||
|
||||
function complete () {
|
||||
if (!next) {
|
||||
console.log(
|
||||
'\n\nBenchmark completed in'.grey
|
||||
, (Date.now() - start).toString().green + ' ms'.grey
|
||||
);
|
||||
} else {
|
||||
console.log('\nStarting benchmark '.grey + next_file.yellow);
|
||||
next.run();
|
||||
}
|
||||
}
|
||||
|
||||
// attach the listener
|
||||
benchmark.on('complete', complete);
|
||||
});
|
||||
|
||||
/**
|
||||
* Start the benchmark
|
||||
*/
|
||||
|
||||
var start = Date.now();
|
||||
console.log('Starting benchmark '.grey + first.yellow);
|
||||
benchmarks[first].run();
|
||||
25
examples/chat/README.md
Normal file
25
examples/chat/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
# Socket.IO Chat
|
||||
|
||||
A simple chat demo for socket.io
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ cd socket.io
|
||||
$ npm install
|
||||
$ cd examples/chat
|
||||
$ npm install
|
||||
$ node .
|
||||
```
|
||||
|
||||
And point your browser to `http://localhost:3000`. Optionally, specify
|
||||
a port by supplying the `PORT` env variable.
|
||||
|
||||
## Features
|
||||
|
||||
- Multiple users can join a chat room by each entering a unique username
|
||||
on website load.
|
||||
- Users can type chat messages to the chat room.
|
||||
- A notification is sent to all users when a user joins or leaves
|
||||
the chatroom.
|
||||
@@ -1,80 +0,0 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('express')
|
||||
, stylus = require('stylus')
|
||||
, nib = require('nib')
|
||||
, sio = require('../../lib/socket.io');
|
||||
|
||||
/**
|
||||
* App.
|
||||
*/
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
/**
|
||||
* App configuration.
|
||||
*/
|
||||
|
||||
app.configure(function () {
|
||||
app.use(stylus.middleware({ src: __dirname + '/public', compile: compile }));
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
app.set('views', __dirname);
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
function compile (str, path) {
|
||||
return stylus(str)
|
||||
.set('filename', path)
|
||||
.use(nib());
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* App routes.
|
||||
*/
|
||||
|
||||
app.get('/', function (req, res) {
|
||||
res.render('index', { layout: false });
|
||||
});
|
||||
|
||||
/**
|
||||
* App listen.
|
||||
*/
|
||||
|
||||
app.listen(3000, function () {
|
||||
var addr = app.address();
|
||||
console.log(' app listening on http://' + addr.address + ':' + addr.port);
|
||||
});
|
||||
|
||||
/**
|
||||
* Socket.IO server (single process only)
|
||||
*/
|
||||
|
||||
var io = sio.listen(app)
|
||||
, nicknames = {};
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.on('user message', function (msg) {
|
||||
socket.broadcast.emit('user message', socket.nickname, msg);
|
||||
});
|
||||
|
||||
socket.on('nickname', function (nick, fn) {
|
||||
if (nicknames[nick]) {
|
||||
fn(true);
|
||||
} else {
|
||||
fn(false);
|
||||
nicknames[nick] = socket.nickname = nick;
|
||||
socket.broadcast.emit('announcement', nick + ' connected');
|
||||
io.sockets.emit('nicknames', nicknames);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
if (!socket.nickname) return;
|
||||
|
||||
delete nicknames[socket.nickname];
|
||||
socket.broadcast.emit('announcement', socket.nickname + ' disconnected');
|
||||
socket.broadcast.emit('nicknames', nicknames);
|
||||
});
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
doctype 5
|
||||
html
|
||||
head
|
||||
link(href='/stylesheets/style.css', rel='stylesheet')
|
||||
script(src='http://code.jquery.com/jquery-1.6.1.min.js')
|
||||
script(src='/socket.io/socket.io.js')
|
||||
script
|
||||
// socket.io specific code
|
||||
var socket = io.connect();
|
||||
|
||||
socket.on('connect', function () {
|
||||
$('#chat').addClass('connected');
|
||||
});
|
||||
|
||||
socket.on('announcement', function (msg) {
|
||||
$('#lines').append($('<p>').append($('<em>').text(msg)));
|
||||
});
|
||||
|
||||
socket.on('nicknames', function (nicknames) {
|
||||
$('#nicknames').empty().append($('<span>Online: </span>'));
|
||||
for (var i in nicknames) {
|
||||
$('#nicknames').append($('<b>').text(nicknames[i]));
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('user message', message);
|
||||
socket.on('reconnect', function () {
|
||||
$('#lines').remove();
|
||||
message('System', 'Reconnected to the server');
|
||||
});
|
||||
|
||||
socket.on('reconnecting', function () {
|
||||
message('System', 'Attempting to re-connect to the server');
|
||||
});
|
||||
|
||||
socket.on('error', function (e) {
|
||||
message('System', e ? e : 'A unknown error occurred');
|
||||
});
|
||||
|
||||
function message (from, msg) {
|
||||
$('#lines').append($('<p>').append($('<b>').text(from), msg));
|
||||
}
|
||||
|
||||
// dom manipulation
|
||||
$(function () {
|
||||
$('#set-nickname').submit(function (ev) {
|
||||
socket.emit('nickname', $('#nick').val(), function (set) {
|
||||
if (!set) {
|
||||
clear();
|
||||
return $('#chat').addClass('nickname-set');
|
||||
}
|
||||
$('#nickname-err').css('visibility', 'visible');
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#send-message').submit(function () {
|
||||
message('me', $('#message').val());
|
||||
socket.emit('user message', $('#message').val());
|
||||
clear();
|
||||
$('#lines').get(0).scrollTop = 10000000;
|
||||
return false;
|
||||
});
|
||||
|
||||
function clear () {
|
||||
$('#message').val('').focus();
|
||||
};
|
||||
});
|
||||
body
|
||||
#chat
|
||||
#nickname
|
||||
form.wrap#set-nickname
|
||||
p Please type in your nickname and press enter.
|
||||
input#nick
|
||||
p#nickname-err Nickname already in use
|
||||
#connecting
|
||||
.wrap Connecting to socket.io server
|
||||
#messages
|
||||
#nicknames
|
||||
#lines
|
||||
form#send-message
|
||||
input#message
|
||||
button Send
|
||||
75
examples/chat/index.js
Normal file
75
examples/chat/index.js
Normal file
@@ -0,0 +1,75 @@
|
||||
// Setup basic express server
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var server = require('http').createServer(app);
|
||||
var io = require('../..')(server);
|
||||
var port = process.env.PORT || 3000;
|
||||
|
||||
server.listen(port, function () {
|
||||
console.log('Server listening at port %d', port);
|
||||
});
|
||||
|
||||
// Routing
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// Chatroom
|
||||
|
||||
var numUsers = 0;
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
var addedUser = false;
|
||||
|
||||
// when the client emits 'new message', this listens and executes
|
||||
socket.on('new message', function (data) {
|
||||
// we tell the client to execute 'new message'
|
||||
socket.broadcast.emit('new message', {
|
||||
username: socket.username,
|
||||
message: data
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'add user', this listens and executes
|
||||
socket.on('add user', function (username) {
|
||||
if (addedUser) return;
|
||||
|
||||
// we store the username in the socket session for this client
|
||||
socket.username = username;
|
||||
++numUsers;
|
||||
addedUser = true;
|
||||
socket.emit('login', {
|
||||
numUsers: numUsers
|
||||
});
|
||||
// echo globally (all clients) that a person has connected
|
||||
socket.broadcast.emit('user joined', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'typing', we broadcast it to others
|
||||
socket.on('typing', function () {
|
||||
socket.broadcast.emit('typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'stop typing', we broadcast it to others
|
||||
socket.on('stop typing', function () {
|
||||
socket.broadcast.emit('stop typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the user disconnects.. perform this
|
||||
socket.on('disconnect', function () {
|
||||
if (addedUser) {
|
||||
--numUsers;
|
||||
|
||||
// echo globally that this client has left
|
||||
socket.broadcast.emit('user left', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "chat.io"
|
||||
, "description": "example chat application with socket.io"
|
||||
, "version": "0.0.1"
|
||||
, "dependencies": {
|
||||
"express": "2.5.5"
|
||||
, "jade": "0.16.4"
|
||||
, "stylus": "0.19.0"
|
||||
, "nib": "0.2.0"
|
||||
}
|
||||
}
|
||||
"name": "socket.io-chat",
|
||||
"version": "0.0.0",
|
||||
"description": "A simple chat client using socket.io",
|
||||
"main": "index.js",
|
||||
"author": "Grant Timmerman",
|
||||
"private": true,
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "3.4.8"
|
||||
}
|
||||
}
|
||||
28
examples/chat/public/index.html
Normal file
28
examples/chat/public/index.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO Chat Example</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<ul class="pages">
|
||||
<li class="chat page">
|
||||
<div class="chatArea">
|
||||
<ul class="messages"></ul>
|
||||
</div>
|
||||
<input class="inputMessage" placeholder="Type here..."/>
|
||||
</li>
|
||||
<li class="login page">
|
||||
<div class="form">
|
||||
<h3 class="title">What's your nickname?</h3>
|
||||
<input class="usernameInput" type="text" maxlength="14" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
266
examples/chat/public/main.js
Normal file
266
examples/chat/public/main.js
Normal file
@@ -0,0 +1,266 @@
|
||||
$(function() {
|
||||
var FADE_TIME = 150; // ms
|
||||
var TYPING_TIMER_LENGTH = 400; // ms
|
||||
var COLORS = [
|
||||
'#e21400', '#91580f', '#f8a700', '#f78b00',
|
||||
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
|
||||
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
|
||||
];
|
||||
|
||||
// Initialize variables
|
||||
var $window = $(window);
|
||||
var $usernameInput = $('.usernameInput'); // Input for username
|
||||
var $messages = $('.messages'); // Messages area
|
||||
var $inputMessage = $('.inputMessage'); // Input message input box
|
||||
|
||||
var $loginPage = $('.login.page'); // The login page
|
||||
var $chatPage = $('.chat.page'); // The chatroom page
|
||||
|
||||
// Prompt for setting a username
|
||||
var username;
|
||||
var connected = false;
|
||||
var typing = false;
|
||||
var lastTypingTime;
|
||||
var $currentInput = $usernameInput.focus();
|
||||
|
||||
var socket = io();
|
||||
|
||||
function addParticipantsMessage (data) {
|
||||
var message = '';
|
||||
if (data.numUsers === 1) {
|
||||
message += "there's 1 participant";
|
||||
} else {
|
||||
message += "there are " + data.numUsers + " participants";
|
||||
}
|
||||
log(message);
|
||||
}
|
||||
|
||||
// Sets the client's username
|
||||
function setUsername () {
|
||||
username = cleanInput($usernameInput.val().trim());
|
||||
|
||||
// If the username is valid
|
||||
if (username) {
|
||||
$loginPage.fadeOut();
|
||||
$chatPage.show();
|
||||
$loginPage.off('click');
|
||||
$currentInput = $inputMessage.focus();
|
||||
|
||||
// Tell the server your username
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
}
|
||||
|
||||
// Sends a chat message
|
||||
function sendMessage () {
|
||||
var message = $inputMessage.val();
|
||||
// Prevent markup from being injected into the message
|
||||
message = cleanInput(message);
|
||||
// if there is a non-empty message and a socket connection
|
||||
if (message && connected) {
|
||||
$inputMessage.val('');
|
||||
addChatMessage({
|
||||
username: username,
|
||||
message: message
|
||||
});
|
||||
// tell server to execute 'new message' and send along one parameter
|
||||
socket.emit('new message', message);
|
||||
}
|
||||
}
|
||||
|
||||
// Log a message
|
||||
function log (message, options) {
|
||||
var $el = $('<li>').addClass('log').text(message);
|
||||
addMessageElement($el, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat message to the message list
|
||||
function addChatMessage (data, options) {
|
||||
// Don't fade the message in if there is an 'X was typing'
|
||||
var $typingMessages = getTypingMessages(data);
|
||||
options = options || {};
|
||||
if ($typingMessages.length !== 0) {
|
||||
options.fade = false;
|
||||
$typingMessages.remove();
|
||||
}
|
||||
|
||||
var $usernameDiv = $('<span class="username"/>')
|
||||
.text(data.username)
|
||||
.css('color', getUsernameColor(data.username));
|
||||
var $messageBodyDiv = $('<span class="messageBody">')
|
||||
.text(data.message);
|
||||
|
||||
var typingClass = data.typing ? 'typing' : '';
|
||||
var $messageDiv = $('<li class="message"/>')
|
||||
.data('username', data.username)
|
||||
.addClass(typingClass)
|
||||
.append($usernameDiv, $messageBodyDiv);
|
||||
|
||||
addMessageElement($messageDiv, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat typing message
|
||||
function addChatTyping (data) {
|
||||
data.typing = true;
|
||||
data.message = 'is typing';
|
||||
addChatMessage(data);
|
||||
}
|
||||
|
||||
// Removes the visual chat typing message
|
||||
function removeChatTyping (data) {
|
||||
getTypingMessages(data).fadeOut(function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Adds a message element to the messages and scrolls to the bottom
|
||||
// el - The element to add as a message
|
||||
// options.fade - If the element should fade-in (default = true)
|
||||
// options.prepend - If the element should prepend
|
||||
// all other messages (default = false)
|
||||
function addMessageElement (el, options) {
|
||||
var $el = $(el);
|
||||
|
||||
// Setup default options
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (typeof options.fade === 'undefined') {
|
||||
options.fade = true;
|
||||
}
|
||||
if (typeof options.prepend === 'undefined') {
|
||||
options.prepend = false;
|
||||
}
|
||||
|
||||
// Apply options
|
||||
if (options.fade) {
|
||||
$el.hide().fadeIn(FADE_TIME);
|
||||
}
|
||||
if (options.prepend) {
|
||||
$messages.prepend($el);
|
||||
} else {
|
||||
$messages.append($el);
|
||||
}
|
||||
$messages[0].scrollTop = $messages[0].scrollHeight;
|
||||
}
|
||||
|
||||
// Prevents input from having injected markup
|
||||
function cleanInput (input) {
|
||||
return $('<div/>').text(input).text();
|
||||
}
|
||||
|
||||
// Updates the typing event
|
||||
function updateTyping () {
|
||||
if (connected) {
|
||||
if (!typing) {
|
||||
typing = true;
|
||||
socket.emit('typing');
|
||||
}
|
||||
lastTypingTime = (new Date()).getTime();
|
||||
|
||||
setTimeout(function () {
|
||||
var typingTimer = (new Date()).getTime();
|
||||
var timeDiff = typingTimer - lastTypingTime;
|
||||
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
}
|
||||
}, TYPING_TIMER_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the 'X is typing' messages of a user
|
||||
function getTypingMessages (data) {
|
||||
return $('.typing.message').filter(function (i) {
|
||||
return $(this).data('username') === data.username;
|
||||
});
|
||||
}
|
||||
|
||||
// Gets the color of a username through our hash function
|
||||
function getUsernameColor (username) {
|
||||
// Compute hash code
|
||||
var hash = 7;
|
||||
for (var i = 0; i < username.length; i++) {
|
||||
hash = username.charCodeAt(i) + (hash << 5) - hash;
|
||||
}
|
||||
// Calculate color
|
||||
var index = Math.abs(hash % COLORS.length);
|
||||
return COLORS[index];
|
||||
}
|
||||
|
||||
// Keyboard events
|
||||
|
||||
$window.keydown(function (event) {
|
||||
// Auto-focus the current input when a key is typed
|
||||
if (!(event.ctrlKey || event.metaKey || event.altKey)) {
|
||||
$currentInput.focus();
|
||||
}
|
||||
// When the client hits ENTER on their keyboard
|
||||
if (event.which === 13) {
|
||||
if (username) {
|
||||
sendMessage();
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
} else {
|
||||
setUsername();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$inputMessage.on('input', function() {
|
||||
updateTyping();
|
||||
});
|
||||
|
||||
// Click events
|
||||
|
||||
// Focus input when clicking anywhere on login page
|
||||
$loginPage.click(function () {
|
||||
$currentInput.focus();
|
||||
});
|
||||
|
||||
// Focus input when clicking on the message input's border
|
||||
$inputMessage.click(function () {
|
||||
$inputMessage.focus();
|
||||
});
|
||||
|
||||
// Socket events
|
||||
|
||||
// Whenever the server emits 'login', log the login message
|
||||
socket.on('login', function (data) {
|
||||
connected = true;
|
||||
// Display the welcome message
|
||||
var message = "Welcome to Socket.IO Chat – ";
|
||||
log(message, {
|
||||
prepend: true
|
||||
});
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'new message', update the chat body
|
||||
socket.on('new message', function (data) {
|
||||
addChatMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user joined', log it in the chat body
|
||||
socket.on('user joined', function (data) {
|
||||
log(data.username + ' joined');
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user left', log it in the chat body
|
||||
socket.on('user left', function (data) {
|
||||
log(data.username + ' left');
|
||||
addParticipantsMessage(data);
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'typing', show the typing message
|
||||
socket.on('typing', function (data) {
|
||||
addChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'stop typing', kill the typing message
|
||||
socket.on('stop typing', function (data) {
|
||||
removeChatTyping(data);
|
||||
});
|
||||
});
|
||||
150
examples/chat/public/style.css
Normal file
150
examples/chat/public/style.css
Normal file
@@ -0,0 +1,150 @@
|
||||
/* Fix user-agent */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-weight: 300;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
html, input {
|
||||
font-family:
|
||||
"HelveticaNeue-Light",
|
||||
"Helvetica Neue Light",
|
||||
"Helvetica Neue",
|
||||
Helvetica,
|
||||
Arial,
|
||||
"Lucida Grande",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Pages */
|
||||
|
||||
.pages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Login Page */
|
||||
|
||||
.login.page {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.login.page .form {
|
||||
height: 100px;
|
||||
margin-top: -100px;
|
||||
position: absolute;
|
||||
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login.page .form .usernameInput {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #fff;
|
||||
outline: none;
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.login.page .title {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
.login.page .usernameInput {
|
||||
font-size: 200%;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.login.page .title, .login.page .usernameInput {
|
||||
color: #fff;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* Chat page */
|
||||
|
||||
.chat.page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
.messages {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.inputMessage {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: gray;
|
||||
font-size: 70%;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
|
||||
.chatArea {
|
||||
height: 100%;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-y: scroll;
|
||||
padding: 10px 20px 10px 20px;
|
||||
}
|
||||
|
||||
.message.typing .messageBody {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.username {
|
||||
float: left;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
padding-right: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
|
||||
.inputMessage {
|
||||
border: 10px solid #000;
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
left: 0;
|
||||
outline: none;
|
||||
padding-left: 10px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
border-radius(n)
|
||||
-webkit-border-radius n
|
||||
-moz-border-radius n
|
||||
border-radius n
|
||||
|
||||
// replace str with val
|
||||
|
||||
replace(expr, str, val)
|
||||
expr = clone(expr)
|
||||
for e, i in expr
|
||||
if str == e
|
||||
expr[i] = val
|
||||
expr
|
||||
|
||||
// normalize gradient point (webkit)
|
||||
|
||||
grad-point(pos)
|
||||
if length(pos) == 1
|
||||
return left pos if pos in (top bottom)
|
||||
return pos top if pos in (left right)
|
||||
else if pos[0] in (top bottom)
|
||||
pos[1] pos[0]
|
||||
else
|
||||
pos
|
||||
|
||||
// implicit color stop position
|
||||
|
||||
pos-in-stops(i, stops)
|
||||
len = length(stops)
|
||||
if len - 1 == i
|
||||
100%
|
||||
else if i
|
||||
unit(i / len * 100, '%')
|
||||
else
|
||||
0%
|
||||
|
||||
// normalize color stops
|
||||
// - (color pos) -> (pos color)
|
||||
// - (color) -> (implied-pos color)
|
||||
|
||||
normalize-stops(stops)
|
||||
stops = clone(stops)
|
||||
for stop, i in stops
|
||||
if length(stop) == 1
|
||||
color = stop[0]
|
||||
stop[0] = pos-in-stops(i, stops)
|
||||
stop[1] = color
|
||||
else if typeof(stop[1]) == 'unit'
|
||||
pos = stop[1]
|
||||
stop[1] = stop[0]
|
||||
stop[0] = pos
|
||||
stops
|
||||
|
||||
// join color stops with the given translation function
|
||||
|
||||
join-stops(stops, translate)
|
||||
str = ''
|
||||
len = length(stops)
|
||||
for stop, i in stops
|
||||
str += ', ' if i
|
||||
pos = stop[0]
|
||||
color = stop[1]
|
||||
str += translate(color, pos)
|
||||
unquote(str)
|
||||
|
||||
// webkit translation function
|
||||
|
||||
webkit-stop(color, pos)
|
||||
s('color-stop(%d, %s)', pos / 100, color)
|
||||
|
||||
// mozilla translation function
|
||||
|
||||
moz-stop(color, pos)
|
||||
s('%s %s', color, pos)
|
||||
|
||||
// create a linear gradient with the given start
|
||||
// position, followed by color stops
|
||||
|
||||
linear-gradient(start, stops...)
|
||||
error('color stops required') unless length(stops)
|
||||
prop = current-property[0]
|
||||
val = current-property[1]
|
||||
stops = normalize-stops(stops)
|
||||
|
||||
// webkit
|
||||
end = grad-point(opposite-position(start))
|
||||
webkit = s('-webkit-gradient(linear, %s, %s, %s)', grad-point(start), end, join-stops(stops, webkit-stop))
|
||||
add-property(prop, replace(val, '__CALL__', webkit))
|
||||
|
||||
// moz
|
||||
stops = join-stops(stops, moz-stop)
|
||||
moz = s('-moz-linear-gradient(%s, %s)', start, stops)
|
||||
add-property(prop, replace(val, '__CALL__', moz))
|
||||
|
||||
// literal
|
||||
s('linear-gradient(%s, %s)', start, stops)
|
||||
@@ -1,188 +0,0 @@
|
||||
#chat,
|
||||
#nickname,
|
||||
#messages {
|
||||
width: 600px;
|
||||
}
|
||||
#chat {
|
||||
position: relative;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
#nickname,
|
||||
#connecting {
|
||||
position: absolute;
|
||||
height: 410px;
|
||||
z-index: 100;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
width: 600px;
|
||||
font: 15px Georgia;
|
||||
color: #666;
|
||||
display: block;
|
||||
}
|
||||
#nickname .wrap,
|
||||
#connecting .wrap {
|
||||
padding-top: 150px;
|
||||
}
|
||||
#nickname input {
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
}
|
||||
#nickname input:focus {
|
||||
border-color: #999;
|
||||
outline: 0;
|
||||
}
|
||||
#nickname #nickname-err {
|
||||
color: #8b0000;
|
||||
font-size: 12px;
|
||||
visibility: hidden;
|
||||
}
|
||||
.connected #connecting {
|
||||
display: none;
|
||||
}
|
||||
.nickname-set #nickname {
|
||||
display: none;
|
||||
}
|
||||
#messages {
|
||||
height: 380px;
|
||||
background: #eee;
|
||||
}
|
||||
#messages em {
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
color: #999;
|
||||
}
|
||||
#messages p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font: 12px Helvetica, Arial;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
#messages p b {
|
||||
display: inline-block;
|
||||
padding-right: 10px;
|
||||
}
|
||||
#messages p:nth-child(even) {
|
||||
background: #fafafa;
|
||||
}
|
||||
#messages #nicknames {
|
||||
background: #ccc;
|
||||
padding: 2px 4px 4px;
|
||||
font: 11px Helvetica;
|
||||
}
|
||||
#messages #nicknames span {
|
||||
color: #000;
|
||||
}
|
||||
#messages #nicknames b {
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
background: #999;
|
||||
padding: 3px 6px;
|
||||
margin-right: 5px;
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
text-shadow: 0 1px 0 #666;
|
||||
}
|
||||
#messages #lines {
|
||||
height: 355px;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
#messages #lines::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
#messages #lines::-webkit-scrollbar-button:start:decrement,
|
||||
#messages #lines ::-webkit-scrollbar-button:end:increment {
|
||||
display: block;
|
||||
height: 10px;
|
||||
}
|
||||
#messages #lines::-webkit-scrollbar-button:vertical:increment {
|
||||
background-color: #fff;
|
||||
}
|
||||
#messages #lines::-webkit-scrollbar-track-piece {
|
||||
background-color: #fff;
|
||||
-webkit-border-radius: 3px;
|
||||
}
|
||||
#messages #lines::-webkit-scrollbar-thumb:vertical {
|
||||
height: 50px;
|
||||
background-color: #ccc;
|
||||
-webkit-border-radius: 3px;
|
||||
}
|
||||
#messages #lines::-webkit-scrollbar-thumb:horizontal {
|
||||
width: 50px;
|
||||
background-color: #fff;
|
||||
-webkit-border-radius: 3px;
|
||||
}
|
||||
#send-message {
|
||||
background: #fff;
|
||||
position: relative;
|
||||
}
|
||||
#send-message input {
|
||||
border: none;
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
line-height: 30px;
|
||||
vertical-align: middle;
|
||||
width: 580px;
|
||||
}
|
||||
#send-message input:focus {
|
||||
outline: 0;
|
||||
}
|
||||
#send-message button {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
button {
|
||||
margin: 0;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
background: #43a1f7;
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #43a1f7), color-stop(1, #377ad0));
|
||||
background: -webkit-linear-gradient(top, #43a1f7 0%, #377ad0 100%);
|
||||
background: -moz-linear-gradient(top, #43a1f7 0%, #377ad0 100%);
|
||||
background: linear-gradient(top, #43a1f7 0%, #377ad0 100%);
|
||||
border: 1px solid #2e70c4;
|
||||
-webkit-border-radius: 16px;
|
||||
-moz-border-radius: 16px;
|
||||
border-radius: 16px;
|
||||
color: #fff;
|
||||
font-family: "lucida grande", sans-serif;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
line-height: 1;
|
||||
padding: 3px 10px 5px 10px;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 1px #2d6dc0;
|
||||
}
|
||||
button:hover,
|
||||
button.hover {
|
||||
background: darker;
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #43a1f7), color-stop(1, #2e70c4));
|
||||
background: -webkit-linear-gradient(top, #43a1f7 0%, #2e70c4 100%);
|
||||
background: -moz-linear-gradient(top, #43a1f7 0%, #2e70c4 100%);
|
||||
background: linear-gradient(top, #43a1f7 0%, #2e70c4 100%);
|
||||
border: 1px solid #2e70c4;
|
||||
cursor: pointer;
|
||||
text-shadow: 0 -1px 1px #2c6bbb;
|
||||
}
|
||||
button:active,
|
||||
button.active {
|
||||
background: #2e70c4;
|
||||
border: 1px solid #2e70c4;
|
||||
border-bottom: 1px solid #2861aa;
|
||||
text-shadow: 0 -1px 1px #2b67b5;
|
||||
}
|
||||
button:focus,
|
||||
button.focus {
|
||||
outline: none;
|
||||
-webkit-box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0;
|
||||
-moz-box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0;
|
||||
box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0;
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
@import 'nib'
|
||||
|
||||
#chat, #nickname, #messages
|
||||
width 600px
|
||||
|
||||
#chat
|
||||
position relative
|
||||
border 1px solid #ccc
|
||||
|
||||
#nickname, #connecting
|
||||
position absolute
|
||||
height 410px
|
||||
z-index 100
|
||||
left 0
|
||||
top 0
|
||||
background #fff
|
||||
text-align center
|
||||
width 600px
|
||||
font 15px Georgia
|
||||
color #666
|
||||
display block
|
||||
.wrap
|
||||
padding-top 150px
|
||||
|
||||
#nickname
|
||||
input
|
||||
border 1px solid #ccc
|
||||
padding 10px
|
||||
&:focus
|
||||
border-color #999
|
||||
outline 0
|
||||
#nickname-err
|
||||
color darkred
|
||||
font-size 12px
|
||||
visibility hidden
|
||||
|
||||
.connected
|
||||
#connecting
|
||||
display none
|
||||
|
||||
.nickname-set
|
||||
#nickname
|
||||
display none
|
||||
|
||||
#messages
|
||||
height 380px
|
||||
background #eee
|
||||
em
|
||||
text-shadow 0 1px 0 #fff
|
||||
color #999
|
||||
p
|
||||
padding 0
|
||||
margin 0
|
||||
font 12px Helvetica, Arial
|
||||
padding 5px 10px
|
||||
b
|
||||
display inline-block
|
||||
padding-right 10px
|
||||
p:nth-child(even)
|
||||
background #fafafa
|
||||
#nicknames
|
||||
background #ccc
|
||||
padding 2px 4px 4px
|
||||
font 11px Helvetica
|
||||
span
|
||||
color black
|
||||
b
|
||||
display inline-block
|
||||
color #fff
|
||||
background #999
|
||||
padding 3px 6px
|
||||
margin-right 5px
|
||||
border-radius 10px
|
||||
text-shadow 0 1px 0 #666
|
||||
#lines
|
||||
height 355px
|
||||
overflow auto
|
||||
overflow-x hidden
|
||||
overflow-y auto
|
||||
&::-webkit-scrollbar
|
||||
width 6px
|
||||
height 6px
|
||||
&::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment
|
||||
display block
|
||||
height 10px
|
||||
&::-webkit-scrollbar-button:vertical:increment
|
||||
background-color #fff
|
||||
&::-webkit-scrollbar-track-piece
|
||||
background-color #fff
|
||||
-webkit-border-radius 3px
|
||||
&::-webkit-scrollbar-thumb:vertical
|
||||
height 50px
|
||||
background-color #ccc
|
||||
-webkit-border-radius 3px
|
||||
&::-webkit-scrollbar-thumb:horizontal
|
||||
width 50px
|
||||
background-color #fff
|
||||
-webkit-border-radius 3px
|
||||
|
||||
#send-message
|
||||
background #fff
|
||||
position relative
|
||||
input
|
||||
border none
|
||||
height 30px
|
||||
padding 0 10px
|
||||
line-height 30px
|
||||
vertical-align middle
|
||||
width 580px
|
||||
&:focus
|
||||
outline 0
|
||||
button
|
||||
position absolute
|
||||
top 5px
|
||||
right 5px
|
||||
|
||||
button
|
||||
download-button()
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('express')
|
||||
, stylus = require('stylus')
|
||||
, nib = require('nib')
|
||||
, sio = require('../../lib/socket.io')
|
||||
, irc = require('./irc');
|
||||
|
||||
/**
|
||||
* App.
|
||||
*/
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
/**
|
||||
* App configuration.
|
||||
*/
|
||||
|
||||
app.configure(function () {
|
||||
app.use(stylus.middleware({ src: __dirname + '/public', compile: compile }))
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
app.set('views', __dirname);
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
function compile (str, path) {
|
||||
return stylus(str)
|
||||
.set('filename', path)
|
||||
.use(nib());
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* App routes.
|
||||
*/
|
||||
|
||||
app.get('/', function (req, res) {
|
||||
res.render('index', { layout: false });
|
||||
});
|
||||
|
||||
/**
|
||||
* App listen.
|
||||
*/
|
||||
|
||||
app.listen(3000, function () {
|
||||
var addr = app.address();
|
||||
console.log(' app listening on http://' + addr.address + ':' + addr.port);
|
||||
});
|
||||
|
||||
/**
|
||||
* Socket.IO server
|
||||
*/
|
||||
|
||||
var io = sio.listen(app)
|
||||
|
||||
/**
|
||||
* Connect to IRC.
|
||||
*/
|
||||
|
||||
var client = new irc.Client('irc.freenode.net', 6667);
|
||||
client.connect('socketio\\test\\' + String(Math.random()).substr(-3));
|
||||
client.on('001', function () {
|
||||
this.send('JOIN', '#node.js');
|
||||
});
|
||||
client.on('PART', function (prefix) {
|
||||
io.sockets.emit('announcement', irc.user(prefix) + ' left the channel');
|
||||
});
|
||||
client.on('JOIN', function (prefix) {
|
||||
io.sockets.emit('announcement', irc.user(prefix) + ' joined the channel');
|
||||
});
|
||||
client.on('PRIVMSG', function (prefix, channel, text) {
|
||||
io.sockets.emit('irc message', irc.user(prefix), text);
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
doctype 5
|
||||
html
|
||||
head
|
||||
link(href='/stylesheets/style.css', rel='stylesheet')
|
||||
script(src='http://code.jquery.com/jquery-1.6.1.min.js')
|
||||
script(src='/socket.io/socket.io.js')
|
||||
script
|
||||
var socket = io.connect();
|
||||
|
||||
socket.on('connect', function () {
|
||||
$('#irc').addClass('connected');
|
||||
});
|
||||
|
||||
socket.on('announcement', function (msg) {
|
||||
$('#messages').append($('<p>').append($('<em>').text(msg)));
|
||||
$('#messages').get(0).scrollTop = 10000000;
|
||||
});
|
||||
|
||||
socket.on('irc message', function (user, msg) {
|
||||
$('#messages').append($('<p>').append($('<b>').text(user), msg));
|
||||
$('#messages').get(0).scrollTop = 10000000;
|
||||
});
|
||||
body
|
||||
h2 Node.JS IRC
|
||||
#irc
|
||||
#connecting
|
||||
.wrap Connecting to socket.io server
|
||||
#messages
|
||||
@@ -1,164 +0,0 @@
|
||||
/**
|
||||
* From https://github.com/felixge/nodelog/
|
||||
*/
|
||||
|
||||
var sys = require('util');
|
||||
var tcp = require('net');
|
||||
var irc = exports;
|
||||
|
||||
function bind(fn, scope) {
|
||||
var bindArgs = Array.prototype.slice.call(arguments);
|
||||
bindArgs.shift();
|
||||
bindArgs.shift();
|
||||
|
||||
return function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
fn.apply(scope, bindArgs.concat(args));
|
||||
};
|
||||
}
|
||||
|
||||
function each(set, iterator) {
|
||||
for (var i = 0; i < set.length; i++) {
|
||||
var r = iterator(set[i], i);
|
||||
if (r === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var Client = irc.Client = function(host, port) {
|
||||
this.host = host || 'localhost';
|
||||
this.port = port || 6667;
|
||||
|
||||
this.connection = null;
|
||||
this.buffer = '';
|
||||
this.encoding = 'utf8';
|
||||
this.timeout = 10 * 60 * 60 * 1000;
|
||||
|
||||
this.nick = null;
|
||||
this.user = null;
|
||||
this.real = null;
|
||||
}
|
||||
sys.inherits(Client, process.EventEmitter);
|
||||
|
||||
Client.prototype.connect = function(nick, user, real) {
|
||||
var connection = tcp.createConnection(this.port, this.host);
|
||||
connection.setEncoding(this.encoding);
|
||||
connection.setTimeout(this.timeout);
|
||||
connection.addListener('connect', bind(this.onConnect, this));
|
||||
connection.addListener('data', bind(this.onReceive, this));
|
||||
connection.addListener('end', bind(this.onEof, this));
|
||||
connection.addListener('timeout', bind(this.onTimeout, this));
|
||||
connection.addListener('close', bind(this.onClose, this));
|
||||
|
||||
this.nick = nick;
|
||||
this.user = user || 'guest';
|
||||
this.real = real || 'Guest';
|
||||
|
||||
this.connection = connection;
|
||||
};
|
||||
|
||||
Client.prototype.disconnect = function(why) {
|
||||
if (this.connection.readyState !== 'closed') {
|
||||
this.connection.close();
|
||||
sys.puts('disconnected (reason: '+why+')');
|
||||
this.emit('DISCONNECT', why);
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype.send = function(arg1) {
|
||||
if (this.connection.readyState !== 'open') {
|
||||
return this.disconnect('cannot send with readyState: '+this.connection.readyState);
|
||||
}
|
||||
|
||||
var message = [];
|
||||
for (var i = 0; i< arguments.length; i++) {
|
||||
if (arguments[i]) {
|
||||
message.push(arguments[i]);
|
||||
}
|
||||
}
|
||||
message = message.join(' ');
|
||||
|
||||
sys.puts('> '+message);
|
||||
message = message + "\r\n";
|
||||
this.connection.write(message, this.encoding);
|
||||
};
|
||||
|
||||
Client.prototype.parse = function(message) {
|
||||
var match = message.match(/(?:(:[^\s]+) )?([^\s]+) (.+)/);
|
||||
var parsed = {
|
||||
prefix: match[1],
|
||||
command: match[2]
|
||||
};
|
||||
|
||||
var params = match[3].match(/(.*?) ?:(.*)/);
|
||||
if (params) {
|
||||
// Params before :
|
||||
params[1] = (params[1])
|
||||
? params[1].split(' ')
|
||||
: [];
|
||||
// Rest after :
|
||||
params[2] = params[2]
|
||||
? [params[2]]
|
||||
: [];
|
||||
|
||||
params = params[1].concat(params[2]);
|
||||
} else {
|
||||
params = match[3].split(' ');
|
||||
}
|
||||
|
||||
parsed.params = params;
|
||||
return parsed;
|
||||
};
|
||||
|
||||
Client.prototype.onConnect = function() {
|
||||
this.send('NICK', this.nick);
|
||||
this.send('USER', this.user, '0', '*', ':'+this.real);
|
||||
};
|
||||
|
||||
Client.prototype.onReceive = function(chunk) {
|
||||
this.buffer = this.buffer + chunk;
|
||||
|
||||
while (this.buffer) {
|
||||
var offset = this.buffer.indexOf("\r\n");
|
||||
if (offset < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var message = this.buffer.substr(0, offset);
|
||||
this.buffer = this.buffer.substr(offset + 2);
|
||||
sys.puts('< '+message);
|
||||
|
||||
message = this.parse(message);
|
||||
|
||||
this.emit.apply(this, [message.command, message.prefix].concat(message.params));
|
||||
|
||||
if (message !== false) {
|
||||
this.onMessage(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype.onMessage = function(message) {
|
||||
switch (message.command) {
|
||||
case 'PING':
|
||||
this.send('PONG', ':'+message.params[0]);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype.onEof = function() {
|
||||
this.disconnect('eof');
|
||||
};
|
||||
|
||||
Client.prototype.onTimeout = function() {
|
||||
this.disconnect('timeout');
|
||||
};
|
||||
|
||||
Client.prototype.onClose = function() {
|
||||
this.disconnect('close');
|
||||
};
|
||||
|
||||
exports.user = function(prefix) {
|
||||
return prefix.match(/:([^!]+)!/)[1]
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "socket.io-irc"
|
||||
, "version": "0.0.1"
|
||||
, "dependencies": {
|
||||
"express": "2.5.5"
|
||||
, "jade": "0.16.4"
|
||||
, "stylus": "0.19.0"
|
||||
, "nib": "0.2.0"
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
@import 'nib'
|
||||
|
||||
h2
|
||||
font bold 18px Helvetica Neue, Arial
|
||||
|
||||
#irc, #messages
|
||||
width 600px
|
||||
|
||||
#irc
|
||||
position relative
|
||||
border 1px solid #ccc
|
||||
|
||||
#connecting
|
||||
position absolute
|
||||
height 410px
|
||||
z-index 100
|
||||
left 0
|
||||
top 0
|
||||
background #fff
|
||||
text-align center
|
||||
width 600px
|
||||
font 15px Georgia
|
||||
color #666
|
||||
display block
|
||||
.wrap
|
||||
padding-top 150px
|
||||
|
||||
.connected
|
||||
#connecting
|
||||
display none
|
||||
|
||||
#messages
|
||||
height 380px
|
||||
background #eee
|
||||
overflow auto
|
||||
overflow-x hidden
|
||||
overflow-y auto
|
||||
&::-webkit-scrollbar
|
||||
width 6px
|
||||
height 6px
|
||||
&::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment
|
||||
display block
|
||||
height 10px
|
||||
&::-webkit-scrollbar-button:vertical:increment
|
||||
background-color #fff
|
||||
&::-webkit-scrollbar-track-piece
|
||||
background-color #fff
|
||||
-webkit-border-radius 3px
|
||||
&::-webkit-scrollbar-thumb:vertical
|
||||
height 50px
|
||||
background-color #ccc
|
||||
-webkit-border-radius 3px
|
||||
&::-webkit-scrollbar-thumb:horizontal
|
||||
width 50px
|
||||
background-color #fff
|
||||
-webkit-border-radius 3px
|
||||
em
|
||||
text-shadow 0 1px 0 #fff
|
||||
color #999
|
||||
p
|
||||
padding 0
|
||||
margin 0
|
||||
font 12px Helvetica, Arial
|
||||
padding 5px 10px
|
||||
b
|
||||
display inline-block
|
||||
padding-right 10px
|
||||
p:nth-child(even)
|
||||
background #fafafa
|
||||
8
index.js
8
index.js
@@ -1,8 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
module.exports = require('./lib/socket.io');
|
||||
251
lib/client.js
Normal file
251
lib/client.js
Normal file
@@ -0,0 +1,251 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var parser = require('socket.io-parser');
|
||||
var debug = require('debug')('socket.io:client');
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = Client;
|
||||
|
||||
/**
|
||||
* Client constructor.
|
||||
*
|
||||
* @param {Server} server instance
|
||||
* @param {Socket} connection
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Client(server, conn){
|
||||
this.server = server;
|
||||
this.conn = conn;
|
||||
this.encoder = new parser.Encoder();
|
||||
this.decoder = new parser.Decoder();
|
||||
this.id = conn.id;
|
||||
this.request = conn.request;
|
||||
this.setup();
|
||||
this.sockets = {};
|
||||
this.nsps = {};
|
||||
this.connectBuffer = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up event listeners.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.setup = function(){
|
||||
this.onclose = this.onclose.bind(this);
|
||||
this.ondata = this.ondata.bind(this);
|
||||
this.onerror = this.onerror.bind(this);
|
||||
this.ondecoded = this.ondecoded.bind(this);
|
||||
|
||||
this.decoder.on('decoded', this.ondecoded);
|
||||
this.conn.on('data', this.ondata);
|
||||
this.conn.on('error', this.onerror);
|
||||
this.conn.on('close', this.onclose);
|
||||
};
|
||||
|
||||
/**
|
||||
* Connects a client to a namespace.
|
||||
*
|
||||
* @param {String} namespace name
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.connect = function(name){
|
||||
debug('connecting to namespace %s', name);
|
||||
var nsp = this.server.nsps[name];
|
||||
if (!nsp) {
|
||||
this.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'});
|
||||
return;
|
||||
}
|
||||
|
||||
if ('/' != name && !this.nsps['/']) {
|
||||
this.connectBuffer.push(name);
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var socket = nsp.add(this, function(){
|
||||
self.sockets[socket.id] = socket;
|
||||
self.nsps[nsp.name] = socket;
|
||||
|
||||
if ('/' == nsp.name && self.connectBuffer.length > 0) {
|
||||
self.connectBuffer.forEach(self.connect, self);
|
||||
self.connectBuffer = [];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnects from all namespaces and closes transport.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.disconnect = function(){
|
||||
for (var id in this.sockets) {
|
||||
if (this.sockets.hasOwnProperty(id)) {
|
||||
this.sockets[id].disconnect();
|
||||
}
|
||||
}
|
||||
this.sockets = {};
|
||||
this.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a socket. Called by each `Socket`.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.remove = function(socket){
|
||||
if (this.sockets.hasOwnProperty(socket.id)) {
|
||||
var nsp = this.sockets[socket.id].nsp.name;
|
||||
delete this.sockets[socket.id];
|
||||
delete this.nsps[nsp];
|
||||
} else {
|
||||
debug('ignoring remove for %s', socket.id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the underlying connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.close = function(){
|
||||
if ('open' == this.conn.readyState) {
|
||||
debug('forcing transport close');
|
||||
this.conn.close();
|
||||
this.onclose('forced server close');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a packet to the transport.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @param {Object} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.packet = function(packet, opts){
|
||||
opts = opts || {};
|
||||
var self = this;
|
||||
|
||||
// this writes to the actual connection
|
||||
function writeToEngine(encodedPackets) {
|
||||
if (opts.volatile && !self.conn.transport.writable) return;
|
||||
for (var i = 0; i < encodedPackets.length; i++) {
|
||||
self.conn.write(encodedPackets[i], { compress: opts.compress });
|
||||
}
|
||||
}
|
||||
|
||||
if ('open' == this.conn.readyState) {
|
||||
debug('writing packet %j', packet);
|
||||
if (!opts.preEncoded) { // not broadcasting, need to encode
|
||||
this.encoder.encode(packet, function (encodedPackets) { // encode, then write results to engine
|
||||
writeToEngine(encodedPackets);
|
||||
});
|
||||
} else { // a broadcast pre-encodes a packet
|
||||
writeToEngine(packet);
|
||||
}
|
||||
} else {
|
||||
debug('ignoring packet write %j', packet);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called with incoming transport data.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.ondata = function(data){
|
||||
// try/catch is needed for protocol violations (GH-1880)
|
||||
try {
|
||||
this.decoder.add(data);
|
||||
} catch(e) {
|
||||
this.onerror(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when parser fully decodes a packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.ondecoded = function(packet) {
|
||||
if (parser.CONNECT == packet.type) {
|
||||
this.connect(packet.nsp);
|
||||
} else {
|
||||
var socket = this.nsps[packet.nsp];
|
||||
if (socket) {
|
||||
socket.onpacket(packet);
|
||||
} else {
|
||||
debug('no socket for namespace %s', packet.nsp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles an error.
|
||||
*
|
||||
* @param {Objcet} error object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.onerror = function(err){
|
||||
for (var id in this.sockets) {
|
||||
if (this.sockets.hasOwnProperty(id)) {
|
||||
this.sockets[id].onerror(err);
|
||||
}
|
||||
}
|
||||
this.onclose('client error');
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon transport close.
|
||||
*
|
||||
* @param {String} reason
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.onclose = function(reason){
|
||||
debug('client close with reason %s', reason);
|
||||
|
||||
// ignore a potential subsequent `close` event
|
||||
this.destroy();
|
||||
|
||||
// `nsps` and `sockets` are cleaned up seamlessly
|
||||
for (var id in this.sockets) {
|
||||
if (this.sockets.hasOwnProperty(id)) {
|
||||
this.sockets[id].onclose(reason);
|
||||
}
|
||||
}
|
||||
this.sockets = {};
|
||||
|
||||
this.decoder.destroy(); // clean up decoder
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleans up event listeners.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.destroy = function(){
|
||||
this.conn.removeListener('data', this.ondata);
|
||||
this.conn.removeListener('error', this.onerror);
|
||||
this.conn.removeListener('close', this.onclose);
|
||||
this.decoder.removeListener('decoded', this.ondecoded);
|
||||
};
|
||||
386
lib/index.js
Normal file
386
lib/index.js
Normal file
@@ -0,0 +1,386 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http');
|
||||
var read = require('fs').readFileSync;
|
||||
var parse = require('url').parse;
|
||||
var engine = require('engine.io');
|
||||
var client = require('socket.io-client');
|
||||
var clientVersion = require('socket.io-client/package').version;
|
||||
var Client = require('./client');
|
||||
var Namespace = require('./namespace');
|
||||
var Adapter = require('socket.io-adapter');
|
||||
var debug = require('debug')('socket.io:server');
|
||||
var url = require('url');
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = Server;
|
||||
|
||||
/**
|
||||
* Socket.IO client source.
|
||||
*/
|
||||
|
||||
var clientSource = read(require.resolve('socket.io-client/socket.io.js'), 'utf-8');
|
||||
|
||||
/**
|
||||
* Server constructor.
|
||||
*
|
||||
* @param {http.Server|Number|Object} http server, port or options
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Server(srv, opts){
|
||||
if (!(this instanceof Server)) return new Server(srv, opts);
|
||||
if ('object' == typeof srv && !srv.listen) {
|
||||
opts = srv;
|
||||
srv = null;
|
||||
}
|
||||
opts = opts || {};
|
||||
this.nsps = {};
|
||||
this.path(opts.path || '/socket.io');
|
||||
this.serveClient(false !== opts.serveClient);
|
||||
this.adapter(opts.adapter || Adapter);
|
||||
this.origins(opts.origins || '*:*');
|
||||
this.sockets = this.of('/');
|
||||
if (srv) this.attach(srv, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Server request verification function, that checks for allowed origins
|
||||
*
|
||||
* @param {http.IncomingMessage} request
|
||||
* @param {Function} callback to be called with the result: `fn(err, success)`
|
||||
*/
|
||||
|
||||
Server.prototype.checkRequest = function(req, fn) {
|
||||
var origin = req.headers.origin || req.headers.referer;
|
||||
|
||||
// file:// URLs produce a null Origin which can't be authorized via echo-back
|
||||
if ('null' == origin || null == origin) origin = '*';
|
||||
|
||||
if (!!origin && typeof(this._origins) == 'function') return this._origins(origin, fn);
|
||||
if (this._origins.indexOf('*:*') !== -1) return fn(null, true);
|
||||
if (origin) {
|
||||
try {
|
||||
var parts = url.parse(origin);
|
||||
var defaultPort = 'https:' == parts.protocol ? 443 : 80;
|
||||
parts.port = parts.port != null
|
||||
? parts.port
|
||||
: defaultPort;
|
||||
var ok =
|
||||
~this._origins.indexOf(parts.hostname + ':' + parts.port) ||
|
||||
~this._origins.indexOf(parts.hostname + ':*') ||
|
||||
~this._origins.indexOf('*:' + parts.port);
|
||||
return fn(null, !!ok);
|
||||
} catch (ex) {
|
||||
}
|
||||
}
|
||||
fn(null, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets/gets whether client code is being served.
|
||||
*
|
||||
* @param {Boolean} whether to serve client code
|
||||
* @return {Server|Boolean} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.serveClient = function(v){
|
||||
if (!arguments.length) return this._serveClient;
|
||||
this._serveClient = v;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Old settings for backwards compatibility
|
||||
*/
|
||||
|
||||
var oldSettings = {
|
||||
"transports": "transports",
|
||||
"heartbeat timeout": "pingTimeout",
|
||||
"heartbeat interval": "pingInterval",
|
||||
"destroy buffer size": "maxHttpBufferSize"
|
||||
};
|
||||
|
||||
/**
|
||||
* Backwards compatiblity.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.set = function(key, val){
|
||||
if ('authorization' == key && val) {
|
||||
this.use(function(socket, next) {
|
||||
val(socket.request, function(err, authorized) {
|
||||
if (err) return next(new Error(err));
|
||||
if (!authorized) return next(new Error('Not authorized'));
|
||||
next();
|
||||
});
|
||||
});
|
||||
} else if ('origins' == key && val) {
|
||||
this.origins(val);
|
||||
} else if ('resource' == key) {
|
||||
this.path(val);
|
||||
} else if (oldSettings[key] && this.eio[oldSettings[key]]) {
|
||||
this.eio[oldSettings[key]] = val;
|
||||
} else {
|
||||
console.error('Option %s is not valid. Please refer to the README.', key);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the client serving path.
|
||||
*
|
||||
* @param {String} pathname
|
||||
* @return {Server|String} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.path = function(v){
|
||||
if (!arguments.length) return this._path;
|
||||
this._path = v.replace(/\/$/, '');
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the adapter for rooms.
|
||||
*
|
||||
* @param {Adapter} pathname
|
||||
* @return {Server|Adapter} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.adapter = function(v){
|
||||
if (!arguments.length) return this._adapter;
|
||||
this._adapter = v;
|
||||
for (var i in this.nsps) {
|
||||
if (this.nsps.hasOwnProperty(i)) {
|
||||
this.nsps[i].initAdapter();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the allowed origins for requests.
|
||||
*
|
||||
* @param {String} origins
|
||||
* @return {Server|Adapter} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.origins = function(v){
|
||||
if (!arguments.length) return this._origins;
|
||||
|
||||
this._origins = v;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches socket.io to a server or port.
|
||||
*
|
||||
* @param {http.Server|Number} server or port
|
||||
* @param {Object} options passed to engine.io
|
||||
* @return {Server} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.listen =
|
||||
Server.prototype.attach = function(srv, opts){
|
||||
if ('function' == typeof srv) {
|
||||
var msg = 'You are trying to attach socket.io to an express ' +
|
||||
'request handler function. Please pass a http.Server instance.';
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
// handle a port as a string
|
||||
if (Number(srv) == srv) {
|
||||
srv = Number(srv);
|
||||
}
|
||||
|
||||
if ('number' == typeof srv) {
|
||||
debug('creating http server and binding to %d', srv);
|
||||
var port = srv;
|
||||
srv = http.Server(function(req, res){
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
});
|
||||
srv.listen(port);
|
||||
|
||||
}
|
||||
|
||||
// set engine.io path to `/socket.io`
|
||||
opts = opts || {};
|
||||
opts.path = opts.path || this.path();
|
||||
// set origins verification
|
||||
opts.allowRequest = opts.allowRequest || this.checkRequest.bind(this);
|
||||
|
||||
// initialize engine
|
||||
debug('creating engine.io instance with opts %j', opts);
|
||||
this.eio = engine.attach(srv, opts);
|
||||
|
||||
// attach static file serving
|
||||
if (this._serveClient) this.attachServe(srv);
|
||||
|
||||
// Export http server
|
||||
this.httpServer = srv;
|
||||
|
||||
// bind to engine events
|
||||
this.bind(this.eio);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches the static file serving.
|
||||
*
|
||||
* @param {Function|http.Server} http server
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.attachServe = function(srv){
|
||||
debug('attaching client serving req handler');
|
||||
var url = this._path + '/socket.io.js';
|
||||
var evs = srv.listeners('request').slice(0);
|
||||
var self = this;
|
||||
srv.removeAllListeners('request');
|
||||
srv.on('request', function(req, res) {
|
||||
if (0 === req.url.indexOf(url)) {
|
||||
self.serve(req, res);
|
||||
} else {
|
||||
for (var i = 0; i < evs.length; i++) {
|
||||
evs[i].call(srv, req, res);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a request serving `/socket.io.js`
|
||||
*
|
||||
* @param {http.Request} req
|
||||
* @param {http.Response} res
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.serve = function(req, res){
|
||||
var etag = req.headers['if-none-match'];
|
||||
if (etag) {
|
||||
if (clientVersion == etag) {
|
||||
debug('serve client 304');
|
||||
res.writeHead(304);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
debug('serve client source');
|
||||
res.setHeader('Content-Type', 'application/javascript');
|
||||
res.setHeader('ETag', clientVersion);
|
||||
res.writeHead(200);
|
||||
res.end(clientSource);
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds socket.io to an engine.io instance.
|
||||
*
|
||||
* @param {engine.Server} engine.io (or compatible) server
|
||||
* @return {Server} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.bind = function(engine){
|
||||
this.engine = engine;
|
||||
this.engine.on('connection', this.onconnection.bind(this));
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called with each incoming transport connection.
|
||||
*
|
||||
* @param {engine.Socket} socket
|
||||
* @return {Server} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.onconnection = function(conn){
|
||||
debug('incoming connection with id %s', conn.id);
|
||||
var client = new Client(this, conn);
|
||||
client.connect('/');
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Looks up a namespace.
|
||||
*
|
||||
* @param {String} nsp name
|
||||
* @param {Function} optional, nsp `connection` ev handler
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.of = function(name, fn){
|
||||
if (String(name)[0] !== '/') name = '/' + name;
|
||||
|
||||
var nsp = this.nsps[name];
|
||||
if (!nsp) {
|
||||
debug('initializing namespace %s', name);
|
||||
nsp = new Namespace(this, name);
|
||||
this.nsps[name] = nsp;
|
||||
}
|
||||
if (fn) nsp.on('connect', fn);
|
||||
return nsp;
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes server connection
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.close = function(){
|
||||
for (var id in this.nsps['/'].sockets) {
|
||||
if (this.nsps['/'].sockets.hasOwnProperty(id)) {
|
||||
this.nsps['/'].sockets[id].onclose();
|
||||
}
|
||||
}
|
||||
|
||||
this.engine.close();
|
||||
|
||||
if(this.httpServer){
|
||||
this.httpServer.close();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose main namespace (/).
|
||||
*/
|
||||
|
||||
['on', 'to', 'in', 'use', 'emit', 'send', 'write', 'clients', 'compress'].forEach(function(fn){
|
||||
Server.prototype[fn] = function(){
|
||||
var nsp = this.sockets[fn];
|
||||
return nsp.apply(this.sockets, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
Namespace.flags.forEach(function(flag){
|
||||
Server.prototype.__defineGetter__(flag, function(){
|
||||
this.sockets.flags = this.sockets.flags || {};
|
||||
this.sockets.flags[flag] = true;
|
||||
return this;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* BC with `io.listen`
|
||||
*/
|
||||
|
||||
Server.listen = Server;
|
||||
@@ -1,97 +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;
|
||||
this.enabled = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Log method.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Logger.prototype.log = function (type) {
|
||||
var index = levels.indexOf(type);
|
||||
|
||||
if (index > this.level || !this.enabled)
|
||||
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)));
|
||||
};
|
||||
});
|
||||
983
lib/manager.js
983
lib/manager.js
@@ -1,983 +0,0 @@
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var fs = require('fs')
|
||||
, url = require('url')
|
||||
, tty = require('tty')
|
||||
, util = require('./util')
|
||||
, store = require('./store')
|
||||
, client = require('socket.io-client')
|
||||
, transports = require('./transports')
|
||||
, Logger = require('./logger')
|
||||
, Socket = require('./socket')
|
||||
, MemoryStore = require('./stores/memory')
|
||||
, SocketNamespace = require('./namespace')
|
||||
, Static = require('./static')
|
||||
, EventEmitter = process.EventEmitter;
|
||||
|
||||
/**
|
||||
* Export the constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = Manager;
|
||||
|
||||
/**
|
||||
* Default transports.
|
||||
*/
|
||||
|
||||
var defaultTransports = exports.defaultTransports = [
|
||||
'websocket'
|
||||
, 'htmlfile'
|
||||
, 'xhr-polling'
|
||||
, 'jsonp-polling'
|
||||
];
|
||||
|
||||
/**
|
||||
* Inherited defaults.
|
||||
*/
|
||||
|
||||
var parent = module.parent.exports
|
||||
, protocol = parent.protocol;
|
||||
|
||||
/**
|
||||
* Manager constructor.
|
||||
*
|
||||
* @param {HTTPServer} server
|
||||
* @param {Object} options, optional
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Manager (server, options) {
|
||||
this.server = server;
|
||||
this.namespaces = {};
|
||||
this.sockets = this.of('');
|
||||
this.settings = {
|
||||
origins: '*:*'
|
||||
, log: true
|
||||
, store: new MemoryStore
|
||||
, logger: new Logger
|
||||
, static: new Static(this)
|
||||
, heartbeats: true
|
||||
, resource: '/socket.io'
|
||||
, transports: defaultTransports
|
||||
, authorization: false
|
||||
, blacklist: ['disconnect']
|
||||
, 'log level': 3
|
||||
, 'log colors': tty.isatty(process.stdout.fd)
|
||||
, 'close timeout': 60
|
||||
, 'heartbeat interval': 25
|
||||
, 'heartbeat timeout': 60
|
||||
, 'polling duration': 20
|
||||
, 'flash policy server': true
|
||||
, 'flash policy port': 10843
|
||||
, 'destroy upgrade': true
|
||||
, 'destroy buffer size': 10E7
|
||||
, 'browser client': true
|
||||
, 'browser client cache': true
|
||||
, 'browser client minification': false
|
||||
, 'browser client etag': false
|
||||
, 'browser client expires': 315360000
|
||||
, 'browser client gzip': false
|
||||
, 'browser client handler': false
|
||||
, 'client store expiration': 15
|
||||
, 'match origin protocol': false
|
||||
};
|
||||
|
||||
for (var i in options) {
|
||||
this.settings[i] = options[i];
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
// default error handler
|
||||
server.on('error', function(err) {
|
||||
self.log.warn('error raised: ' + err);
|
||||
});
|
||||
|
||||
this.initStore();
|
||||
|
||||
this.on('set:store', function() {
|
||||
self.initStore();
|
||||
});
|
||||
|
||||
// reset listeners
|
||||
this.oldListeners = server.listeners('request');
|
||||
server.removeAllListeners('request');
|
||||
|
||||
server.on('request', function (req, res) {
|
||||
self.handleRequest(req, res);
|
||||
});
|
||||
|
||||
server.on('upgrade', function (req, socket, head) {
|
||||
self.handleUpgrade(req, socket, head);
|
||||
});
|
||||
|
||||
server.on('close', function () {
|
||||
clearInterval(self.gc);
|
||||
});
|
||||
|
||||
server.once('listening', function () {
|
||||
self.gc = setInterval(self.garbageCollection.bind(self), 10000);
|
||||
});
|
||||
|
||||
for (var i in transports) {
|
||||
if (transports[i].init) {
|
||||
transports[i].init(this);
|
||||
}
|
||||
}
|
||||
|
||||
// forward-compatibility with 1.0
|
||||
var self = this;
|
||||
this.sockets.on('connection', function (conn) {
|
||||
self.emit('connection', conn);
|
||||
});
|
||||
|
||||
this.log.info('socket.io started');
|
||||
};
|
||||
|
||||
Manager.prototype.__proto__ = EventEmitter.prototype
|
||||
|
||||
/**
|
||||
* Store accessor shortcut.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Manager.prototype.__defineGetter__('store', function () {
|
||||
var store = this.get('store');
|
||||
store.manager = this;
|
||||
return store;
|
||||
});
|
||||
|
||||
/**
|
||||
* Logger accessor.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Manager.prototype.__defineGetter__('log', function () {
|
||||
var logger = this.get('logger');
|
||||
|
||||
logger.level = this.get('log level') || -1;
|
||||
logger.colors = this.get('log colors');
|
||||
logger.enabled = this.enabled('log');
|
||||
|
||||
return logger;
|
||||
});
|
||||
|
||||
/**
|
||||
* Static accessor.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Manager.prototype.__defineGetter__('static', function () {
|
||||
return this.get('static');
|
||||
});
|
||||
|
||||
/**
|
||||
* Get settings.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Manager.prototype.get = function (key) {
|
||||
return this.settings[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Set settings
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Manager.prototype.set = function (key, value) {
|
||||
if (arguments.length == 1) return this.get(key);
|
||||
this.settings[key] = value;
|
||||
this.emit('set:' + key, this.settings[key], key);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable a setting
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Manager.prototype.enable = function (key) {
|
||||
this.settings[key] = true;
|
||||
this.emit('set:' + key, this.settings[key], key);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Disable a setting
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Manager.prototype.disable = function (key) {
|
||||
this.settings[key] = false;
|
||||
this.emit('set:' + key, this.settings[key], key);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a setting is enabled
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Manager.prototype.enabled = function (key) {
|
||||
return !!this.settings[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a setting is disabled
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Manager.prototype.disabled = function (key) {
|
||||
return !this.settings[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Configure callbacks.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Manager.prototype.configure = function (env, fn) {
|
||||
if ('function' == typeof env) {
|
||||
env.call(this);
|
||||
} else if (env == (process.env.NODE_ENV || 'development')) {
|
||||
fn.call(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes everything related to the message dispatcher.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.initStore = function () {
|
||||
this.handshaken = {};
|
||||
this.connected = {};
|
||||
this.open = {};
|
||||
this.closed = {};
|
||||
this.rooms = {};
|
||||
this.roomClients = {};
|
||||
|
||||
var self = this;
|
||||
|
||||
this.store.subscribe('handshake', function (id, data) {
|
||||
self.onHandshake(id, data);
|
||||
});
|
||||
|
||||
this.store.subscribe('connect', function (id) {
|
||||
self.onConnect(id);
|
||||
});
|
||||
|
||||
this.store.subscribe('open', function (id) {
|
||||
self.onOpen(id);
|
||||
});
|
||||
|
||||
this.store.subscribe('join', function (id, room) {
|
||||
self.onJoin(id, room);
|
||||
});
|
||||
|
||||
this.store.subscribe('leave', function (id, room) {
|
||||
self.onLeave(id, room);
|
||||
});
|
||||
|
||||
this.store.subscribe('close', function (id) {
|
||||
self.onClose(id);
|
||||
});
|
||||
|
||||
this.store.subscribe('dispatch', function (room, packet, volatile, exceptions) {
|
||||
self.onDispatch(room, packet, volatile, exceptions);
|
||||
});
|
||||
|
||||
this.store.subscribe('disconnect', function (id) {
|
||||
self.onDisconnect(id);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a client handshakes.
|
||||
*
|
||||
* @param text
|
||||
*/
|
||||
|
||||
Manager.prototype.onHandshake = function (id, data) {
|
||||
this.handshaken[id] = data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a client connects (ie: transport first opens)
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.onConnect = function (id) {
|
||||
this.connected[id] = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a client opens a request in a different node.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.onOpen = function (id) {
|
||||
this.open[id] = true;
|
||||
|
||||
// if we were buffering messages for the client, clear them
|
||||
if (this.closed[id]) {
|
||||
var self = this;
|
||||
|
||||
this.store.unsubscribe('dispatch:' + id, function () {
|
||||
delete self.closed[id];
|
||||
});
|
||||
}
|
||||
|
||||
// clear the current transport
|
||||
if (this.transports[id]) {
|
||||
this.transports[id].discard();
|
||||
this.transports[id] = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a message is sent to a namespace and/or room.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.onDispatch = function (room, packet, volatile, exceptions) {
|
||||
if (this.rooms[room]) {
|
||||
for (var i = 0, l = this.rooms[room].length; i < l; i++) {
|
||||
var id = this.rooms[room][i];
|
||||
|
||||
if (!~exceptions.indexOf(id)) {
|
||||
if (this.transports[id] && this.transports[id].open) {
|
||||
this.transports[id].onDispatch(packet, volatile);
|
||||
} else if (!volatile) {
|
||||
this.onClientDispatch(id, packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a client joins a nsp / room.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.onJoin = function (id, name) {
|
||||
if (!this.roomClients[id]) {
|
||||
this.roomClients[id] = {};
|
||||
}
|
||||
|
||||
if (!this.rooms[name]) {
|
||||
this.rooms[name] = [];
|
||||
}
|
||||
|
||||
if (!~this.rooms[name].indexOf(id)) {
|
||||
this.rooms[name].push(id);
|
||||
this.roomClients[id][name] = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a client leaves a nsp / room.
|
||||
*
|
||||
* @param private
|
||||
*/
|
||||
|
||||
Manager.prototype.onLeave = function (id, room) {
|
||||
if (this.rooms[room]) {
|
||||
var index = this.rooms[room].indexOf(id);
|
||||
|
||||
if (index >= 0) {
|
||||
this.rooms[room].splice(index, 1);
|
||||
}
|
||||
|
||||
if (!this.rooms[room].length) {
|
||||
delete this.rooms[room];
|
||||
}
|
||||
delete this.roomClients[id][room];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a client closes a request in different node.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.onClose = function (id) {
|
||||
if (this.open[id]) {
|
||||
delete this.open[id];
|
||||
}
|
||||
|
||||
this.closed[id] = [];
|
||||
|
||||
var self = this;
|
||||
|
||||
this.store.subscribe('dispatch:' + id, function (packet, volatile) {
|
||||
if (!volatile) {
|
||||
self.onClientDispatch(id, packet);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatches a message for a closed client.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.onClientDispatch = function (id, packet) {
|
||||
if (this.closed[id]) {
|
||||
this.closed[id].push(packet);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Receives a message for a client.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.onClientMessage = function (id, packet) {
|
||||
if (this.namespaces[packet.endpoint]) {
|
||||
this.namespaces[packet.endpoint].handlePacket(id, packet);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fired when a client disconnects (not triggered).
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.onClientDisconnect = function (id, reason) {
|
||||
for (var name in this.namespaces) {
|
||||
this.namespaces[name].handleDisconnect(id, reason, typeof this.roomClients[id] !== 'undefined' &&
|
||||
typeof this.roomClients[id][name] !== 'undefined');
|
||||
}
|
||||
|
||||
this.onDisconnect(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a client disconnects.
|
||||
*
|
||||
* @param text
|
||||
*/
|
||||
|
||||
Manager.prototype.onDisconnect = function (id, local) {
|
||||
delete this.handshaken[id];
|
||||
|
||||
if (this.open[id]) {
|
||||
delete this.open[id];
|
||||
}
|
||||
|
||||
if (this.connected[id]) {
|
||||
delete this.connected[id];
|
||||
}
|
||||
|
||||
if (this.transports[id]) {
|
||||
this.transports[id].discard();
|
||||
delete this.transports[id];
|
||||
}
|
||||
|
||||
if (this.closed[id]) {
|
||||
delete this.closed[id];
|
||||
}
|
||||
|
||||
if (this.roomClients[id]) {
|
||||
for (var room in this.roomClients[id]) {
|
||||
this.onLeave(id, room);
|
||||
}
|
||||
delete this.roomClients[id]
|
||||
}
|
||||
|
||||
this.store.destroyClient(id, this.get('client store expiration'));
|
||||
|
||||
this.store.unsubscribe('dispatch:' + id);
|
||||
|
||||
if (local) {
|
||||
this.store.unsubscribe('message:' + id);
|
||||
this.store.unsubscribe('disconnect:' + id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles an HTTP request.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.handleRequest = function (req, res) {
|
||||
var data = this.checkRequest(req);
|
||||
|
||||
if (!data) {
|
||||
for (var i = 0, l = this.oldListeners.length; i < l; i++) {
|
||||
this.oldListeners[i].call(this.server, req, res);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.static || !data.transport && !data.protocol) {
|
||||
if (data.static && this.enabled('browser client')) {
|
||||
this.static.write(data.path, req, res);
|
||||
} else {
|
||||
res.writeHead(200);
|
||||
res.end('Welcome to socket.io.');
|
||||
|
||||
this.log.info('unhandled socket.io url');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.protocol != protocol) {
|
||||
res.writeHead(500);
|
||||
res.end('Protocol version not supported.');
|
||||
|
||||
this.log.info('client protocol version unsupported');
|
||||
} else {
|
||||
if (data.id) {
|
||||
this.handleHTTPRequest(data, req, res);
|
||||
} else {
|
||||
this.handleHandshake(data, req, res);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles an HTTP Upgrade.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.handleUpgrade = function (req, socket, head) {
|
||||
var data = this.checkRequest(req)
|
||||
, self = this;
|
||||
|
||||
if (!data) {
|
||||
if (this.enabled('destroy upgrade')) {
|
||||
socket.end();
|
||||
this.log.debug('destroying non-socket.io upgrade');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
req.head = head;
|
||||
this.handleClient(data, req);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a normal handshaken HTTP request (eg: long-polling)
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.handleHTTPRequest = function (data, req, res) {
|
||||
req.res = res;
|
||||
this.handleClient(data, req);
|
||||
};
|
||||
|
||||
/**
|
||||
* Intantiantes a new client.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.handleClient = function (data, req) {
|
||||
var socket = req.socket
|
||||
, store = this.store
|
||||
, self = this;
|
||||
|
||||
if (undefined != data.query.disconnect) {
|
||||
if (this.transports[data.id] && this.transports[data.id].open) {
|
||||
this.transports[data.id].onForcedDisconnect();
|
||||
} else {
|
||||
this.store.publish('disconnect-force:' + data.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!~this.get('transports').indexOf(data.transport)) {
|
||||
this.log.warn('unknown transport: "' + data.transport + '"');
|
||||
req.connection.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var transport = new transports[data.transport](this, data, req)
|
||||
, handshaken = this.handshaken[data.id];
|
||||
|
||||
if (transport.disconnected) {
|
||||
// failed during transport setup
|
||||
req.connection.end();
|
||||
return;
|
||||
}
|
||||
if (handshaken) {
|
||||
if (transport.open) {
|
||||
if (this.closed[data.id] && this.closed[data.id].length) {
|
||||
transport.payload(this.closed[data.id]);
|
||||
this.closed[data.id] = [];
|
||||
}
|
||||
|
||||
this.onOpen(data.id);
|
||||
this.store.publish('open', data.id);
|
||||
this.transports[data.id] = transport;
|
||||
}
|
||||
|
||||
if (!this.connected[data.id]) {
|
||||
this.onConnect(data.id);
|
||||
this.store.publish('connect', data.id);
|
||||
|
||||
// flag as used
|
||||
delete handshaken.issued;
|
||||
this.onHandshake(data.id, handshaken);
|
||||
this.store.publish('handshake', data.id, handshaken);
|
||||
|
||||
// initialize the socket for all namespaces
|
||||
for (var i in this.namespaces) {
|
||||
var socket = this.namespaces[i].socket(data.id, true);
|
||||
|
||||
// echo back connect packet and fire connection event
|
||||
if (i === '') {
|
||||
this.namespaces[i].handlePacket(data.id, { type: 'connect' });
|
||||
}
|
||||
}
|
||||
|
||||
this.store.subscribe('message:' + data.id, function (packet) {
|
||||
self.onClientMessage(data.id, packet);
|
||||
});
|
||||
|
||||
this.store.subscribe('disconnect:' + data.id, function (reason) {
|
||||
self.onClientDisconnect(data.id, reason);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (transport.open) {
|
||||
transport.error('client not handshaken', 'reconnect');
|
||||
}
|
||||
|
||||
transport.discard();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a session id.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.generateId = function () {
|
||||
return Math.abs(Math.random() * Math.random() * Date.now() | 0).toString()
|
||||
+ Math.abs(Math.random() * Math.random() * Date.now() | 0).toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a handshake request.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.handleHandshake = function (data, req, res) {
|
||||
var self = this
|
||||
, origin = req.headers.origin
|
||||
, headers = {
|
||||
'Content-Type': 'text/plain'
|
||||
};
|
||||
|
||||
function writeErr (status, message) {
|
||||
if (data.query.jsonp) {
|
||||
res.writeHead(200, { 'Content-Type': 'application/javascript' });
|
||||
res.end('io.j[' + data.query.jsonp + '](new Error("' + message + '"));');
|
||||
} else {
|
||||
res.writeHead(status, headers);
|
||||
res.end(message);
|
||||
}
|
||||
};
|
||||
|
||||
function error (err) {
|
||||
writeErr(500, 'handshake error');
|
||||
self.log.warn('handshake error ' + err);
|
||||
};
|
||||
|
||||
if (!this.verifyOrigin(req)) {
|
||||
writeErr(403, 'handshake bad origin');
|
||||
return;
|
||||
}
|
||||
|
||||
var handshakeData = this.handshakeData(data);
|
||||
|
||||
if (origin) {
|
||||
// https://developer.mozilla.org/En/HTTP_Access_Control
|
||||
headers['Access-Control-Allow-Origin'] = origin;
|
||||
headers['Access-Control-Allow-Credentials'] = 'true';
|
||||
}
|
||||
|
||||
this.authorize(handshakeData, function (err, authorized, newData) {
|
||||
if (err) return error(err);
|
||||
|
||||
if (authorized) {
|
||||
var id = self.generateId()
|
||||
, hs = [
|
||||
id
|
||||
, self.enabled('heartbeats') ? self.get('heartbeat timeout') || '' : ''
|
||||
, self.get('close timeout') || ''
|
||||
, self.transports(data).join(',')
|
||||
].join(':');
|
||||
|
||||
if (data.query.jsonp) {
|
||||
hs = 'io.j[' + data.query.jsonp + '](' + JSON.stringify(hs) + ');';
|
||||
res.writeHead(200, { 'Content-Type': 'application/javascript' });
|
||||
} else {
|
||||
res.writeHead(200, headers);
|
||||
}
|
||||
|
||||
res.end(hs);
|
||||
|
||||
self.onHandshake(id, newData || handshakeData);
|
||||
self.store.publish('handshake', id, newData || handshakeData);
|
||||
|
||||
self.log.info('handshake authorized', id);
|
||||
} else {
|
||||
writeErr(403, 'handshake unauthorized');
|
||||
self.log.info('handshake unauthorized');
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets normalized handshake data
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.handshakeData = function (data) {
|
||||
var connection = data.request.connection
|
||||
, connectionAddress
|
||||
, date = new Date;
|
||||
|
||||
if (connection.remoteAddress) {
|
||||
connectionAddress = {
|
||||
address: connection.remoteAddress
|
||||
, port: connection.remotePort
|
||||
};
|
||||
} else if (connection.socket && connection.socket.remoteAddress) {
|
||||
connectionAddress = {
|
||||
address: connection.socket.remoteAddress
|
||||
, port: connection.socket.remotePort
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
headers: data.headers
|
||||
, address: connectionAddress
|
||||
, time: date.toString()
|
||||
, query: data.query
|
||||
, url: data.request.url
|
||||
, xdomain: !!data.request.headers.origin
|
||||
, secure: data.request.connection.secure
|
||||
, issued: +date
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies the origin of a request.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.verifyOrigin = function (request) {
|
||||
var origin = request.headers.origin || request.headers.referer
|
||||
, origins = this.get('origins');
|
||||
|
||||
if (origin === 'null') origin = '*';
|
||||
|
||||
if (origins.indexOf('*:*') !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (origin) {
|
||||
try {
|
||||
var parts = url.parse(origin);
|
||||
parts.port = parts.port || 80;
|
||||
var ok =
|
||||
~origins.indexOf(parts.hostname + ':' + parts.port) ||
|
||||
~origins.indexOf(parts.hostname + ':*') ||
|
||||
~origins.indexOf('*:' + parts.port);
|
||||
if (!ok) this.log.warn('illegal origin: ' + origin);
|
||||
return ok;
|
||||
} catch (ex) {
|
||||
this.log.warn('error parsing origin');
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.log.warn('origin missing from handshake, yet required by config');
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles an incoming packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.handlePacket = function (sessid, packet) {
|
||||
this.of(packet.endpoint || '').handlePacket(sessid, packet);
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs authentication.
|
||||
*
|
||||
* @param Object client request data
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.authorize = function (data, fn) {
|
||||
if (this.get('authorization')) {
|
||||
var self = this;
|
||||
|
||||
this.get('authorization').call(this, data, function (err, authorized) {
|
||||
self.log.debug('client ' + authorized ? 'authorized' : 'unauthorized');
|
||||
fn(err, authorized);
|
||||
});
|
||||
} else {
|
||||
this.log.debug('client authorized');
|
||||
fn(null, true);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the transports adviced to the user.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.transports = function (data) {
|
||||
var transp = this.get('transports')
|
||||
, ret = [];
|
||||
|
||||
for (var i = 0, l = transp.length; i < l; i++) {
|
||||
var transport = transp[i];
|
||||
|
||||
if (transport) {
|
||||
if (!transport.checkClient || transport.checkClient(data)) {
|
||||
ret.push(transport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether a request is a socket.io one.
|
||||
*
|
||||
* @return {Object} a client request data object or `false`
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var regexp = /^\/([^\/]+)\/?([^\/]+)?\/?([^\/]+)?\/?$/
|
||||
|
||||
Manager.prototype.checkRequest = function (req) {
|
||||
var resource = this.get('resource');
|
||||
|
||||
var match;
|
||||
if (typeof resource === 'string') {
|
||||
match = req.url.substr(0, resource.length);
|
||||
if (match !== resource) match = null;
|
||||
} else {
|
||||
match = resource.exec(req.url);
|
||||
if (match) match = match[0];
|
||||
}
|
||||
|
||||
if (match) {
|
||||
var uri = url.parse(req.url.substr(match.length), true)
|
||||
, path = uri.pathname || ''
|
||||
, pieces = path.match(regexp);
|
||||
|
||||
// client request data
|
||||
var data = {
|
||||
query: uri.query || {}
|
||||
, headers: req.headers
|
||||
, request: req
|
||||
, path: path
|
||||
};
|
||||
|
||||
if (pieces) {
|
||||
data.protocol = Number(pieces[1]);
|
||||
data.transport = pieces[2];
|
||||
data.id = pieces[3];
|
||||
data.static = !!this.static.has(path);
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Declares a socket namespace
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Manager.prototype.of = function (nsp) {
|
||||
if (this.namespaces[nsp]) {
|
||||
return this.namespaces[nsp];
|
||||
}
|
||||
|
||||
return this.namespaces[nsp] = new SocketNamespace(this, nsp);
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform garbage collection on long living objects and properties that cannot
|
||||
* be removed automatically.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.garbageCollection = function () {
|
||||
// clean up unused handshakes
|
||||
var ids = Object.keys(this.handshaken)
|
||||
, i = ids.length
|
||||
, now = Date.now()
|
||||
, handshake;
|
||||
|
||||
while (i--) {
|
||||
handshake = this.handshaken[ids[i]];
|
||||
|
||||
if ('issued' in handshake && (now - handshake.issued) >= 3E4) {
|
||||
this.onDisconnect(ids[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
521
lib/namespace.js
521
lib/namespace.js
@@ -1,355 +1,272 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Socket = require('./socket')
|
||||
, EventEmitter = process.EventEmitter
|
||||
, parser = require('./parser')
|
||||
, util = require('./util');
|
||||
var Socket = require('./socket');
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var parser = require('socket.io-parser');
|
||||
var debug = require('debug')('socket.io:namespace');
|
||||
var hasBin = require('has-binary');
|
||||
|
||||
/**
|
||||
* Exports the constructor.
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
exports = module.exports = SocketNamespace;
|
||||
module.exports = exports = Namespace;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Blacklisted events.
|
||||
*/
|
||||
|
||||
exports.events = [
|
||||
'connect', // for symmetry with client
|
||||
'connection',
|
||||
'newListener'
|
||||
];
|
||||
|
||||
/**
|
||||
* Flags.
|
||||
*/
|
||||
|
||||
exports.flags = [
|
||||
'json',
|
||||
'volatile'
|
||||
];
|
||||
|
||||
/**
|
||||
* `EventEmitter#emit` reference.
|
||||
*/
|
||||
|
||||
var emit = Emitter.prototype.emit;
|
||||
|
||||
/**
|
||||
* Namespace constructor.
|
||||
*
|
||||
* @api public.
|
||||
* @param {Server} server instance
|
||||
* @param {Socket} name
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function SocketNamespace (mgr, name) {
|
||||
this.manager = mgr;
|
||||
this.name = name || '';
|
||||
function Namespace(server, name){
|
||||
this.name = name;
|
||||
this.server = server;
|
||||
this.sockets = {};
|
||||
this.auth = false;
|
||||
this.setFlags();
|
||||
};
|
||||
this.connected = {};
|
||||
this.fns = [];
|
||||
this.ids = 0;
|
||||
this.initAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherits from EventEmitter.
|
||||
* Inherits from `EventEmitter`.
|
||||
*/
|
||||
|
||||
SocketNamespace.prototype.__proto__ = EventEmitter.prototype;
|
||||
Namespace.prototype.__proto__ = Emitter.prototype;
|
||||
|
||||
/**
|
||||
* Copies emit since we override it.
|
||||
*
|
||||
* @api private
|
||||
* Apply flags from `Socket`.
|
||||
*/
|
||||
|
||||
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
|
||||
exports.flags.forEach(function(flag){
|
||||
Namespace.prototype.__defineGetter__(flag, function(){
|
||||
this.flags = this.flags || {};
|
||||
this.flags[flag] = true;
|
||||
return this;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Initializes the `Adapter` for this nsp.
|
||||
* Run upon changing adapter by `Server#adapter`
|
||||
* in addition to the constructor.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Namespace.prototype.initAdapter = function(){
|
||||
this.adapter = new (this.server.adapter())(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits to everyone (override).
|
||||
* Sets up namespace middleware.
|
||||
*
|
||||
* @return {Namespace} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
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 public
|
||||
*/
|
||||
|
||||
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;
|
||||
Namespace.prototype.use = function(fn){
|
||||
this.fns.push(fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a socket disconnects entirely.
|
||||
* Executes the middleware for an incoming client.
|
||||
*
|
||||
* @param {Socket} socket that will get added
|
||||
* @param {Function} last fn call in the middleware
|
||||
* @api private
|
||||
*/
|
||||
|
||||
SocketNamespace.prototype.handleDisconnect = function (sid, reason, raiseOnDisconnect) {
|
||||
if (this.sockets[sid] && this.sockets[sid].readable) {
|
||||
if (raiseOnDisconnect) this.sockets[sid].onDisconnect(reason);
|
||||
delete this.sockets[sid];
|
||||
}
|
||||
};
|
||||
Namespace.prototype.run = function(socket, fn){
|
||||
var fns = this.fns.slice(0);
|
||||
if (!fns.length) return fn(null);
|
||||
|
||||
/**
|
||||
* Performs authentication.
|
||||
*
|
||||
* @param Object client request data
|
||||
* @api private
|
||||
*/
|
||||
function run(i){
|
||||
fns[i](socket, function(err){
|
||||
// upon error, short-circuit
|
||||
if (err) return fn(err);
|
||||
|
||||
SocketNamespace.prototype.authorize = function (data, fn) {
|
||||
if (this.auth) {
|
||||
var self = this;
|
||||
// if no middleware left, summon callback
|
||||
if (!fns[i + 1]) return fn(null);
|
||||
|
||||
this.auth.call(this, data, function (err, authorized) {
|
||||
self.log.debug('client ' +
|
||||
(authorized ? '' : 'un') + 'authorized for ' + self.name);
|
||||
fn(err, authorized);
|
||||
// go on to next
|
||||
run(i + 1);
|
||||
});
|
||||
}
|
||||
|
||||
run(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Targets a room when emitting.
|
||||
*
|
||||
* @param {String} name
|
||||
* @return {Namespace} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Namespace.prototype.to =
|
||||
Namespace.prototype['in'] = function(name){
|
||||
this.rooms = this.rooms || [];
|
||||
if (!~this.rooms.indexOf(name)) this.rooms.push(name);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new client.
|
||||
*
|
||||
* @return {Socket}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Namespace.prototype.add = function(client, fn){
|
||||
debug('adding socket to nsp %s', this.name);
|
||||
var socket = new Socket(this, client);
|
||||
var self = this;
|
||||
this.run(socket, function(err){
|
||||
process.nextTick(function(){
|
||||
if ('open' == client.conn.readyState) {
|
||||
if (err) return socket.error(err.data || err.message);
|
||||
|
||||
// track socket
|
||||
self.sockets[socket.id] = socket;
|
||||
|
||||
// it's paramount that the internal `onconnect` logic
|
||||
// fires before user-set events to prevent state order
|
||||
// violations (such as a disconnection before the connection
|
||||
// logic is complete)
|
||||
socket.onconnect();
|
||||
if (fn) fn();
|
||||
|
||||
// fire user-set events
|
||||
self.emit('connect', socket);
|
||||
self.emit('connection', socket);
|
||||
} else {
|
||||
debug('next called after client was closed - ignoring socket');
|
||||
}
|
||||
});
|
||||
});
|
||||
return socket;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a client. Called by each `Socket`.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Namespace.prototype.remove = function(socket){
|
||||
if (this.sockets.hasOwnProperty(socket.id)) {
|
||||
delete this.sockets[socket.id];
|
||||
} else {
|
||||
this.log.debug('client authorized for ' + this.name);
|
||||
fn(null, true);
|
||||
debug('ignoring remove for %s', socket.id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits to all clients.
|
||||
*
|
||||
* @return {Namespace} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Namespace.prototype.emit = function(ev){
|
||||
if (~exports.events.indexOf(ev)) {
|
||||
emit.apply(this, arguments);
|
||||
} else {
|
||||
// set up packet object
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var parserType = parser.EVENT; // default
|
||||
if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary
|
||||
|
||||
var packet = { type: parserType, data: args };
|
||||
|
||||
if ('function' == typeof args[args.length - 1]) {
|
||||
throw new Error('Callbacks are not supported when broadcasting');
|
||||
}
|
||||
|
||||
this.adapter.broadcast(packet, {
|
||||
rooms: this.rooms,
|
||||
flags: this.flags
|
||||
});
|
||||
|
||||
delete this.rooms;
|
||||
delete this.flags;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a packet.
|
||||
* Sends a `message` event to all clients.
|
||||
*
|
||||
* @api private
|
||||
* @return {Namespace} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
SocketNamespace.prototype.handlePacket = function (sessid, packet) {
|
||||
var socket = this.socket(sessid)
|
||||
, dataAck = packet.ack == 'data'
|
||||
, manager = this.manager
|
||||
, 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 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':
|
||||
// check if the emitted event is not blacklisted
|
||||
if (-~manager.get('blacklist').indexOf(packet.name)) {
|
||||
this.log.debug('ignoring blacklisted event `' + packet.name + '`');
|
||||
} else {
|
||||
var params = [packet.name].concat(packet.args);
|
||||
|
||||
if (dataAck) {
|
||||
params.push(ack);
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
Namespace.prototype.send =
|
||||
Namespace.prototype.write = function(){
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift('message');
|
||||
this.emit.apply(this, args);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a list of clients.
|
||||
*
|
||||
* @return {Namespace} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Namespace.prototype.clients = function(fn){
|
||||
this.adapter.clients(this.rooms, fn);
|
||||
// delete rooms flag for scenario:
|
||||
// .in('room').clients() (GH-1978)
|
||||
delete this.rooms;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the compress flag.
|
||||
*
|
||||
* @param {Boolean} if `true`, compresses the sending data
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Namespace.prototype.compress = function(compress){
|
||||
this.flags = this.flags || {};
|
||||
this.flags.compress = compress;
|
||||
return this;
|
||||
};
|
||||
|
||||
249
lib/parser.js
249
lib/parser.js
@@ -1,249 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Packet types.
|
||||
*/
|
||||
|
||||
var packets = exports.packets = {
|
||||
'disconnect': 0
|
||||
, 'connect': 1
|
||||
, 'heartbeat': 2
|
||||
, 'message': 3
|
||||
, 'json': 4
|
||||
, 'event': 5
|
||||
, 'ack': 6
|
||||
, 'error': 7
|
||||
, 'noop': 8
|
||||
}
|
||||
, packetslist = Object.keys(packets);
|
||||
|
||||
/**
|
||||
* Errors reasons.
|
||||
*/
|
||||
|
||||
var reasons = exports.reasons = {
|
||||
'transport not supported': 0
|
||||
, 'client not handshaken': 1
|
||||
, 'unauthorized': 2
|
||||
}
|
||||
, reasonslist = Object.keys(reasons);
|
||||
|
||||
/**
|
||||
* Errors advice.
|
||||
*/
|
||||
|
||||
var advice = exports.advice = {
|
||||
'reconnect': 0
|
||||
}
|
||||
, advicelist = Object.keys(advice);
|
||||
|
||||
/**
|
||||
* Encodes a packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.encodePacket = function (packet) {
|
||||
var type = packets[packet.type]
|
||||
, id = packet.id || ''
|
||||
, endpoint = packet.endpoint || ''
|
||||
, ack = packet.ack
|
||||
, data = null;
|
||||
|
||||
switch (packet.type) {
|
||||
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 'ack':
|
||||
data = packet.ackId
|
||||
+ (packet.args && packet.args.length
|
||||
? '+' + JSON.stringify(packet.args) : '');
|
||||
break;
|
||||
|
||||
case 'connect':
|
||||
if (packet.qs)
|
||||
data = packet.qs;
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
var reason = packet.reason ? reasons[packet.reason] : ''
|
||||
, adv = packet.advice ? advice[packet.advice] : ''
|
||||
|
||||
if (reason !== '' || adv !== '')
|
||||
data = reason + (adv !== '' ? ('+' + adv) : '')
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// construct packet with required fragments
|
||||
var encoded = type + ':' + id + (ack == 'data' ? '+' : '') + ':' + endpoint;
|
||||
|
||||
// data fragment is optional
|
||||
if (data !== null && data !== undefined)
|
||||
encoded += ':' + data;
|
||||
|
||||
return encoded;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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]*)?/;
|
||||
|
||||
/**
|
||||
* Wrap the JSON.parse in a seperate function the crankshaft optimizer will
|
||||
* only punish this function for the usage for try catch
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function parse (data) {
|
||||
try { return JSON.parse(data) }
|
||||
catch (e) { return false }
|
||||
}
|
||||
|
||||
exports.decodePacket = function (data) {
|
||||
var pieces = data.match(regexp);
|
||||
|
||||
if (!pieces) return {};
|
||||
|
||||
var id = pieces[2] || ''
|
||||
, data = pieces[5] || ''
|
||||
, packet = {
|
||||
type: packetslist[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 'message':
|
||||
packet.data = data || '';
|
||||
break;
|
||||
|
||||
case 'event':
|
||||
pieces = parse(data);
|
||||
if (pieces) {
|
||||
packet.name = pieces.name;
|
||||
packet.args = pieces.args;
|
||||
}
|
||||
|
||||
packet.args = packet.args || [];
|
||||
break;
|
||||
|
||||
case 'json':
|
||||
packet.data = parse(data);
|
||||
break;
|
||||
|
||||
case 'connect':
|
||||
packet.qs = data || '';
|
||||
break;
|
||||
|
||||
case 'ack':
|
||||
pieces = data.match(/^([0-9]+)(\+)?(.*)/);
|
||||
if (pieces) {
|
||||
packet.ackId = pieces[1];
|
||||
packet.args = [];
|
||||
|
||||
if (pieces[3]) {
|
||||
packet.args = parse(pieces[3]) || [];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
pieces = data.split('+');
|
||||
packet.reason = reasonslist[pieces[0]] || '';
|
||||
packet.advice = advicelist[pieces[1]] || '';
|
||||
}
|
||||
|
||||
return packet;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes data payload. Detects multiple messages
|
||||
*
|
||||
* @return {Array} messages
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.decodePayload = function (data) {
|
||||
if (undefined == data || null == data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (data[0] == '\ufffd') {
|
||||
var ret = [];
|
||||
|
||||
for (var i = 1, length = ''; i < data.length; i++) {
|
||||
if (data[i] == '\ufffd') {
|
||||
ret.push(exports.decodePacket(data.substr(i + 1, length)));
|
||||
i += Number(length) + 1;
|
||||
length = '';
|
||||
} else {
|
||||
length += data[i];
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
} else {
|
||||
return [exports.decodePacket(data)];
|
||||
}
|
||||
};
|
||||
136
lib/socket.io.js
136
lib/socket.io.js
@@ -1,136 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var client = require('socket.io-client');
|
||||
|
||||
/**
|
||||
* Version.
|
||||
*/
|
||||
|
||||
exports.version = '0.9.2';
|
||||
|
||||
/**
|
||||
* Supported protocol version.
|
||||
*/
|
||||
|
||||
exports.protocol = 1;
|
||||
|
||||
/**
|
||||
* Client that we serve.
|
||||
*/
|
||||
|
||||
exports.clientVersion = client.version;
|
||||
|
||||
/**
|
||||
* Attaches a manager
|
||||
*
|
||||
* @param {HTTPServer/Number} a HTTP/S server or a port number to listen on.
|
||||
* @param {Object} opts to be passed to Manager and/or http server
|
||||
* @param {Function} callback if a port is supplied
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.listen = function (server, options, fn) {
|
||||
if ('function' == typeof options) {
|
||||
fn = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.Manager = require('./manager');
|
||||
|
||||
/**
|
||||
* Transport constructor.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.Transport = require('./transport');
|
||||
|
||||
/**
|
||||
* Socket constructor.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.Socket = require('./socket');
|
||||
|
||||
/**
|
||||
* Static constructor.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.Static = require('./static');
|
||||
|
||||
/**
|
||||
* Store constructor.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.Store = require('./store');
|
||||
|
||||
/**
|
||||
* Memory Store constructor.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.MemoryStore = require('./stores/memory');
|
||||
|
||||
/**
|
||||
* Redis Store constructor.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.RedisStore = require('./stores/redis');
|
||||
|
||||
/**
|
||||
* Parser.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.parser = require('./parser');
|
||||
678
lib/socket.js
678
lib/socket.js
@@ -1,362 +1,470 @@
|
||||
|
||||
/*!
|
||||
* 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 Emitter = require('events').EventEmitter;
|
||||
var parser = require('socket.io-parser');
|
||||
var url = require('url');
|
||||
var debug = require('debug')('socket.io:socket');
|
||||
var hasBin = require('has-binary');
|
||||
|
||||
/**
|
||||
* Export the constructor.
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
exports = module.exports = Socket;
|
||||
module.exports = exports = Socket;
|
||||
|
||||
/**
|
||||
* Default error event listener to prevent uncaught exceptions.
|
||||
*/
|
||||
|
||||
var defaultError = function () {};
|
||||
|
||||
/**
|
||||
* Socket constructor.
|
||||
* Blacklisted events.
|
||||
*
|
||||
* @param {Manager} manager instance
|
||||
* @param {String} session id
|
||||
* @param {Namespace} namespace the socket belongs to
|
||||
* @param {Boolean} whether the
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Socket (manager, id, nsp, readable) {
|
||||
this.id = id;
|
||||
this.namespace = nsp;
|
||||
this.manager = manager;
|
||||
this.disconnected = false;
|
||||
this.ackPackets = 0;
|
||||
exports.events = [
|
||||
'error',
|
||||
'connect',
|
||||
'disconnect',
|
||||
'newListener',
|
||||
'removeListener'
|
||||
];
|
||||
|
||||
/**
|
||||
* Flags.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var flags = [
|
||||
'json',
|
||||
'volatile',
|
||||
'broadcast'
|
||||
];
|
||||
|
||||
/**
|
||||
* `EventEmitter#emit` reference.
|
||||
*/
|
||||
|
||||
var emit = Emitter.prototype.emit;
|
||||
|
||||
/**
|
||||
* Interface to a `Client` for a given `Namespace`.
|
||||
*
|
||||
* @param {Namespace} nsp
|
||||
* @param {Client} client
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Socket(nsp, client){
|
||||
this.nsp = nsp;
|
||||
this.server = nsp.server;
|
||||
this.adapter = this.nsp.adapter;
|
||||
this.id = nsp.name + '#' + client.id;
|
||||
this.client = client;
|
||||
this.conn = client.conn;
|
||||
this.rooms = {};
|
||||
this.acks = {};
|
||||
this.setFlags();
|
||||
this.readable = readable;
|
||||
this.store = this.manager.store.client(this.id);
|
||||
this.on('error', defaultError);
|
||||
};
|
||||
this.connected = true;
|
||||
this.disconnected = false;
|
||||
this.handshake = this.buildHandshake();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherits from EventEmitter.
|
||||
* Inherits from `EventEmitter`.
|
||||
*/
|
||||
|
||||
Socket.prototype.__proto__ = EventEmitter.prototype;
|
||||
Socket.prototype.__proto__ = Emitter.prototype;
|
||||
|
||||
/**
|
||||
* Accessor shortcut for the handshake data
|
||||
*
|
||||
* @api private
|
||||
* Apply flags from `Socket`.
|
||||
*/
|
||||
|
||||
Socket.prototype.__defineGetter__('handshake', function () {
|
||||
return this.manager.handshaken[this.id];
|
||||
flags.forEach(function(flag){
|
||||
Socket.prototype.__defineGetter__(flag, function(){
|
||||
this.flags = this.flags || {};
|
||||
this.flags[flag] = true;
|
||||
return this;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Accessor shortcut for the transport type
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.__defineGetter__('transport', function () {
|
||||
return this.manager.transports[this.id].name;
|
||||
});
|
||||
|
||||
/**
|
||||
* Accessor shortcut for the logger.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.__defineGetter__('log', function () {
|
||||
return this.manager.log;
|
||||
});
|
||||
|
||||
/**
|
||||
* JSON message flag.
|
||||
* `request` engine.io shorcut.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.__defineGetter__('json', function () {
|
||||
this.flags.json = true;
|
||||
return this;
|
||||
Socket.prototype.__defineGetter__('request', function(){
|
||||
return this.conn.request;
|
||||
});
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Builds the `handshake` BC object
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.setFlags = function () {
|
||||
this.flags = {
|
||||
endpoint: this.namespace.name
|
||||
, room: ''
|
||||
Socket.prototype.buildHandshake = function(){
|
||||
return {
|
||||
headers: this.request.headers,
|
||||
time: (new Date) + '',
|
||||
address: this.conn.remoteAddress,
|
||||
xdomain: !!this.request.headers.origin,
|
||||
secure: !!this.request.connection.encrypted,
|
||||
issued: +(new Date),
|
||||
url: this.request.url,
|
||||
query: url.parse(this.request.url, true).query || {}
|
||||
};
|
||||
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.
|
||||
* Emits to this client.
|
||||
*
|
||||
* @return {Socket} self
|
||||
* @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.emit = function(ev){
|
||||
if (~exports.events.indexOf(ev)) {
|
||||
emit.apply(this, arguments);
|
||||
} else {
|
||||
packet.endpoint = this.flags.endpoint;
|
||||
packet = parser.encodePacket(packet);
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var packet = {};
|
||||
packet.type = hasBin(args) ? parser.BINARY_EVENT : parser.EVENT;
|
||||
packet.data = args;
|
||||
var flags = this.flags || {};
|
||||
|
||||
this.dispatch(packet, this.flags.volatile);
|
||||
}
|
||||
// access last argument to see if it's an ACK callback
|
||||
if ('function' == typeof args[args.length - 1]) {
|
||||
if (this._rooms || flags.broadcast) {
|
||||
throw new Error('Callbacks are not supported when broadcasting');
|
||||
}
|
||||
|
||||
this.setFlags();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatches a packet
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.dispatch = function (packet, volatile) {
|
||||
if (this.manager.transports[this.id] && this.manager.transports[this.id].open) {
|
||||
this.manager.transports[this.id].onDispatch(packet, volatile);
|
||||
} else {
|
||||
if (!volatile) {
|
||||
this.manager.onClientDispatch(this.id, packet, volatile);
|
||||
debug('emitting packet with ack id %d', this.nsp.ids);
|
||||
this.acks[this.nsp.ids] = args.pop();
|
||||
packet.id = this.nsp.ids++;
|
||||
}
|
||||
|
||||
this.manager.store.publish('dispatch:' + this.id, packet, volatile);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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();
|
||||
if (this._rooms || flags.broadcast) {
|
||||
this.adapter.broadcast(packet, {
|
||||
except: [this.id],
|
||||
rooms: this._rooms,
|
||||
flags: flags
|
||||
});
|
||||
} else {
|
||||
this.manager.onClientDisconnect(this.id);
|
||||
this.manager.store.publish('disconnect:' + this.id);
|
||||
// dispatch packet
|
||||
this.packet(packet, {
|
||||
volatile: flags.volatile,
|
||||
compress: flags.compress
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// reset flags
|
||||
delete this._rooms;
|
||||
delete this.flags;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a message.
|
||||
* Targets a room when broadcasting.
|
||||
*
|
||||
* @param {String} name
|
||||
* @return {Socket} self
|
||||
* @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);
|
||||
Socket.prototype.to =
|
||||
Socket.prototype.in = function(name){
|
||||
this._rooms = this._rooms || [];
|
||||
if (!~this._rooms.indexOf(name)) this._rooms.push(name);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Original emit function.
|
||||
* Sends a `message` event.
|
||||
*
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.send =
|
||||
Socket.prototype.write = function(){
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift('message');
|
||||
this.emit.apply(this, args);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a packet.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @param {Object} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.packet = function(packet, opts){
|
||||
packet.nsp = this.nsp.name;
|
||||
opts = opts || {};
|
||||
opts.compress = false !== opts.compress;
|
||||
this.client.packet(packet, opts);
|
||||
};
|
||||
|
||||
/**
|
||||
* Joins a room.
|
||||
*
|
||||
* @param {String} room
|
||||
* @param {Function} optional, callback
|
||||
* @return {Socket} self
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.join = function(room, fn){
|
||||
debug('joining room %s', room);
|
||||
var self = this;
|
||||
if (this.rooms.hasOwnProperty(room)) {
|
||||
fn && fn(null);
|
||||
return this;
|
||||
}
|
||||
this.adapter.add(this.id, room, function(err){
|
||||
if (err) return fn && fn(err);
|
||||
debug('joined room %s', room);
|
||||
self.rooms[room] = room;
|
||||
fn && fn(null);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Leaves a room.
|
||||
*
|
||||
* @param {String} room
|
||||
* @param {Function} optional, callback
|
||||
* @return {Socket} self
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.leave = function(room, fn){
|
||||
debug('leave room %s', room);
|
||||
var self = this;
|
||||
this.adapter.del(this.id, room, function(err){
|
||||
if (err) return fn && fn(err);
|
||||
debug('left room %s', room);
|
||||
delete self.rooms[room];
|
||||
fn && fn(null);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Leave all rooms.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.$emit = EventEmitter.prototype.emit;
|
||||
Socket.prototype.leaveAll = function(){
|
||||
this.adapter.delAll(this.id);
|
||||
this.rooms = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit override for custom events.
|
||||
* Called by `Namespace` upon succesful
|
||||
* middleware execution (ie: authorization).
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onconnect = function(){
|
||||
debug('socket connected - writing packet');
|
||||
this.nsp.connected[this.id] = this;
|
||||
this.join(this.id);
|
||||
this.packet({ type: parser.CONNECT });
|
||||
};
|
||||
|
||||
/**
|
||||
* Called with each packet. Called by `Client`.
|
||||
*
|
||||
* @param {Object} packet
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onpacket = function(packet){
|
||||
debug('got packet %j', packet);
|
||||
switch (packet.type) {
|
||||
case parser.EVENT:
|
||||
this.onevent(packet);
|
||||
break;
|
||||
|
||||
case parser.BINARY_EVENT:
|
||||
this.onevent(packet);
|
||||
break;
|
||||
|
||||
case parser.ACK:
|
||||
this.onack(packet);
|
||||
break;
|
||||
|
||||
case parser.BINARY_ACK:
|
||||
this.onack(packet);
|
||||
break;
|
||||
|
||||
case parser.DISCONNECT:
|
||||
this.ondisconnect();
|
||||
break;
|
||||
|
||||
case parser.ERROR:
|
||||
this.emit('error', packet.data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon event packet.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onevent = function(packet){
|
||||
var args = packet.data || [];
|
||||
debug('emitting event %j', args);
|
||||
|
||||
if (null != packet.id) {
|
||||
debug('attaching ack callback to event');
|
||||
args.push(this.ack(packet.id));
|
||||
}
|
||||
|
||||
emit.apply(this, args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Produces an ack callback to emit with an event.
|
||||
*
|
||||
* @param {Number} packet id
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.ack = function(id){
|
||||
var self = this;
|
||||
var sent = false;
|
||||
return function(){
|
||||
// prevent double callbacks
|
||||
if (sent) return;
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
debug('sending ack %j', args);
|
||||
|
||||
var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK;
|
||||
self.packet({
|
||||
id: id,
|
||||
type: type,
|
||||
data: args
|
||||
});
|
||||
|
||||
sent = true;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon ack packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onack = function(packet){
|
||||
var ack = this.acks[packet.id];
|
||||
if ('function' == typeof ack) {
|
||||
debug('calling ack %s with %j', packet.id, packet.data);
|
||||
ack.apply(this, packet.data);
|
||||
delete this.acks[packet.id];
|
||||
} else {
|
||||
debug('bad ack %s', packet.id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon client disconnect packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.ondisconnect = function(){
|
||||
debug('got disconnect packet');
|
||||
this.onclose('client namespace disconnect');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a client error.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onerror = function(err){
|
||||
if (this.listeners('error').length) {
|
||||
this.emit('error', err);
|
||||
} else {
|
||||
console.error('Missing error handler on `socket`.');
|
||||
console.error(err.stack);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon closing. Called by `Client`.
|
||||
*
|
||||
* @param {String} reason
|
||||
* @param {Error} optional error object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onclose = function(reason){
|
||||
if (!this.connected) return this;
|
||||
debug('closing socket - reason %s', reason);
|
||||
this.leaveAll();
|
||||
this.nsp.remove(this);
|
||||
this.client.remove(this);
|
||||
this.connected = false;
|
||||
this.disconnected = true;
|
||||
delete this.nsp.connected[this.id];
|
||||
this.emit('disconnect', reason);
|
||||
};
|
||||
|
||||
/**
|
||||
* Produces an `error` packet.
|
||||
*
|
||||
* @param {Object} error object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.error = function(err){
|
||||
this.packet({ type: parser.ERROR, data: err });
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnects this client.
|
||||
*
|
||||
* @param {Boolean} if `true`, closes the underlying connection
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.emit = function (ev) {
|
||||
if (ev == 'newListener') {
|
||||
return this.$emit.apply(this, arguments);
|
||||
Socket.prototype.disconnect = function(close){
|
||||
if (!this.connected) return this;
|
||||
if (close) {
|
||||
this.client.disconnect();
|
||||
} else {
|
||||
this.packet({ type: parser.DISCONNECT });
|
||||
this.onclose('server namespace disconnect');
|
||||
}
|
||||
|
||||
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);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the compress flag.
|
||||
*
|
||||
* @param {Boolean} if `true`, compresses the sending data
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.compress = function(compress){
|
||||
this.flags = this.flags || {};
|
||||
this.flags.compress = compress;
|
||||
return this;
|
||||
};
|
||||
|
||||
395
lib/static.js
395
lib/static.js
@@ -1,395 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var client = require('socket.io-client')
|
||||
, cp = require('child_process')
|
||||
, fs = require('fs')
|
||||
, util = require('./util');
|
||||
|
||||
/**
|
||||
* File type details.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var mime = {
|
||||
js: {
|
||||
type: 'application/javascript'
|
||||
, encoding: 'utf8'
|
||||
, gzip: true
|
||||
}
|
||||
, swf: {
|
||||
type: 'application/x-shockwave-flash'
|
||||
, encoding: 'binary'
|
||||
, gzip: false
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Regexp for matching custom transport patterns. Users can configure their own
|
||||
* socket.io bundle based on the url structure. Different transport names are
|
||||
* concatinated using the `+` char. /socket.io/socket.io+websocket.js should
|
||||
* create a bundle that only contains support for the websocket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var bundle = /\+((?:\+)?[\w\-]+)*(?:\.v\d+\.\d+\.\d+)?(?:\.js)$/
|
||||
, versioning = /\.v\d+\.\d+\.\d+(?:\.js)$/;
|
||||
|
||||
/**
|
||||
* Export the constructor
|
||||
*/
|
||||
|
||||
exports = module.exports = Static;
|
||||
|
||||
/**
|
||||
* Static constructor
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Static (manager) {
|
||||
this.manager = manager;
|
||||
this.cache = {};
|
||||
this.paths = {};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Static by adding default file paths.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Static.prototype.init = function () {
|
||||
/**
|
||||
* Generates a unique id based the supplied transports array
|
||||
*
|
||||
* @param {Array} transports The array with transport types
|
||||
* @api private
|
||||
*/
|
||||
function id (transports) {
|
||||
var id = transports.join('').split('').map(function (char) {
|
||||
return ('' + char.charCodeAt(0)).split('').pop();
|
||||
}).reduce(function (char, id) {
|
||||
return char +id;
|
||||
});
|
||||
|
||||
return client.version + ':' + id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a socket.io-client file based on the supplied transports.
|
||||
*
|
||||
* @param {Array} transports The array with transport types
|
||||
* @param {Function} callback Callback for the static.write
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function build (transports, callback) {
|
||||
client.builder(transports, {
|
||||
minify: self.manager.enabled('browser client minification')
|
||||
}, function (err, content) {
|
||||
callback(err, content ? new Buffer(content) : null, id(transports));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
// add our default static files
|
||||
this.add('/static/flashsocket/WebSocketMain.swf', {
|
||||
file: client.dist + '/WebSocketMain.swf'
|
||||
});
|
||||
|
||||
this.add('/static/flashsocket/WebSocketMainInsecure.swf', {
|
||||
file: client.dist + '/WebSocketMainInsecure.swf'
|
||||
});
|
||||
|
||||
// generates dedicated build based on the available transports
|
||||
this.add('/socket.io.js', function (path, callback) {
|
||||
build(self.manager.get('transports'), callback);
|
||||
});
|
||||
|
||||
this.add('/socket.io.v', { mime: mime.js }, function (path, callback) {
|
||||
build(self.manager.get('transports'), callback);
|
||||
});
|
||||
|
||||
// allow custom builds based on url paths
|
||||
this.add('/socket.io+', { mime: mime.js }, function (path, callback) {
|
||||
var available = self.manager.get('transports')
|
||||
, matches = path.match(bundle)
|
||||
, transports = [];
|
||||
|
||||
if (!matches) return callback('No valid transports');
|
||||
|
||||
// make sure they valid transports
|
||||
matches[0].split('.')[0].split('+').slice(1).forEach(function (transport) {
|
||||
if (!!~available.indexOf(transport)) {
|
||||
transports.push(transport);
|
||||
}
|
||||
});
|
||||
|
||||
if (!transports.length) return callback('No valid transports');
|
||||
build(transports, callback);
|
||||
});
|
||||
|
||||
// clear cache when transports change
|
||||
this.manager.on('set:transports', function (key, value) {
|
||||
delete self.cache['/socket.io.js'];
|
||||
Object.keys(self.cache).forEach(function (key) {
|
||||
if (bundle.test(key)) {
|
||||
delete self.cache[key];
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Gzip compress buffers.
|
||||
*
|
||||
* @param {Buffer} data The buffer that needs gzip compression
|
||||
* @param {Function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Static.prototype.gzip = function (data, callback) {
|
||||
var gzip = cp.spawn('gzip', ['-9', '-c', '-f', '-n'])
|
||||
, encoding = Buffer.isBuffer(data) ? 'binary' : 'utf8'
|
||||
, buffer = []
|
||||
, err;
|
||||
|
||||
gzip.stdout.on('data', function (data) {
|
||||
buffer.push(data);
|
||||
});
|
||||
|
||||
gzip.stderr.on('data', function (data) {
|
||||
err = data +'';
|
||||
buffer.length = 0;
|
||||
});
|
||||
|
||||
gzip.on('exit', function () {
|
||||
if (err) return callback(err);
|
||||
|
||||
var size = 0
|
||||
, index = 0
|
||||
, i = buffer.length
|
||||
, content;
|
||||
|
||||
while (i--) {
|
||||
size += buffer[i].length;
|
||||
}
|
||||
|
||||
content = new Buffer(size);
|
||||
i = buffer.length;
|
||||
|
||||
buffer.forEach(function (buffer) {
|
||||
var length = buffer.length;
|
||||
|
||||
buffer.copy(content, index, 0, length);
|
||||
index += length;
|
||||
});
|
||||
|
||||
buffer.length = 0;
|
||||
callback(null, content);
|
||||
});
|
||||
|
||||
gzip.stdin.end(data, encoding);
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the path a static file?
|
||||
*
|
||||
* @param {String} path The path that needs to be checked
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Static.prototype.has = function (path) {
|
||||
// fast case
|
||||
if (this.paths[path]) return this.paths[path];
|
||||
|
||||
var keys = Object.keys(this.paths)
|
||||
, i = keys.length;
|
||||
|
||||
while (i--) {
|
||||
if (-~path.indexOf(keys[i])) return this.paths[keys[i]];
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add new paths new paths that can be served using the static provider.
|
||||
*
|
||||
* @param {String} path The path to respond to
|
||||
* @param {Options} options Options for writing out the response
|
||||
* @param {Function} [callback] Optional callback if no options.file is
|
||||
* supplied this would be called instead.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Static.prototype.add = function (path, options, callback) {
|
||||
var extension = /(?:\.(\w{1,4}))$/.exec(path);
|
||||
|
||||
if (!callback && typeof options == 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
options.mime = options.mime || (extension ? mime[extension[1]] : false);
|
||||
|
||||
if (callback) options.callback = callback;
|
||||
if (!(options.file || options.callback) || !options.mime) return false;
|
||||
|
||||
this.paths[path] = options;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a static response.
|
||||
*
|
||||
* @param {String} path The path for the static content
|
||||
* @param {HTTPRequest} req The request object
|
||||
* @param {HTTPResponse} res The response object
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Static.prototype.write = function (path, req, res) {
|
||||
/**
|
||||
* Write a response without throwing errors because can throw error if the
|
||||
* response is no longer writable etc.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function write (status, headers, content, encoding) {
|
||||
try {
|
||||
res.writeHead(status, headers || undefined);
|
||||
|
||||
// only write content if it's not a HEAD request and we actually have
|
||||
// some content to write (304's doesn't have content).
|
||||
res.end(
|
||||
req.method !== 'HEAD' && content ? content : ''
|
||||
, encoding || undefined
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Answers requests depending on the request properties and the reply object.
|
||||
*
|
||||
* @param {Object} reply The details and content to reply the response with
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function answer (reply) {
|
||||
var cached = req.headers['if-none-match'] === reply.etag;
|
||||
if (cached && self.manager.enabled('browser client etag')) {
|
||||
return write(304);
|
||||
}
|
||||
|
||||
var accept = req.headers['accept-encoding'] || ''
|
||||
, gzip = !!~accept.toLowerCase().indexOf('gzip')
|
||||
, mime = reply.mime
|
||||
, versioned = reply.versioned
|
||||
, headers = {
|
||||
'Content-Type': mime.type
|
||||
};
|
||||
|
||||
// check if we can add a etag
|
||||
if (self.manager.enabled('browser client etag') && reply.etag && !versioned) {
|
||||
headers['Etag'] = reply.etag;
|
||||
}
|
||||
|
||||
// see if we need to set Expire headers because the path is versioned
|
||||
if (versioned) {
|
||||
var expires = self.manager.get('browser client expires');
|
||||
headers['Cache-Control'] = 'private, x-gzip-ok="", max-age=' + expires;
|
||||
headers['Date'] = new Date().toUTCString();
|
||||
headers['Expires'] = new Date(Date.now() + (expires * 1000)).toUTCString();
|
||||
}
|
||||
|
||||
if (gzip && reply.gzip) {
|
||||
headers['Content-Length'] = reply.gzip.length;
|
||||
headers['Content-Encoding'] = 'gzip';
|
||||
headers['Vary'] = 'Accept-Encoding';
|
||||
write(200, headers, reply.gzip.content, mime.encoding);
|
||||
} else {
|
||||
headers['Content-Length'] = reply.length;
|
||||
write(200, headers, reply.content, mime.encoding);
|
||||
}
|
||||
|
||||
self.manager.log.debug('served static content ' + path);
|
||||
}
|
||||
|
||||
var self = this
|
||||
, details;
|
||||
|
||||
// most common case first
|
||||
if (this.manager.enabled('browser client cache') && this.cache[path]) {
|
||||
return answer(this.cache[path]);
|
||||
} else if (this.manager.get('browser client handler')) {
|
||||
return this.manager.get('browser client handler').call(this, req, res);
|
||||
} else if ((details = this.has(path))) {
|
||||
/**
|
||||
* A small helper function that will let us deal with fs and dynamic files
|
||||
*
|
||||
* @param {Object} err Optional error
|
||||
* @param {Buffer} content The data
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function ready (err, content, etag) {
|
||||
if (err) {
|
||||
self.manager.log.warn('Unable to serve file. ' + (err.message || err));
|
||||
return write(500, null, 'Error serving static ' + path);
|
||||
}
|
||||
|
||||
// store the result in the cache
|
||||
var reply = self.cache[path] = {
|
||||
content: content
|
||||
, length: content.length
|
||||
, mime: details.mime
|
||||
, etag: etag || client.version
|
||||
, versioned: versioning.test(path)
|
||||
};
|
||||
|
||||
// check if gzip is enabled
|
||||
if (details.mime.gzip && self.manager.enabled('browser client gzip')) {
|
||||
self.gzip(content, function (err, content) {
|
||||
if (!err) {
|
||||
reply.gzip = {
|
||||
content: content
|
||||
, length: content.length
|
||||
}
|
||||
}
|
||||
|
||||
answer(reply);
|
||||
});
|
||||
} else {
|
||||
answer(reply);
|
||||
}
|
||||
}
|
||||
|
||||
if (details.file) {
|
||||
fs.readFile(details.file, ready);
|
||||
} else if(details.callback) {
|
||||
details.callback.call(this, path, ready);
|
||||
} else {
|
||||
write(404, null, 'File handle not found');
|
||||
}
|
||||
} else {
|
||||
write(404, null, 'File not found');
|
||||
}
|
||||
};
|
||||
98
lib/store.js
98
lib/store.js
@@ -1,98 +0,0 @@
|
||||
|
||||
/*!
|
||||
* 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
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Store (options) {
|
||||
this.options = options;
|
||||
this.clients = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherit from EventEmitter.
|
||||
*/
|
||||
|
||||
Store.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Initializes a client store
|
||||
*
|
||||
* @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
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Store.prototype.destroyClient = function (id, expiration) {
|
||||
if (this.clients[id]) {
|
||||
this.clients[id].destroy(expiration);
|
||||
delete this.clients[id];
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys the store
|
||||
*
|
||||
* @param {Number} number of seconds to expire client data
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Store.prototype.destroy = function (clientExpiration) {
|
||||
var keys = Object.keys(this.clients)
|
||||
, count = keys.length;
|
||||
|
||||
for (var i = 0, l = count; i < l; i++) {
|
||||
this.destroyClient(keys[i], clientExpiration);
|
||||
}
|
||||
|
||||
this.clients = {};
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Client.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Store.Client = function (store, id) {
|
||||
this.store = store;
|
||||
this.id = id;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -1,269 +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')
|
||||
, RedisClient = redis.RedisClient;
|
||||
|
||||
// initialize a pubsub client and a regular client
|
||||
if (opts.redisPub instanceof RedisClient) {
|
||||
this.pub = opts.redisPub;
|
||||
} else {
|
||||
opts.redisPub || (opts.redisPub = {});
|
||||
this.pub = redis.createClient(opts.redisPub.port, opts.redisPub.host, opts.redisPub);
|
||||
}
|
||||
if (opts.redisSub instanceof RedisClient) {
|
||||
this.sub = opts.redisSub;
|
||||
} else {
|
||||
opts.redisSub || (opts.redisSub = {});
|
||||
this.sub = redis.createClient(opts.redisSub.port, opts.redisSub.host, opts.redisSub);
|
||||
}
|
||||
if (opts.redisClient instanceof RedisClient) {
|
||||
this.cmd = opts.redisClient;
|
||||
} else {
|
||||
opts.redisClient || (opts.redisClient = {});
|
||||
this.cmd = redis.createClient(opts.redisClient.port, opts.redisClient.host, opts.redisClient);
|
||||
}
|
||||
|
||||
Store.call(this, opts);
|
||||
|
||||
this.sub.setMaxListeners(0);
|
||||
this.setMaxListeners(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Store.
|
||||
*/
|
||||
|
||||
Redis.prototype.__proto__ = Store.prototype;
|
||||
|
||||
/**
|
||||
* Publishes a message.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Redis.prototype.publish = function (name) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
this.pub.publish(name, this.pack({ nodeId: this.nodeId, args: args }));
|
||||
this.emit.apply(this, ['publish', name].concat(args));
|
||||
};
|
||||
|
||||
/**
|
||||
* Subscribes to a channel
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Redis.prototype.subscribe = function (name, consumer, fn) {
|
||||
this.sub.subscribe(name);
|
||||
|
||||
if (consumer || fn) {
|
||||
var self = this;
|
||||
|
||||
self.sub.on('subscribe', function subscribe (ch) {
|
||||
if (name == ch) {
|
||||
function message (ch, msg) {
|
||||
if (name == ch) {
|
||||
msg = self.unpack(msg);
|
||||
|
||||
// we check that the message consumed wasnt emitted by this node
|
||||
if (self.nodeId != msg.nodeId) {
|
||||
consumer.apply(null, msg.args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.sub.on('message', message);
|
||||
|
||||
self.on('unsubscribe', function unsubscribe (ch) {
|
||||
if (name == ch) {
|
||||
self.sub.removeListener('message', message);
|
||||
self.removeListener('unsubscribe', unsubscribe);
|
||||
}
|
||||
});
|
||||
|
||||
self.sub.removeListener('subscribe', subscribe);
|
||||
|
||||
fn && fn();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.emit('subscribe', name, consumer, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unsubscribes
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Redis.prototype.unsubscribe = function (name, fn) {
|
||||
this.sub.unsubscribe(name);
|
||||
|
||||
if (fn) {
|
||||
var client = this.sub;
|
||||
|
||||
client.on('unsubscribe', function unsubscribe (ch) {
|
||||
if (name == ch) {
|
||||
fn();
|
||||
client.removeListener('unsubscribe', unsubscribe);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.emit('unsubscribe', name, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys the store
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Redis.prototype.destroy = function () {
|
||||
Store.prototype.destroy.call(this);
|
||||
|
||||
this.pub.end();
|
||||
this.sub.end();
|
||||
this.cmd.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* Client constructor
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Client (store, id) {
|
||||
Store.Client.call(this, store, id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Store.Client
|
||||
*/
|
||||
|
||||
Client.prototype.__proto__ = Store.Client;
|
||||
|
||||
/**
|
||||
* Redis hash get
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.get = function (key, fn) {
|
||||
this.store.cmd.hget(this.id, key, fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Redis hash set
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.set = function (key, value, fn) {
|
||||
this.store.cmd.hset(this.id, key, value, fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Redis hash del
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.del = function (key, fn) {
|
||||
this.store.cmd.hdel(this.id, key, fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Redis hash has
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.has = function (key, fn) {
|
||||
this.store.cmd.hexists(this.id, key, function (err, has) {
|
||||
if (err) return fn(err);
|
||||
fn(null, !!has);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys client
|
||||
*
|
||||
* @param {Number} number of seconds to expire data
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.destroy = function (expiration) {
|
||||
if ('number' != typeof expiration) {
|
||||
this.store.cmd.del(this.id);
|
||||
} else {
|
||||
this.store.cmd.expire(this.id, expiration);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
534
lib/transport.js
534
lib/transport.js
@@ -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');
|
||||
}
|
||||
};
|
||||
@@ -1,106 +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
|
||||
*/
|
||||
|
||||
|
||||
FlashSocket.init = function (manager) {
|
||||
var server;
|
||||
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 (~transports.indexOf('flashsocket')) {
|
||||
if (server) {
|
||||
if (server.port === value) return;
|
||||
// destroy the server and rebuild it on a new port
|
||||
try {
|
||||
server.close();
|
||||
}
|
||||
catch (e) { /* ignore exception. could e.g. be that the server isn't started yet */ }
|
||||
}
|
||||
create();
|
||||
}
|
||||
});
|
||||
|
||||
// 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();
|
||||
}
|
||||
};
|
||||
@@ -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; charset=UTF-8'
|
||||
, '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);
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -1,118 +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;
|
||||
|
||||
if (Buffer.byteLength(buffer) >= self.manager.get('destroy buffer size')) {
|
||||
buffer = '';
|
||||
req.connection.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
req.on('end', function () {
|
||||
res.writeHead(200, headers);
|
||||
res.end('1');
|
||||
|
||||
self.onData(self.postEncoded ? qs.parse(buffer).d : buffer);
|
||||
});
|
||||
|
||||
// prevent memory leaks for uncompleted requests
|
||||
req.on('close', function () {
|
||||
buffer = '';
|
||||
});
|
||||
|
||||
if (origin) {
|
||||
// https://developer.mozilla.org/En/HTTP_Access_Control
|
||||
headers['Access-Control-Allow-Origin'] = origin;
|
||||
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));
|
||||
};
|
||||
@@ -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')
|
||||
};
|
||||
@@ -1,96 +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;
|
||||
|
||||
/**
|
||||
* Handles incoming data.
|
||||
* Due to a bug in \n handling by browsers, we expect a JSONified string.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
JSONPPolling.prototype.onData = function (data) {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch (e) {
|
||||
this.error('parse', 'reconnect');
|
||||
return;
|
||||
}
|
||||
|
||||
HTTPPolling.prototype.onData.call(this, data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs the write.
|
||||
*
|
||||
* @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);
|
||||
};
|
||||
@@ -1,36 +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 transport
|
||||
, version = req.headers['sec-websocket-version'];
|
||||
if (typeof version !== 'undefined' && typeof protocolVersions[version] !== 'undefined') {
|
||||
transport = new protocolVersions[version](mng, data, req);
|
||||
}
|
||||
else transport = new protocolVersions['default'](mng, data, req);
|
||||
if (typeof this.name !== 'undefined') transport.name = this.name;
|
||||
return transport;
|
||||
};
|
||||
@@ -1,360 +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';
|
||||
|
||||
/**
|
||||
* Websocket draft version
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
WebSocket.prototype.protocolVersion = 'hixie-76';
|
||||
|
||||
/**
|
||||
* Called when the socket connects.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.onSocketConnect = function () {
|
||||
var self = this;
|
||||
|
||||
this.socket.setNoDelay(true);
|
||||
|
||||
this.buffer = true;
|
||||
this.buffered = [];
|
||||
|
||||
if (this.req.headers.upgrade !== 'WebSocket') {
|
||||
this.log.warn(this.name + ' connection invalid');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var origin = this.req.headers['origin']
|
||||
, location = ((this.manager.settings['match origin protocol'] ?
|
||||
origin.match(/^https/) : 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;
|
||||
};
|
||||
@@ -1,622 +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')
|
||||
, url = require('url')
|
||||
, parser = require('../../parser')
|
||||
, util = require('../../util');
|
||||
|
||||
/**
|
||||
* Export the constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = WebSocket;
|
||||
exports.Parser = Parser;
|
||||
|
||||
/**
|
||||
* HTTP interface constructor. Interface compatible with all transports that
|
||||
* depend on request-response cycles.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function WebSocket (mng, data, req) {
|
||||
// parser
|
||||
var self = this;
|
||||
|
||||
this.manager = mng;
|
||||
this.parser = new Parser();
|
||||
this.parser.on('data', function (packet) {
|
||||
self.onMessage(parser.decodePacket(packet));
|
||||
});
|
||||
this.parser.on('ping', function () {
|
||||
// version 8 ping => pong
|
||||
try {
|
||||
self.socket.write('\u008a\u0000');
|
||||
}
|
||||
catch (e) {
|
||||
self.end();
|
||||
return;
|
||||
}
|
||||
});
|
||||
this.parser.on('close', function () {
|
||||
self.end();
|
||||
});
|
||||
this.parser.on('error', function (reason) {
|
||||
self.log.warn(self.name + ' parser error: ' + reason);
|
||||
self.end();
|
||||
});
|
||||
|
||||
Transport.call(this, mng, data, req);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Transport.
|
||||
*/
|
||||
|
||||
WebSocket.prototype.__proto__ = Transport.prototype;
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
WebSocket.prototype.name = 'websocket';
|
||||
|
||||
/**
|
||||
* Websocket draft version
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
WebSocket.prototype.protocolVersion = '07-12';
|
||||
|
||||
/**
|
||||
* Called when the socket connects.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.onSocketConnect = function () {
|
||||
var self = this;
|
||||
|
||||
if (typeof this.req.headers.upgrade === 'undefined' ||
|
||||
this.req.headers.upgrade.toLowerCase() !== 'websocket') {
|
||||
this.log.warn(this.name + ' connection invalid');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var origin = this.req.headers['sec-websocket-origin']
|
||||
, location = ((this.manager.settings['match origin protocol'] ?
|
||||
origin.match(/^https/) : this.socket.encrypted) ?
|
||||
'wss' : 'ws')
|
||||
+ '://' + this.req.headers.host + this.req.url;
|
||||
|
||||
if (!this.verifyOrigin(origin)) {
|
||||
this.log.warn(this.name + ' connection invalid: origin mismatch');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.req.headers['sec-websocket-key']) {
|
||||
this.log.warn(this.name + ' connection invalid: received no key');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// calc key
|
||||
var key = this.req.headers['sec-websocket-key'];
|
||||
var shasum = crypto.createHash('sha1');
|
||||
shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||
key = shasum.digest('base64');
|
||||
|
||||
var headers = [
|
||||
'HTTP/1.1 101 Switching Protocols'
|
||||
, 'Upgrade: websocket'
|
||||
, 'Connection: Upgrade'
|
||||
, 'Sec-WebSocket-Accept: ' + key
|
||||
];
|
||||
|
||||
try {
|
||||
this.socket.write(headers.concat('', '').join('\r\n'));
|
||||
this.socket.setTimeout(0);
|
||||
this.socket.setNoDelay(true);
|
||||
} catch (e) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket.on('data', function (data) {
|
||||
self.parser.add(data);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies the origin of a request.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.verifyOrigin = function (origin) {
|
||||
var origins = this.manager.get('origins');
|
||||
|
||||
if (origin === 'null') origin = '*';
|
||||
|
||||
if (origins.indexOf('*:*') !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (origin) {
|
||||
try {
|
||||
var parts = url.parse(origin);
|
||||
parts.port = parts.port || 80;
|
||||
var ok =
|
||||
~origins.indexOf(parts.hostname + ':' + parts.port) ||
|
||||
~origins.indexOf(parts.hostname + ':*') ||
|
||||
~origins.indexOf('*:' + parts.port);
|
||||
if (!ok) this.log.warn('illegal origin: ' + origin);
|
||||
return ok;
|
||||
} catch (ex) {
|
||||
this.log.warn('error parsing origin');
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.log.warn('origin missing from websocket call, yet required by config');
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes to the socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.write = function (data) {
|
||||
if (this.open) {
|
||||
var buf = this.frame(0x81, data);
|
||||
try {
|
||||
this.socket.write(buf, 'binary');
|
||||
}
|
||||
catch (e) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
this.log.debug(this.name + ' writing', data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a payload.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.payload = function (msgs) {
|
||||
for (var i = 0, l = msgs.length; i < l; i++) {
|
||||
this.write(msgs[i]);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Frame server-to-client output as a text packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.frame = function (opcode, str) {
|
||||
var dataBuffer = new Buffer(str)
|
||||
, dataLength = dataBuffer.length
|
||||
, startOffset = 2
|
||||
, secondByte = dataLength;
|
||||
if (dataLength > 65536) {
|
||||
startOffset = 10;
|
||||
secondByte = 127;
|
||||
}
|
||||
else if (dataLength > 125) {
|
||||
startOffset = 4;
|
||||
secondByte = 126;
|
||||
}
|
||||
var outputBuffer = new Buffer(dataLength + startOffset);
|
||||
outputBuffer[0] = opcode;
|
||||
outputBuffer[1] = secondByte;
|
||||
dataBuffer.copy(outputBuffer, startOffset);
|
||||
switch (secondByte) {
|
||||
case 126:
|
||||
outputBuffer[2] = dataLength >>> 8;
|
||||
outputBuffer[3] = dataLength % 256;
|
||||
break;
|
||||
case 127:
|
||||
var l = dataLength;
|
||||
for (var i = 1; i <= 8; ++i) {
|
||||
outputBuffer[startOffset - i] = l & 0xff;
|
||||
l >>>= 8;
|
||||
}
|
||||
}
|
||||
return outputBuffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.doClose = function () {
|
||||
this.socket.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* WebSocket parser
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Parser () {
|
||||
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));
|
||||
});
|
||||
}
|
||||
},
|
||||
// binary
|
||||
'2': function(data) {
|
||||
var finish = function(mask, data) {
|
||||
if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list
|
||||
self.currentMessage.push(self.unmask(mask, data, true));
|
||||
if (self.state.lastFragment) {
|
||||
self.emit('binary', self.concatBuffers(self.currentMessage));
|
||||
self.currentMessage = '';
|
||||
}
|
||||
self.endPacket();
|
||||
}
|
||||
|
||||
var expectData = function(length) {
|
||||
if (self.state.masked) {
|
||||
self.expect('Mask', 4, function(data) {
|
||||
var mask = data;
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(mask, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(null, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// decode length
|
||||
var firstLength = data[1] & 0x7f;
|
||||
if (firstLength < 126) {
|
||||
expectData(firstLength);
|
||||
}
|
||||
else if (firstLength == 126) {
|
||||
self.expect('Length', 2, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
else if (firstLength == 127) {
|
||||
self.expect('Length', 8, function(data) {
|
||||
if (util.unpack(data.slice(0, 4)) != 0) {
|
||||
self.error('packets with length spanning more than 32 bit is currently not supported');
|
||||
return;
|
||||
}
|
||||
var lengthBytes = data.slice(4); // note: cap to 32 bit length
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
},
|
||||
// close
|
||||
'8': function(data) {
|
||||
self.emit('close');
|
||||
self.reset();
|
||||
},
|
||||
// ping
|
||||
'9': function(data) {
|
||||
if (self.state.lastFragment == false) {
|
||||
self.error('fragmented ping is not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
var finish = function(mask, data) {
|
||||
self.emit('ping', self.unmask(mask, data));
|
||||
self.endPacket();
|
||||
}
|
||||
|
||||
var expectData = function(length) {
|
||||
if (self.state.masked) {
|
||||
self.expect('Mask', 4, function(data) {
|
||||
var mask = data;
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(mask, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(null, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// decode length
|
||||
var firstLength = data[1] & 0x7f;
|
||||
if (firstLength == 0) {
|
||||
finish(null, null);
|
||||
}
|
||||
else if (firstLength < 126) {
|
||||
expectData(firstLength);
|
||||
}
|
||||
else if (firstLength == 126) {
|
||||
self.expect('Length', 2, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
else if (firstLength == 127) {
|
||||
self.expect('Length', 8, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.expect('Opcode', 2, this.processPacket);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from EventEmitter.
|
||||
*/
|
||||
|
||||
Parser.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Add new data to the parser.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Parser.prototype.add = function(data) {
|
||||
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 must be empty');
|
||||
}
|
||||
this.state.lastFragment = (data[0] & 0x80) == 0x80;
|
||||
this.state.masked = (data[1] & 0x80) == 0x80;
|
||||
var opcode = data[0] & 0xf;
|
||||
if (opcode == 0) {
|
||||
// continuation frame
|
||||
this.state.opcode = this.state.activeFragmentedOperation;
|
||||
if (!(this.state.opcode == 1 || this.state.opcode == 2)) {
|
||||
this.error('continuation frame cannot follow current opcode')
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.state.opcode = opcode;
|
||||
if (this.state.lastFragment === false) {
|
||||
this.state.activeFragmentedOperation = opcode;
|
||||
}
|
||||
}
|
||||
var handler = this.opcodeHandlers[this.state.opcode];
|
||||
if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode);
|
||||
else handler(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endprocessing a packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.endPacket = function() {
|
||||
this.expectOffset = 0;
|
||||
this.expectBuffer = null;
|
||||
this.expectHandler = null;
|
||||
if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) {
|
||||
// end current fragmented operation
|
||||
this.state.activeFragmentedOperation = null;
|
||||
}
|
||||
this.state.lastFragment = false;
|
||||
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
|
||||
this.state.masked = false;
|
||||
this.expect('Opcode', 2, this.processPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the parser state.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.reset = function() {
|
||||
this.state = {
|
||||
activeFragmentedOperation: null,
|
||||
lastFragment: false,
|
||||
masked: false,
|
||||
opcode: 0
|
||||
};
|
||||
this.expectOffset = 0;
|
||||
this.expectBuffer = null;
|
||||
this.expectHandler = null;
|
||||
this.overflow = null;
|
||||
this.currentMessage = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmask received data.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.unmask = function (mask, buf, binary) {
|
||||
if (mask != null) {
|
||||
for (var i = 0, ll = buf.length; i < ll; i++) {
|
||||
buf[i] ^= mask[i % 4];
|
||||
}
|
||||
}
|
||||
if (binary) return buf;
|
||||
return buf != null ? buf.toString('utf8') : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenates a list of buffers.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.concatBuffers = function(buffers) {
|
||||
var length = 0;
|
||||
for (var i = 0, l = buffers.length; i < l; ++i) {
|
||||
length += buffers[i].length;
|
||||
}
|
||||
var mergedBuffer = new Buffer(length);
|
||||
var offset = 0;
|
||||
for (var i = 0, l = buffers.length; i < l; ++i) {
|
||||
buffers[i].copy(mergedBuffer, offset);
|
||||
offset += buffers[i].length;
|
||||
}
|
||||
return mergedBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.error = function (reason) {
|
||||
this.reset();
|
||||
this.emit('error', reason);
|
||||
return this;
|
||||
};
|
||||
@@ -1,623 +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')
|
||||
, url = require('url')
|
||||
, parser = require('../../parser')
|
||||
, util = require('../../util');
|
||||
|
||||
/**
|
||||
* Export the constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = WebSocket;
|
||||
exports.Parser = Parser;
|
||||
|
||||
/**
|
||||
* HTTP interface constructor. Interface compatible with all transports that
|
||||
* depend on request-response cycles.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function WebSocket (mng, data, req) {
|
||||
// parser
|
||||
var self = this;
|
||||
|
||||
this.manager = mng;
|
||||
this.parser = new Parser();
|
||||
this.parser.on('data', function (packet) {
|
||||
self.onMessage(parser.decodePacket(packet));
|
||||
});
|
||||
this.parser.on('ping', function () {
|
||||
// version 8 ping => pong
|
||||
try {
|
||||
self.socket.write('\u008a\u0000');
|
||||
}
|
||||
catch (e) {
|
||||
self.end();
|
||||
return;
|
||||
}
|
||||
});
|
||||
this.parser.on('close', function () {
|
||||
self.end();
|
||||
});
|
||||
this.parser.on('error', function (reason) {
|
||||
self.log.warn(self.name + ' parser error: ' + reason);
|
||||
self.end();
|
||||
});
|
||||
|
||||
Transport.call(this, mng, data, req);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Transport.
|
||||
*/
|
||||
|
||||
WebSocket.prototype.__proto__ = Transport.prototype;
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
WebSocket.prototype.name = 'websocket';
|
||||
|
||||
/**
|
||||
* Websocket draft version
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
WebSocket.prototype.protocolVersion = '16';
|
||||
|
||||
/**
|
||||
* Called when the socket connects.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.onSocketConnect = function () {
|
||||
var self = this;
|
||||
|
||||
if (typeof this.req.headers.upgrade === 'undefined' ||
|
||||
this.req.headers.upgrade.toLowerCase() !== 'websocket') {
|
||||
this.log.warn(this.name + ' connection invalid');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var origin = this.req.headers['origin']
|
||||
, location = ((this.manager.settings['match origin protocol'] ?
|
||||
origin.match(/^https/) : this.socket.encrypted) ?
|
||||
'wss' : 'ws')
|
||||
+ '://' + this.req.headers.host + this.req.url;
|
||||
|
||||
if (!this.verifyOrigin(origin)) {
|
||||
this.log.warn(this.name + ' connection invalid: origin mismatch');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.req.headers['sec-websocket-key']) {
|
||||
this.log.warn(this.name + ' connection invalid: received no key');
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// calc key
|
||||
var key = this.req.headers['sec-websocket-key'];
|
||||
var shasum = crypto.createHash('sha1');
|
||||
shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||
key = shasum.digest('base64');
|
||||
|
||||
var headers = [
|
||||
'HTTP/1.1 101 Switching Protocols'
|
||||
, 'Upgrade: websocket'
|
||||
, 'Connection: Upgrade'
|
||||
, 'Sec-WebSocket-Accept: ' + key
|
||||
];
|
||||
|
||||
try {
|
||||
this.socket.write(headers.concat('', '').join('\r\n'));
|
||||
this.socket.setTimeout(0);
|
||||
this.socket.setNoDelay(true);
|
||||
} catch (e) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket.on('data', function (data) {
|
||||
self.parser.add(data);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies the origin of a request.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.verifyOrigin = function (origin) {
|
||||
var origins = this.manager.get('origins');
|
||||
|
||||
if (origin === 'null') origin = '*';
|
||||
|
||||
if (origins.indexOf('*:*') !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (origin) {
|
||||
try {
|
||||
var parts = url.parse(origin);
|
||||
parts.port = parts.port || 80;
|
||||
var ok =
|
||||
~origins.indexOf(parts.hostname + ':' + parts.port) ||
|
||||
~origins.indexOf(parts.hostname + ':*') ||
|
||||
~origins.indexOf('*:' + parts.port);
|
||||
if (!ok) this.log.warn('illegal origin: ' + origin);
|
||||
return ok;
|
||||
} catch (ex) {
|
||||
this.log.warn('error parsing origin');
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.log.warn('origin missing from websocket call, yet required by config');
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes to the socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.write = function (data) {
|
||||
if (this.open) {
|
||||
var buf = this.frame(0x81, data);
|
||||
try {
|
||||
this.socket.write(buf, 'binary');
|
||||
}
|
||||
catch (e) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
this.log.debug(this.name + ' writing', data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a payload.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.payload = function (msgs) {
|
||||
for (var i = 0, l = msgs.length; i < l; i++) {
|
||||
this.write(msgs[i]);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Frame server-to-client output as a text packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.frame = function (opcode, str) {
|
||||
var dataBuffer = new Buffer(str)
|
||||
, dataLength = dataBuffer.length
|
||||
, startOffset = 2
|
||||
, secondByte = dataLength;
|
||||
if (dataLength > 65536) {
|
||||
startOffset = 10;
|
||||
secondByte = 127;
|
||||
}
|
||||
else if (dataLength > 125) {
|
||||
startOffset = 4;
|
||||
secondByte = 126;
|
||||
}
|
||||
var outputBuffer = new Buffer(dataLength + startOffset);
|
||||
outputBuffer[0] = opcode;
|
||||
outputBuffer[1] = secondByte;
|
||||
dataBuffer.copy(outputBuffer, startOffset);
|
||||
switch (secondByte) {
|
||||
case 126:
|
||||
outputBuffer[2] = dataLength >>> 8;
|
||||
outputBuffer[3] = dataLength % 256;
|
||||
break;
|
||||
case 127:
|
||||
var l = dataLength;
|
||||
for (var i = 1; i <= 8; ++i) {
|
||||
outputBuffer[startOffset - i] = l & 0xff;
|
||||
l >>>= 8;
|
||||
}
|
||||
}
|
||||
return outputBuffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WebSocket.prototype.doClose = function () {
|
||||
this.socket.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* WebSocket parser
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Parser () {
|
||||
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));
|
||||
});
|
||||
}
|
||||
},
|
||||
// binary
|
||||
'2': function(data) {
|
||||
var finish = function(mask, data) {
|
||||
if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list
|
||||
self.currentMessage.push(self.unmask(mask, data, true));
|
||||
if (self.state.lastFragment) {
|
||||
self.emit('binary', self.concatBuffers(self.currentMessage));
|
||||
self.currentMessage = '';
|
||||
}
|
||||
self.endPacket();
|
||||
}
|
||||
|
||||
var expectData = function(length) {
|
||||
if (self.state.masked) {
|
||||
self.expect('Mask', 4, function(data) {
|
||||
var mask = data;
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(mask, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(null, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// decode length
|
||||
var firstLength = data[1] & 0x7f;
|
||||
if (firstLength < 126) {
|
||||
expectData(firstLength);
|
||||
}
|
||||
else if (firstLength == 126) {
|
||||
self.expect('Length', 2, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
else if (firstLength == 127) {
|
||||
self.expect('Length', 8, function(data) {
|
||||
if (util.unpack(data.slice(0, 4)) != 0) {
|
||||
self.error('packets with length spanning more than 32 bit is currently not supported');
|
||||
return;
|
||||
}
|
||||
var lengthBytes = data.slice(4); // note: cap to 32 bit length
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
},
|
||||
// close
|
||||
'8': function(data) {
|
||||
self.emit('close');
|
||||
self.reset();
|
||||
},
|
||||
// ping
|
||||
'9': function(data) {
|
||||
if (self.state.lastFragment == false) {
|
||||
self.error('fragmented ping is not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
var finish = function(mask, data) {
|
||||
self.emit('ping', self.unmask(mask, data));
|
||||
self.endPacket();
|
||||
}
|
||||
|
||||
var expectData = function(length) {
|
||||
if (self.state.masked) {
|
||||
self.expect('Mask', 4, function(data) {
|
||||
var mask = data;
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(mask, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.expect('Data', length, function(data) {
|
||||
finish(null, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// decode length
|
||||
var firstLength = data[1] & 0x7f;
|
||||
if (firstLength == 0) {
|
||||
finish(null, null);
|
||||
}
|
||||
else if (firstLength < 126) {
|
||||
expectData(firstLength);
|
||||
}
|
||||
else if (firstLength == 126) {
|
||||
self.expect('Length', 2, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
else if (firstLength == 127) {
|
||||
self.expect('Length', 8, function(data) {
|
||||
expectData(util.unpack(data));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.expect('Opcode', 2, this.processPacket);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from EventEmitter.
|
||||
*/
|
||||
|
||||
Parser.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Add new data to the parser.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Parser.prototype.add = function(data) {
|
||||
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 must be empty');
|
||||
return;
|
||||
}
|
||||
this.state.lastFragment = (data[0] & 0x80) == 0x80;
|
||||
this.state.masked = (data[1] & 0x80) == 0x80;
|
||||
var opcode = data[0] & 0xf;
|
||||
if (opcode == 0) {
|
||||
// continuation frame
|
||||
this.state.opcode = this.state.activeFragmentedOperation;
|
||||
if (!(this.state.opcode == 1 || this.state.opcode == 2)) {
|
||||
this.error('continuation frame cannot follow current opcode')
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.state.opcode = opcode;
|
||||
if (this.state.lastFragment === false) {
|
||||
this.state.activeFragmentedOperation = opcode;
|
||||
}
|
||||
}
|
||||
var handler = this.opcodeHandlers[this.state.opcode];
|
||||
if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode);
|
||||
else handler(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endprocessing a packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.endPacket = function() {
|
||||
this.expectOffset = 0;
|
||||
this.expectBuffer = null;
|
||||
this.expectHandler = null;
|
||||
if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) {
|
||||
// end current fragmented operation
|
||||
this.state.activeFragmentedOperation = null;
|
||||
}
|
||||
this.state.lastFragment = false;
|
||||
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
|
||||
this.state.masked = false;
|
||||
this.expect('Opcode', 2, this.processPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the parser state.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.reset = function() {
|
||||
this.state = {
|
||||
activeFragmentedOperation: null,
|
||||
lastFragment: false,
|
||||
masked: false,
|
||||
opcode: 0
|
||||
};
|
||||
this.expectOffset = 0;
|
||||
this.expectBuffer = null;
|
||||
this.expectHandler = null;
|
||||
this.overflow = null;
|
||||
this.currentMessage = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmask received data.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.unmask = function (mask, buf, binary) {
|
||||
if (mask != null) {
|
||||
for (var i = 0, ll = buf.length; i < ll; i++) {
|
||||
buf[i] ^= mask[i % 4];
|
||||
}
|
||||
}
|
||||
if (binary) return buf;
|
||||
return buf != null ? buf.toString('utf8') : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenates a list of buffers.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.concatBuffers = function(buffers) {
|
||||
var length = 0;
|
||||
for (var i = 0, l = buffers.length; i < l; ++i) {
|
||||
length += buffers[i].length;
|
||||
}
|
||||
var mergedBuffer = new Buffer(length);
|
||||
var offset = 0;
|
||||
for (var i = 0, l = buffers.length; i < l; ++i) {
|
||||
buffers[i].copy(mergedBuffer, offset);
|
||||
offset += buffers[i].length;
|
||||
}
|
||||
return mergedBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Parser.prototype.error = function (reason) {
|
||||
this.reset();
|
||||
this.emit('error', reason);
|
||||
return this;
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
/**
|
||||
* Export websocket versions.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
7: require('./hybi-07-12'),
|
||||
8: require('./hybi-07-12'),
|
||||
13: require('./hybi-16'),
|
||||
default: require('./default')
|
||||
};
|
||||
@@ -1,69 +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'] = origin;
|
||||
headers['Access-Control-Allow-Credentials'] = 'true';
|
||||
}
|
||||
|
||||
this.response.writeHead(200, headers);
|
||||
this.response.write(data);
|
||||
this.log.debug(this.name + ' writing', data);
|
||||
};
|
||||
50
lib/util.js
50
lib/util.js
@@ -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;
|
||||
}
|
||||
|
||||
86
package.json
86
package.json
@@ -1,35 +1,57 @@
|
||||
{
|
||||
"name": "socket.io"
|
||||
, "version": "0.9.2"
|
||||
, "description": "Real-time apps made cross-browser & easy with a WebSocket-like API"
|
||||
, "homepage": "http://socket.io"
|
||||
, "keywords": ["websocket", "socket", "realtime", "socket.io", "comet", "ajax"]
|
||||
, "author": "Guillermo Rauch <guillermo@learnboost.com>"
|
||||
, "contributors": [
|
||||
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
|
||||
, { "name": "Arnout Kazemier", "email": "info@3rd-eden.com" }
|
||||
, { "name": "Vladimir Dronnikov", "email": "dronnikov@gmail.com" }
|
||||
, { "name": "Einar Otto Stangvik", "email": "einaros@gmail.com" }
|
||||
]
|
||||
, "repository":{
|
||||
"type": "git"
|
||||
, "url": "https://github.com/LearnBoost/socket.io.git"
|
||||
}
|
||||
, "dependencies": {
|
||||
"socket.io-client": "0.9.2"
|
||||
, "policyfile": "0.0.4"
|
||||
, "redis": "0.6.7"
|
||||
}
|
||||
, "devDependencies": {
|
||||
"expresso": "0.9.2"
|
||||
, "should": "0.0.4"
|
||||
, "benchmark": "0.2.2"
|
||||
, "microtime": "0.1.3-1"
|
||||
, "colors": "0.5.1"
|
||||
}
|
||||
, "main": "index"
|
||||
, "engines": { "node": ">= 0.4.0" }
|
||||
, "scripts": {
|
||||
"test": "make test"
|
||||
"name": "socket.io",
|
||||
"version": "1.4.0",
|
||||
"description": "node.js realtime framework server",
|
||||
"keywords": [
|
||||
"realtime",
|
||||
"framework",
|
||||
"websocket",
|
||||
"tcp",
|
||||
"events",
|
||||
"socket",
|
||||
"io"
|
||||
],
|
||||
"main": "./lib/index",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Automattic/socket.io"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha --reporter dot --slow 200ms --bail"
|
||||
},
|
||||
"dependencies": {
|
||||
"engine.io": "1.6.5",
|
||||
"socket.io-parser": "2.2.6",
|
||||
"socket.io-client": "1.4.0",
|
||||
"socket.io-adapter": "0.4.0",
|
||||
"has-binary": "0.1.7",
|
||||
"debug": "2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"expect.js": "0.3.1",
|
||||
"istanbul": "0.2.3",
|
||||
"mocha": "2.3.4",
|
||||
"superagent": "0.17.0",
|
||||
"supertest": "0.8.2",
|
||||
"zuul-ngrok": "3.1.0"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Guillermo Rauch",
|
||||
"email": "rauchg@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Arnout Kazemier",
|
||||
"email": "info@3rd-eden.com"
|
||||
},
|
||||
{
|
||||
"name": "Vladimir Dronnikov",
|
||||
"email": "dronnikov@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Einar Otto Stangvik",
|
||||
"email": "einaros@gmail.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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]);
|
||||
@@ -1,617 +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('util');
|
||||
|
||||
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 opt = {};
|
||||
var agent = null;
|
||||
switch (getUrlScheme(url)) {
|
||||
case 'ws':
|
||||
var u = urllib.parse(url);
|
||||
agent = new http.Agent({
|
||||
host: u.hostname,
|
||||
port: u.port || 80
|
||||
});
|
||||
opt.agent = agent;
|
||||
opt.host = u.hostname;
|
||||
opt.port = u.port || 80;
|
||||
opt.path = (u.pathname || '/') + (u.search || '');
|
||||
opt.headers = httpHeaders;
|
||||
break;
|
||||
|
||||
case 'ws+unix':
|
||||
var sockPath = url.substring('ws+unix://'.length, url.length);
|
||||
var u = urllib.parse(url);
|
||||
agent = new http.Agent({
|
||||
host: 'localhost',
|
||||
port: sockPath
|
||||
});
|
||||
opt.agent = agent;
|
||||
opt.host = 'localhost';
|
||||
opt.path = sockPath;
|
||||
opt.headers = httpHeaders;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error('Invalid URL scheme \'' + urlScheme + '\' specified.');
|
||||
}
|
||||
|
||||
var httpReq = http.request(opt, function() { });
|
||||
var upgradeHandler = (function() {
|
||||
var data = undefined;
|
||||
|
||||
return function(req, s, head) {
|
||||
req.socket.setNoDelay(true);
|
||||
stream = s;
|
||||
|
||||
if (readyState == CLOSED) {
|
||||
stream.end();
|
||||
stream.destroy();
|
||||
stream = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
stream.on('data', function(d) {
|
||||
if (d.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
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.
|
||||
httpReq.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);
|
||||
};
|
||||
})();
|
||||
agent.on('upgrade', upgradeHandler); // node v0.4
|
||||
httpReq.on('upgrade', upgradeHandler); // node v0.5+
|
||||
|
||||
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
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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) { }
|
||||
});
|
||||
@@ -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) { }
|
||||
});
|
||||
274
test/common.js
274
test/common.js
@@ -1,274 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test dependencies.
|
||||
*/
|
||||
|
||||
var io = require('../')
|
||||
, 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 () {
|
||||
// node <v0.5 compat
|
||||
if (this.agent.sockets.forEach) {
|
||||
this.agent.sockets.forEach(function (socket) {
|
||||
if (socket.end) socket.end();
|
||||
});
|
||||
return;
|
||||
}
|
||||
// node >=v0.5 compat
|
||||
var self = this;
|
||||
Object.keys(this.agent.sockets).forEach(function (socket) {
|
||||
for (var i = 0, l = self.agent.sockets[socket].length; i < l; ++i) {
|
||||
if (self.agent.sockets[socket][i]._handle) {
|
||||
self.agent.sockets[socket][i]._handle.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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Issue a HEAD request
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
HTTPClient.prototype.head = function (path, opts, fn) {
|
||||
if ('function' == typeof opts) {
|
||||
fn = opts;
|
||||
opts = {};
|
||||
}
|
||||
|
||||
opts = opts || {};
|
||||
opts.method = 'HEAD';
|
||||
|
||||
return this.request(path, opts, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs a handshake (GET) request
|
||||
*
|
||||
* @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) {
|
||||
var manager = io.listen(cl.port);
|
||||
manager.set('client store expiration', 0);
|
||||
return manager;
|
||||
};
|
||||
|
||||
/**
|
||||
* WebSocket socket.io client.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function WSClient (port, sid, transport) {
|
||||
this.sid = sid;
|
||||
this.port = port;
|
||||
this.transportName = transport || 'websocket';
|
||||
WebSocket.call(
|
||||
this
|
||||
, 'ws://localhost:' + port + '/socket.io/'
|
||||
+ io.protocol + '/' + this.transportName + '/' + sid
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from WebSocket.
|
||||
*/
|
||||
|
||||
WSClient.prototype.__proto__ = WebSocket.prototype;
|
||||
|
||||
/**
|
||||
* Overrides message event emission.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WSClient.prototype.emit = function (name) {
|
||||
var args = arguments;
|
||||
|
||||
if (name == 'message' || name == 'data') {
|
||||
args[1] = parser.decodePacket(args[1].toString());
|
||||
}
|
||||
|
||||
return WebSocket.prototype.emit.apply(this, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a packet
|
||||
*/
|
||||
|
||||
WSClient.prototype.packet = function (pack) {
|
||||
this.write(parser.encodePacket(pack));
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a websocket client.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
websocket = function (cl, sid, transport) {
|
||||
return new WSClient(cl.port, sid, transport);
|
||||
};
|
||||
BIN
test/fixtures/big.jpg
vendored
Normal file
BIN
test/fixtures/big.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 MiB |
174066
test/fixtures/big.json
vendored
Normal file
174066
test/fixtures/big.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
21
test/fixtures/cert.crt
vendored
21
test/fixtures/cert.crt
vendored
@@ -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
27
test/fixtures/key.key
vendored
@@ -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-----
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* Returns a Buffer from a "ff 00 ff"-type hex string.
|
||||
*/
|
||||
|
||||
getBufferFromHexString = function(byteStr) {
|
||||
var bytes = byteStr.split(' ');
|
||||
var buf = new Buffer(bytes.length);
|
||||
for (var i = 0; i < bytes.length; ++i) {
|
||||
buf[i] = parseInt(bytes[i], 16);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hex string from a Buffer.
|
||||
*/
|
||||
|
||||
getHexStringFromBuffer = function(data) {
|
||||
var s = '';
|
||||
for (var i = 0; i < data.length; ++i) {
|
||||
s += padl(data[i].toString(16), 2, '0') + ' ';
|
||||
}
|
||||
return s.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a buffer in two parts.
|
||||
*/
|
||||
|
||||
splitBuffer = function(buffer) {
|
||||
var b1 = new Buffer(Math.ceil(buffer.length / 2));
|
||||
buffer.copy(b1, 0, 0, b1.length);
|
||||
var b2 = new Buffer(Math.floor(buffer.length / 2));
|
||||
buffer.copy(b2, 0, b1.length, b1.length + b2.length);
|
||||
return [b1, b2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs hybi07+ type masking on a hex string or buffer.
|
||||
*/
|
||||
|
||||
mask = function(buf, maskString) {
|
||||
if (typeof buf == 'string') buf = new Buffer(buf);
|
||||
var mask = getBufferFromHexString(maskString || '34 83 a8 68');
|
||||
for (var i = 0; i < buf.length; ++i) {
|
||||
buf[i] ^= mask[i % 4];
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hex string representing the length of a message
|
||||
*/
|
||||
|
||||
getHybiLengthAsHexString = function(len, masked) {
|
||||
if (len < 126) {
|
||||
var buf = new Buffer(1);
|
||||
buf[0] = (masked ? 0x80 : 0) | len;
|
||||
}
|
||||
else if (len < 65536) {
|
||||
var buf = new Buffer(3);
|
||||
buf[0] = (masked ? 0x80 : 0) | 126;
|
||||
getBufferFromHexString(pack(4, len)).copy(buf, 1);
|
||||
}
|
||||
else {
|
||||
var buf = new Buffer(9);
|
||||
buf[0] = (masked ? 0x80 : 0) | 127;
|
||||
getBufferFromHexString(pack(16, len)).copy(buf, 1);
|
||||
}
|
||||
return getHexStringFromBuffer(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks a Buffer into a number.
|
||||
*/
|
||||
|
||||
unpack = function(buffer) {
|
||||
var n = 0;
|
||||
for (var i = 0; i < buffer.length; ++i) {
|
||||
n = (i == 0) ? buffer[i] : (n * 256) + buffer[i];
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hex string, representing a specific byte count 'length', from a number.
|
||||
*/
|
||||
|
||||
pack = function(length, number) {
|
||||
return padl(number.toString(16), length, '0').replace(/(\d\d)/g, '$1 ').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Left pads the string 's' to a total length of 'n' with char 'c'.
|
||||
*/
|
||||
|
||||
padl = function(s, n, c) {
|
||||
return new Array(1 + n - s.length).join(c) + s;
|
||||
}
|
||||
125
test/io.test.js
125
test/io.test.js
@@ -1,125 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test dependencies.
|
||||
*/
|
||||
|
||||
var sio = require('../')
|
||||
, 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();
|
||||
});
|
||||
done();
|
||||
} catch (e) {
|
||||
e.should.match(/EACCES/);
|
||||
done();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
@@ -1,589 +0,0 @@
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test dependencies.
|
||||
*/
|
||||
|
||||
var sio = require('../')
|
||||
, 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 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 a referer with implicit port 80 is accepted for foo.bar.com:80 origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', 'foo.bar.com:80');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that erroneous referer is denied for addr:* origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', 'foo.bar.com:*');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://baz.bar.com/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(403);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that valid referer port is accepted for addr:port origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', 'foo.bar.com:81');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com:81/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that erroneous referer port is denied for addr:port origin': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('origins', 'foo.bar.com:81');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
|
||||
res.statusCode.should.eql(403);
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test handshake cross domain access control': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port)
|
||||
, headers = {
|
||||
Origin: 'http://example.org:1337'
|
||||
, Cookie: 'name=value'
|
||||
};
|
||||
|
||||
cl.get('/socket.io/{protocol}/', { headers:headers }, function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
res.headers['access-control-allow-origin'].should.eql('http://example.org:1337');
|
||||
res.headers['access-control-allow-credentials'].should.eql('true');
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test limiting the supported transports for a manager': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, 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
|
||||
, cl = client(port)
|
||||
, io = create(cl)
|
||||
, messages = 0
|
||||
, beat = false
|
||||
, ws;
|
||||
|
||||
io.configure(function () {
|
||||
io.disable('heartbeats');
|
||||
io.set('heartbeat interval', .05);
|
||||
io.set('heartbeat timeout', .05);
|
||||
io.set('close timeout', .05);
|
||||
});
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
setTimeout(function () {
|
||||
socket.disconnect();
|
||||
}, io.get('heartbeat timeout') * 1000 + 100);
|
||||
|
||||
socket.on('disconnect', function (reason) {
|
||||
beat.should.be.false;
|
||||
ws.finishClose();
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
cl.get('/socket.io/{protocol}/', function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
data.should.match(/([^:]+)::[\.0-9]+:(.*)/);
|
||||
|
||||
cl.handshake(function (sid) {
|
||||
ws = websocket(cl, sid);
|
||||
ws.on('message', function (packet) {
|
||||
if (++messages == 1) {
|
||||
packet.type.should.eql('connect');
|
||||
} else if (packet.type == 'heartbeat'){
|
||||
beat = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'no duplicate room members': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port);
|
||||
|
||||
Object.keys(io.rooms).length.should.equal(0);
|
||||
|
||||
io.onJoin(123, 'foo');
|
||||
io.rooms.foo.length.should.equal(1);
|
||||
|
||||
io.onJoin(123, 'foo');
|
||||
io.rooms.foo.length.should.equal(1);
|
||||
|
||||
io.onJoin(124, 'foo');
|
||||
io.rooms.foo.length.should.equal(2);
|
||||
|
||||
io.onJoin(124, 'foo');
|
||||
io.rooms.foo.length.should.equal(2);
|
||||
|
||||
io.onJoin(123, 'bar');
|
||||
io.rooms.foo.length.should.equal(2);
|
||||
io.rooms.bar.length.should.equal(1);
|
||||
|
||||
io.onJoin(123, 'bar');
|
||||
io.rooms.foo.length.should.equal(2);
|
||||
io.rooms.bar.length.should.equal(1);
|
||||
|
||||
io.onJoin(124, 'bar');
|
||||
io.rooms.foo.length.should.equal(2);
|
||||
io.rooms.bar.length.should.equal(2);
|
||||
|
||||
io.onJoin(124, 'bar');
|
||||
io.rooms.foo.length.should.equal(2);
|
||||
io.rooms.bar.length.should.equal(2);
|
||||
|
||||
process.nextTick(function() {
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test passing options directly to the Manager through listen': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port, { resource: '/my resource', custom: 'opt' });
|
||||
|
||||
io.get('resource').should.equal('/my resource');
|
||||
io.get('custom').should.equal('opt');
|
||||
process.nextTick(function() {
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test disabling the log': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port, { log: false })
|
||||
, _console = console.log
|
||||
, calls = 0;
|
||||
|
||||
// the logger uses console.log to output data, override it to see if get's
|
||||
// used
|
||||
console.log = function () { ++calls };
|
||||
|
||||
io.log.debug('test');
|
||||
io.log.log('testing');
|
||||
|
||||
console.log = _console;
|
||||
calls.should.equal(0);
|
||||
|
||||
process.nextTick(function() {
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test disabling logging with colors': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port, { 'log colors': false })
|
||||
, _console = console.log
|
||||
, calls = 0;
|
||||
|
||||
// the logger uses console.log to output data, override it to see if get's
|
||||
// used
|
||||
console.log = function (data) {
|
||||
++calls;
|
||||
data.indexOf('\033').should.equal(-1);
|
||||
};
|
||||
|
||||
io.log.debug('test');
|
||||
io.log.log('testing');
|
||||
|
||||
console.log = _console;
|
||||
calls.should.equal(2);
|
||||
|
||||
process.nextTick(function() {
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,286 +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){
|
||||
if (connect < 2) {
|
||||
return;
|
||||
}
|
||||
socket.broadcast.emit('b', 'test');
|
||||
socket.broadcast.json.emit('json', {foo:'bar'});
|
||||
socket.broadcast.send('foo');
|
||||
});
|
||||
|
||||
function finish () {
|
||||
connect.should.equal(2);
|
||||
message.should.equal(1);
|
||||
events.should.equal(2);
|
||||
cl.end();
|
||||
ws1.finishClose();
|
||||
ws2.finishClose();
|
||||
io.server.close();
|
||||
done();
|
||||
}
|
||||
|
||||
cl.handshake(function (sid) {
|
||||
ws1 = websocket(cl, sid);
|
||||
ws1.on('message', function (data) {
|
||||
if (data.type === 'connect') {
|
||||
if (connect == 0) {
|
||||
cl.handshake(function (sid) {
|
||||
ws2 = websocket(cl, sid);
|
||||
ws2.on('open', function () {
|
||||
ws2.packet({
|
||||
type: 'connect'
|
||||
, endpoint: 'a'
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
++connect;
|
||||
if (++calls === expected) finish();
|
||||
}
|
||||
|
||||
if (data.type === 'message') {
|
||||
++message;
|
||||
if (++calls === expected) finish();
|
||||
}
|
||||
|
||||
if (data.type === 'event') {
|
||||
if (data.name === 'b' || data.name === 'json') ++events;
|
||||
if (++calls === expected) finish();
|
||||
}
|
||||
});
|
||||
ws1.on('open', function() {
|
||||
ws1.packet({
|
||||
type: 'connect'
|
||||
, endpoint: 'a'
|
||||
});
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
'joining rooms inside a namespace': function (done) {
|
||||
var cl = client(++ports)
|
||||
, io = create(cl)
|
||||
, calls = 0
|
||||
, ws;
|
||||
|
||||
io.of('/foo').on('connection', function (socket) {
|
||||
socket.join('foo.bar');
|
||||
this.in('foo.bar').emit('baz', 'pewpew');
|
||||
});
|
||||
|
||||
cl.handshake(function (sid) {
|
||||
ws = websocket(cl, sid);
|
||||
|
||||
ws.on('open', function (){
|
||||
ws.packet({
|
||||
type: 'connect'
|
||||
, endpoint: '/foo'
|
||||
});
|
||||
});
|
||||
|
||||
ws.on('message', function (data) {
|
||||
if (data.type === 'event') {
|
||||
data.name.should.equal('baz');
|
||||
|
||||
cl.end();
|
||||
ws.finishClose();
|
||||
io.server.close();
|
||||
done();
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
'ignoring blacklisted events': function (done) {
|
||||
var cl = client(++ports)
|
||||
, io = create(cl)
|
||||
, calls = 0
|
||||
, ws;
|
||||
|
||||
io.set('heartbeat interval', 1);
|
||||
io.set('blacklist', ['foobar']);
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.on('foobar', function () {
|
||||
calls++;
|
||||
});
|
||||
});
|
||||
|
||||
cl.handshake(function (sid) {
|
||||
ws = websocket(cl, sid);
|
||||
|
||||
ws.on('open', function (){
|
||||
ws.packet({
|
||||
type: 'event'
|
||||
, name: 'foobar'
|
||||
, endpoint: ''
|
||||
});
|
||||
});
|
||||
|
||||
ws.on('message', function (data) {
|
||||
if (data.type === 'heartbeat') {
|
||||
cl.end();
|
||||
ws.finishClose();
|
||||
io.server.close();
|
||||
|
||||
calls.should.equal(0);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,356 +0,0 @@
|
||||
|
||||
/**
|
||||
* Test dependencies.
|
||||
*/
|
||||
|
||||
var parser = require('../').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'
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
2166
test/socket.io.js
Normal file
2166
test/socket.io.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,549 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test dependencies.
|
||||
*/
|
||||
|
||||
var sio = require('../')
|
||||
, should = require('./common')
|
||||
, ports = 15400;
|
||||
|
||||
/**
|
||||
* Test.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
|
||||
'test that the default static files are available': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port);
|
||||
|
||||
(!!io.static.has('/socket.io.js')).should.be.true;
|
||||
(!!io.static.has('/socket.io.v1.0.0.js')).should.be.true;
|
||||
(!!io.static.has('/socket.io+xhr-polling.js')).should.be.true;
|
||||
(!!io.static.has('/socket.io+xhr-polling.v1.0.0.js')).should.be.true;
|
||||
(!!io.static.has('/static/flashsocket/WebSocketMain.swf')).should.be.true;
|
||||
(!!io.static.has('/static/flashsocket/WebSocketMainInsecure.swf')).should.be.true;
|
||||
|
||||
process.nextTick(function() {
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that static files are correctly looked up': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port);
|
||||
|
||||
(!!io.static.has('/socket.io.js')).should.be.true;
|
||||
(!!io.static.has('/invalidfilehereplease.js')).should.be.false;
|
||||
|
||||
process.nextTick(function() {
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that the client is served': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
should.strictEqual(res.headers.etag, undefined);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that the custom build client is served': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.enable('browser client etag');
|
||||
|
||||
cl.get('/socket.io/socket.io+websocket.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
data.should.match(/WS\.prototype\.name/);
|
||||
data.should.not.match(/Flashsocket\.prototype\.name/);
|
||||
data.should.not.match(/HTMLFile\.prototype\.name/);
|
||||
data.should.not.match(/JSONPPolling\.prototype\.name/);
|
||||
data.should.not.match(/XHRPolling\.prototype\.name/);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that the client is build with the enabled transports': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.set('transports', ['websocket']);
|
||||
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
data.should.match(/WS\.prototype\.name/);
|
||||
data.should.not.match(/Flashsocket\.prototype\.name/);
|
||||
data.should.not.match(/HTMLFile\.prototype\.name/);
|
||||
data.should.not.match(/JSONPPolling\.prototype\.name/);
|
||||
data.should.not.match(/XHRPolling\.prototype\.name/);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that the client cache is cleared when transports change': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.set('transports', ['websocket']);
|
||||
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
data.should.match(/WS\.prototype\.name/);
|
||||
data.should.not.match(/Flashsocket\.prototype\.name/);
|
||||
data.should.not.match(/HTMLFile\.prototype\.name/);
|
||||
data.should.not.match(/JSONPPolling\.prototype\.name/);
|
||||
data.should.not.match(/XHRPolling\.prototype\.name/);
|
||||
|
||||
io.set('transports', ['xhr-polling']);
|
||||
should.strictEqual(io.static.cache['/socket.io.js'], undefined);
|
||||
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
data.should.match(/XHRPolling\.prototype\.name/);
|
||||
data.should.not.match(/Flashsocket\.prototype\.name/);
|
||||
data.should.not.match(/HTMLFile\.prototype\.name/);
|
||||
data.should.not.match(/JSONPPolling\.prototype\.name/);
|
||||
data.should.not.match(/WS\.prototype\.name/);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test that the client etag is served': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.enable('browser client etag');
|
||||
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that the client etag is changed for new transports': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.set('transports', ['websocket']);
|
||||
io.enable('browser client etag');
|
||||
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
var wsEtag = res.headers.etag;
|
||||
|
||||
io.set('transports', ['xhr-polling']);
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
res.headers.etag.should.not.equal(wsEtag);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test that the client is served with gzip': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.enable('browser client gzip');
|
||||
|
||||
cl.get('/socket.io/socket.io.js', {
|
||||
headers: {
|
||||
'accept-encoding': 'deflate, gzip'
|
||||
}
|
||||
}
|
||||
, function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-encoding'].should.eql('gzip');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
'test that the cached client is served': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
should.strictEqual(res.headers.etag, undefined);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
var static = io.static;
|
||||
static.cache['/socket.io.js'].content.should.match(/XMLHttpRequest/);
|
||||
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
should.strictEqual(res.headers.etag, undefined);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test that the client is not cached': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.static.add('/random.js', function (path, callback) {
|
||||
var random = Math.floor(Date.now() * Math.random()).toString();
|
||||
callback(null, new Buffer(random));
|
||||
});
|
||||
|
||||
io.disable('browser client cache');
|
||||
|
||||
cl.get('/socket.io/random.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
should.strictEqual(res.headers.etag, undefined);
|
||||
|
||||
cl.get('/socket.io/random.js', function (res, random) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
should.strictEqual(res.headers.etag, undefined);
|
||||
|
||||
data.should.not.equal(random);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test that the cached client etag is served': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.enable('browser client etag');
|
||||
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
var static = io.static
|
||||
, cache = static.cache['/socket.io.js'];
|
||||
|
||||
cache.content.toString().should.match(/XMLHttpRequest/);
|
||||
Buffer.isBuffer(cache.content).should.be.true;
|
||||
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test that the cached client sends a 304 header': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.enable('browser client etag');
|
||||
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
cl.get('/socket.io/socket.io.js', {
|
||||
headers: {
|
||||
'if-none-match': res.headers.etag
|
||||
}
|
||||
}, function (res, data) {
|
||||
res.statusCode.should.eql(304);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
'test that client minification works': function (done) {
|
||||
// server 1
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
// server 2
|
||||
var port = ++ports
|
||||
, io2 = sio.listen(port)
|
||||
, cl2 = client(port);
|
||||
|
||||
io.enable('browser client minification');
|
||||
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
var length = data.length;
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
|
||||
cl2.get('/socket.io/socket.io.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
should.strictEqual(res.headers.etag, undefined);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
data.length.should.be.greaterThan(length);
|
||||
|
||||
cl2.end();
|
||||
io2.server.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test that the WebSocketMain.swf is served': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
cl.get('/socket.io/static/flashsocket/WebSocketMain.swf', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/x-shockwave-flash');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
should.strictEqual(res.headers.etag, undefined);
|
||||
|
||||
var static = io.static
|
||||
, cache = static.cache['/static/flashsocket/WebSocketMain.swf'];
|
||||
|
||||
Buffer.isBuffer(cache.content).should.be.true;
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that the WebSocketMainInsecure.swf is served': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
cl.get('/socket.io/static/flashsocket/WebSocketMainInsecure.swf', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/x-shockwave-flash');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
should.strictEqual(res.headers.etag, undefined);
|
||||
|
||||
var static = io.static
|
||||
, cache = static.cache['/static/flashsocket/WebSocketMainInsecure.swf'];
|
||||
|
||||
Buffer.isBuffer(cache.content).should.be.true;
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that swf files are not served with gzip': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.enable('browser client gzip');
|
||||
|
||||
cl.get('/socket.io/static/flashsocket/WebSocketMain.swf', {
|
||||
headers: {
|
||||
'accept-encoding': 'deflate, gzip'
|
||||
}
|
||||
}
|
||||
, function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/x-shockwave-flash');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
should.strictEqual(res.headers['content-encoding'], undefined);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
'test that you can serve custom clients': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.set('browser client handler', function (req, res) {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'application/javascript'
|
||||
, 'Content-Length': 13
|
||||
, 'ETag': '1.0'
|
||||
});
|
||||
res.end('custom_client');
|
||||
});
|
||||
|
||||
cl.get('/socket.io/socket.io.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.eql(13);
|
||||
res.headers.etag.should.eql('1.0');
|
||||
|
||||
data.should.eql('custom_client');
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that HEAD requests work': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
cl.head('/socket.io/socket.io.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
|
||||
data.should.eql('');
|
||||
|
||||
cl.end();
|
||||
io.server.close()
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that a versioned client is served': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
cl.get('/socket.io/socket.io.v0.8.9.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
res.headers['cache-control']
|
||||
.indexOf(io.get('browser client expires')).should.be.above(-1);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that a custom versioned build client is served': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.set('browser client expires', 1337);
|
||||
|
||||
cl.get('/socket.io/socket.io+websocket.v0.8.10.js', function (res, data) {
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
res.headers['cache-control']
|
||||
.indexOf(io.get('browser client expires')).should.be.above(-1);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
data.should.match(/WS\.prototype\.name/);
|
||||
data.should.not.match(/Flashsocket\.prototype\.name/);
|
||||
data.should.not.match(/HTMLFile\.prototype\.name/);
|
||||
data.should.not.match(/JSONPPolling\.prototype\.name/);
|
||||
data.should.not.match(/XHRPolling\.prototype\.name/);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
'test that etags are ignored for versioned requests': function (done) {
|
||||
var port = ++ports
|
||||
, io = sio.listen(port)
|
||||
, cl = client(port);
|
||||
|
||||
io.enable('browser client etag');
|
||||
|
||||
cl.get('/socket.io/socket.io.v0.8.9.js', function (res, data) {
|
||||
should.strictEqual(res.headers.etag, undefined);
|
||||
res.headers['content-type'].should.eql('application/javascript');
|
||||
res.headers['content-length'].should.match(/([0-9]+)/);
|
||||
res.headers['cache-control']
|
||||
.indexOf(io.get('browser client expires')).should.be.above(-1);
|
||||
|
||||
data.should.match(/XMLHttpRequest/);
|
||||
|
||||
cl.end();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -1,190 +0,0 @@
|
||||
|
||||
/**
|
||||
* Test dependencies
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var sio = require('../')
|
||||
, should = require('should')
|
||||
, MemoryStore = sio.MemoryStore;
|
||||
|
||||
/**
|
||||
* Test.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
|
||||
'test storing data for a client': function (done) {
|
||||
var store = new MemoryStore
|
||||
, client = store.client('test');
|
||||
|
||||
client.id.should.equal('test');
|
||||
|
||||
client.set('a', 'b', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client.get('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.eql('b');
|
||||
|
||||
client.has('a', function (err, has) {
|
||||
should.strictEqual(err, null);
|
||||
has.should.be.true;
|
||||
|
||||
client.has('b', function (err, has) {
|
||||
should.strictEqual(err, null);
|
||||
has.should.be.false;
|
||||
|
||||
client.del('a', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client.has('a', function (err, has) {
|
||||
should.strictEqual(err, null);
|
||||
has.should.be.false;
|
||||
|
||||
client.set('b', 'c', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client.set('c', 'd', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client.get('b', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.equal('c');
|
||||
|
||||
client.get('c', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.equal('d');
|
||||
|
||||
store.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test cleaning up clients data': function (done) {
|
||||
var rand1 = Math.abs(Math.random() * Date.now() | 0)
|
||||
, rand2 = Math.abs(Math.random() * Date.now() | 0);
|
||||
|
||||
var store = new MemoryStore()
|
||||
, client1 = store.client(rand1)
|
||||
, client2 = store.client(rand2);
|
||||
|
||||
client1.set('a', 'b', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client2.set('c', 'd', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client1.has('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.be.true;
|
||||
|
||||
client2.has('c', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.be.true;
|
||||
|
||||
store.destroy();
|
||||
|
||||
var newstore = new MemoryStore()
|
||||
, newclient1 = newstore.client(rand1)
|
||||
, newclient2 = newstore.client(rand2);
|
||||
|
||||
newclient1.has('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.be.false;
|
||||
|
||||
newclient2.has('c', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.be.false;
|
||||
|
||||
newstore.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test cleaning up a particular client': function (done) {
|
||||
var rand1 = Math.abs(Math.random() * Date.now() | 0)
|
||||
, rand2 = Math.abs(Math.random() * Date.now() | 0);
|
||||
|
||||
var store = new MemoryStore()
|
||||
, client1 = store.client(rand1)
|
||||
, client2 = store.client(rand2);
|
||||
|
||||
client1.set('a', 'b', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client2.set('c', 'd', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client1.has('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.be.true;
|
||||
|
||||
client2.has('c', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.be.true;
|
||||
|
||||
store.clients.should.have.property(rand1);
|
||||
store.clients.should.have.property(rand2);
|
||||
store.destroyClient(rand1);
|
||||
|
||||
store.clients.should.not.have.property(rand1);
|
||||
store.clients.should.have.property(rand2);
|
||||
|
||||
client1.has('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.equal(false);
|
||||
|
||||
store.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test destroy expiration': function (done) {
|
||||
var store = new MemoryStore()
|
||||
, id = Math.abs(Math.random() * Date.now() | 0)
|
||||
, client = store.client(id);
|
||||
|
||||
client.set('a', 'b', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
store.destroyClient(id, 1);
|
||||
|
||||
setTimeout(function () {
|
||||
client.get('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.equal('b');
|
||||
});
|
||||
}, 500);
|
||||
|
||||
setTimeout(function () {
|
||||
client.get('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
should.strictEqual(val, null);
|
||||
|
||||
store.destroy();
|
||||
done();
|
||||
});
|
||||
}, 1900);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,240 +0,0 @@
|
||||
|
||||
/**
|
||||
* Test dependencies
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var sio = require('../')
|
||||
, redis = require('redis')
|
||||
, should = require('should')
|
||||
, RedisStore = sio.RedisStore;
|
||||
|
||||
/**
|
||||
* Test.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
|
||||
'test publishing doesnt get caught by the own store subscriber': function (done) {
|
||||
var a = new RedisStore
|
||||
, b = new RedisStore;
|
||||
|
||||
a.subscribe('woot', function (arg) {
|
||||
arg.should.equal('bb');
|
||||
a.destroy();
|
||||
b.destroy();
|
||||
done();
|
||||
}, function () {
|
||||
a.publish('woot', 'aa');
|
||||
b.publish('woot', 'bb');
|
||||
});
|
||||
},
|
||||
|
||||
'test publishing to multiple subscribers': function (done) {
|
||||
var a = new RedisStore
|
||||
, b = new RedisStore
|
||||
, c = new RedisStore
|
||||
, subscriptions = 3
|
||||
, messages = 2;
|
||||
|
||||
a.subscribe('tobi', function () {
|
||||
throw new Error('Shouldnt publish to itself');
|
||||
}, publish);
|
||||
|
||||
function subscription (arg1, arg2, arg3) {
|
||||
arg1.should.equal(1);
|
||||
arg2.should.equal(2);
|
||||
arg3.should.equal(3);
|
||||
--messages || finish();
|
||||
}
|
||||
|
||||
b.subscribe('tobi', subscription, publish);
|
||||
c.subscribe('tobi', subscription, publish);
|
||||
|
||||
function publish () {
|
||||
--subscriptions || a.publish('tobi', 1, 2, 3);
|
||||
}
|
||||
|
||||
function finish () {
|
||||
a.destroy();
|
||||
b.destroy();
|
||||
c.destroy();
|
||||
done();
|
||||
}
|
||||
},
|
||||
|
||||
'test storing data for a client': function (done) {
|
||||
var store = new RedisStore
|
||||
, rand = 'test-' + Date.now()
|
||||
, client = store.client(rand);
|
||||
|
||||
client.id.should.equal(rand);
|
||||
|
||||
client.set('a', 'b', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client.get('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.equal('b');
|
||||
|
||||
client.has('a', function (err, has) {
|
||||
should.strictEqual(err, null);
|
||||
has.should.be.true;
|
||||
|
||||
client.has('b', function (err, has) {
|
||||
should.strictEqual(err, null);
|
||||
has.should.be.false;
|
||||
|
||||
client.del('a', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client.has('a', function (err, has) {
|
||||
should.strictEqual(err, null);
|
||||
has.should.be.false;
|
||||
|
||||
client.set('b', 'c', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client.set('c', 'd', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client.get('b', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.equal('c');
|
||||
|
||||
client.get('c', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.equal('d');
|
||||
|
||||
store.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test cleaning up clients data': function (done) {
|
||||
var rand1 = Math.abs(Math.random() * Date.now() | 0)
|
||||
, rand2 = Math.abs(Math.random() * Date.now() | 0);
|
||||
|
||||
var store = new RedisStore()
|
||||
, client1 = store.client(rand1)
|
||||
, client2 = store.client(rand2);
|
||||
|
||||
client1.set('a', 'b', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client2.set('c', 'd', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client1.has('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.be.true;
|
||||
|
||||
client2.has('c', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.be.true;
|
||||
|
||||
store.destroy();
|
||||
|
||||
var newstore = new RedisStore()
|
||||
, newclient1 = newstore.client(rand1)
|
||||
, newclient2 = newstore.client(rand2);
|
||||
|
||||
newclient1.has('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.be.false;
|
||||
|
||||
newclient2.has('c', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.be.false;
|
||||
|
||||
newstore.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test cleaning up a particular client': function (done) {
|
||||
var rand1 = Math.abs(Math.random() * Date.now() | 0)
|
||||
, rand2 = Math.abs(Math.random() * Date.now() | 0);
|
||||
|
||||
var store = new RedisStore()
|
||||
, client1 = store.client(rand1)
|
||||
, client2 = store.client(rand2);
|
||||
|
||||
client1.set('a', 'b', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client2.set('c', 'd', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
|
||||
client1.has('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.be.true;
|
||||
|
||||
client2.has('c', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.be.true;
|
||||
|
||||
store.clients.should.have.property(rand1);
|
||||
store.clients.should.have.property(rand2);
|
||||
store.destroyClient(rand1);
|
||||
|
||||
store.clients.should.not.have.property(rand1);
|
||||
store.clients.should.have.property(rand2);
|
||||
|
||||
client1.has('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.equal(false);
|
||||
|
||||
store.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'test destroy expiration': function (done) {
|
||||
var store = new RedisStore()
|
||||
, id = Math.abs(Math.random() * Date.now() | 0)
|
||||
, client = store.client(id);
|
||||
|
||||
client.set('a', 'b', function (err) {
|
||||
should.strictEqual(err, null);
|
||||
store.destroyClient(id, 1);
|
||||
|
||||
setTimeout(function () {
|
||||
client.get('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
val.should.equal('b');
|
||||
});
|
||||
}, 500);
|
||||
|
||||
setTimeout(function () {
|
||||
client.get('a', function (err, val) {
|
||||
should.strictEqual(err, null);
|
||||
should.strictEqual(val, null);
|
||||
|
||||
store.destroy();
|
||||
done();
|
||||
});
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
BIN
test/support/doge.jpg
Normal file
BIN
test/support/doge.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
@@ -1,187 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test dependencies.
|
||||
*/
|
||||
|
||||
var sio = require('../')
|
||||
, 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();
|
||||
},
|
||||
|
||||
'flashsocket identifies as flashsocket': function (done) {
|
||||
var cl = client(++ports)
|
||||
, io = create(cl)
|
||||
, ws;
|
||||
|
||||
io.set('transports', ['flashsocket']);
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.manager.transports[socket.id].name.should.equal('flashsocket');
|
||||
ws.finishClose();
|
||||
cl.end();
|
||||
io.flashPolicyServer.close();
|
||||
io.server.close();
|
||||
done();
|
||||
});
|
||||
|
||||
cl.handshake(function (sid) {
|
||||
ws = websocket(cl, sid, 'flashsocket');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,458 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test dependencies.
|
||||
*/
|
||||
|
||||
var sio = require('../')
|
||||
, 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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,774 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test dependencies.
|
||||
*/
|
||||
|
||||
var sio = require('../')
|
||||
, 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;
|
||||
io.server.close();
|
||||
cl.end();
|
||||
done();
|
||||
});
|
||||
|
||||
// with the new http bits in node 0.5, there's no guarantee that
|
||||
// the previous request is actually dispatched (and received) before the following
|
||||
// reset call is sent. to not waste more time on a workaround, a timeout is added.
|
||||
setTimeout(function() {
|
||||
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid + '/?disconnect');
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'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
|
||||
, JSON.stringify(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
|
||||
, JSON.stringify(parser.encodePayload([
|
||||
parser.encodePacket({ type: 'message', data: 'a' })
|
||||
, parser.encodePacket({ type: 'message', data: 'b' })
|
||||
, parser.encodePacket({ type: 'disconnect' })
|
||||
]))
|
||||
, function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
data.should.eql('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
|
||||
, JSON.stringify(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
|
||||
, JSON.stringify(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
|
||||
, JSON.stringify(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
|
||||
, JSON.stringify(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
|
||||
, JSON.stringify(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
|
||||
, JSON.stringify(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
|
||||
, JSON.stringify(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
|
||||
, JSON.stringify(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
|
||||
, JSON.stringify(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
|
||||
, JSON.stringify(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
|
||||
, JSON.stringify(parser.encodePacket({ type: 'message', endpoint: '/b', data: 'b' }))
|
||||
, function (res, data) {
|
||||
res.statusCode.should.eql(200);
|
||||
data.should.eql('1');
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,262 +0,0 @@
|
||||
/**
|
||||
* Test dependencies.
|
||||
*/
|
||||
|
||||
var assert = require('assert');
|
||||
var Parser = require('../lib/transports/websocket/hybi-07-12.js').Parser;
|
||||
require('./hybi-common');
|
||||
|
||||
/**
|
||||
* Tests.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
'can parse unmasked text message': function() {
|
||||
var p = new Parser();
|
||||
var packet = '81 05 48 65 6c 6c 6f';
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal('Hello', data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse close message': function() {
|
||||
var p = new Parser();
|
||||
var packet = '88 00';
|
||||
|
||||
var gotClose = false;
|
||||
p.on('close', function(data) {
|
||||
gotClose = true;
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotClose);
|
||||
},
|
||||
'can parse masked text message': function() {
|
||||
var p = new Parser();
|
||||
var packet = '81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5';
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal('5:::{"name":"echo"}', data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a masked text message longer than 125 bytes': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
|
||||
var packet = '81 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a really long masked text message': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 64*1024; ++i) message += (i % 5).toString();
|
||||
var packet = '81 FF ' + pack(16, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a fragmented masked text message of 300 bytes': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
|
||||
var msgpiece1 = message.substr(0, 150);
|
||||
var msgpiece2 = message.substr(150);
|
||||
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
|
||||
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet1));
|
||||
p.add(getBufferFromHexString(packet2));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a ping message': function() {
|
||||
var p = new Parser();
|
||||
var message = 'Hello';
|
||||
var packet = '89 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a ping with no data': function() {
|
||||
var p = new Parser();
|
||||
var packet = '89 00';
|
||||
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a fragmented masked text message of 300 bytes with a ping in the middle': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
|
||||
|
||||
var msgpiece1 = message.substr(0, 150);
|
||||
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
|
||||
|
||||
var pingMessage = 'Hello';
|
||||
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
|
||||
|
||||
var msgpiece2 = message.substr(150);
|
||||
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(pingMessage, data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet1));
|
||||
p.add(getBufferFromHexString(pingPacket));
|
||||
p.add(getBufferFromHexString(packet2));
|
||||
assert.ok(gotData);
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a fragmented masked text message of 300 bytes with a ping in the middle, which is delievered over sevaral tcp packets': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
|
||||
|
||||
var msgpiece1 = message.substr(0, 150);
|
||||
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
|
||||
|
||||
var pingMessage = 'Hello';
|
||||
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
|
||||
|
||||
var msgpiece2 = message.substr(150);
|
||||
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(pingMessage, data);
|
||||
});
|
||||
|
||||
var buffers = [];
|
||||
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet1)));
|
||||
buffers = buffers.concat(splitBuffer(getBufferFromHexString(pingPacket)));
|
||||
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet2)));
|
||||
for (var i = 0; i < buffers.length; ++i) {
|
||||
p.add(buffers[i]);
|
||||
}
|
||||
assert.ok(gotData);
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a 100 byte long masked binary message': function() {
|
||||
var p = new Parser();
|
||||
var length = 100;
|
||||
var message = new Buffer(length);
|
||||
for (var i = 0; i < length; ++i) message[i] = i % 256;
|
||||
var originalMessage = getHexStringFromBuffer(message);
|
||||
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('binary', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(originalMessage, getHexStringFromBuffer(data));
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a 256 byte long masked binary message': function() {
|
||||
var p = new Parser();
|
||||
var length = 256;
|
||||
var message = new Buffer(length);
|
||||
for (var i = 0; i < length; ++i) message[i] = i % 256;
|
||||
var originalMessage = getHexStringFromBuffer(message);
|
||||
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('binary', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(originalMessage, getHexStringFromBuffer(data));
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a 200kb long masked binary message': function() {
|
||||
var p = new Parser();
|
||||
var length = 200 * 1024;
|
||||
var message = new Buffer(length);
|
||||
for (var i = 0; i < length; ++i) message[i] = i % 256;
|
||||
var originalMessage = getHexStringFromBuffer(message);
|
||||
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('binary', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(originalMessage, getHexStringFromBuffer(data));
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a 200kb long unmasked binary message': function() {
|
||||
var p = new Parser();
|
||||
var length = 200 * 1024;
|
||||
var message = new Buffer(length);
|
||||
for (var i = 0; i < length; ++i) message[i] = i % 256;
|
||||
var originalMessage = getHexStringFromBuffer(message);
|
||||
var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message);
|
||||
|
||||
var gotData = false;
|
||||
p.on('binary', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(originalMessage, getHexStringFromBuffer(data));
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
/**
|
||||
* Test dependencies.
|
||||
*/
|
||||
|
||||
var assert = require('assert');
|
||||
var Parser = require('../lib/transports/websocket/hybi-16.js').Parser;
|
||||
require('./hybi-common');
|
||||
|
||||
/**
|
||||
* Tests.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
'can parse unmasked text message': function() {
|
||||
var p = new Parser();
|
||||
var packet = '81 05 48 65 6c 6c 6f';
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal('Hello', data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse close message': function() {
|
||||
var p = new Parser();
|
||||
var packet = '88 00';
|
||||
|
||||
var gotClose = false;
|
||||
p.on('close', function(data) {
|
||||
gotClose = true;
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotClose);
|
||||
},
|
||||
'can parse masked text message': function() {
|
||||
var p = new Parser();
|
||||
var packet = '81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5';
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal('5:::{"name":"echo"}', data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a masked text message longer than 125 bytes': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
|
||||
var packet = '81 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a really long masked text message': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 64*1024; ++i) message += (i % 5).toString();
|
||||
var packet = '81 FF ' + pack(16, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a fragmented masked text message of 300 bytes': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
|
||||
var msgpiece1 = message.substr(0, 150);
|
||||
var msgpiece2 = message.substr(150);
|
||||
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
|
||||
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet1));
|
||||
p.add(getBufferFromHexString(packet2));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a ping message': function() {
|
||||
var p = new Parser();
|
||||
var message = 'Hello';
|
||||
var packet = '89 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a ping with no data': function() {
|
||||
var p = new Parser();
|
||||
var packet = '89 00';
|
||||
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a fragmented masked text message of 300 bytes with a ping in the middle': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
|
||||
|
||||
var msgpiece1 = message.substr(0, 150);
|
||||
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
|
||||
|
||||
var pingMessage = 'Hello';
|
||||
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
|
||||
|
||||
var msgpiece2 = message.substr(150);
|
||||
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(pingMessage, data);
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet1));
|
||||
p.add(getBufferFromHexString(pingPacket));
|
||||
p.add(getBufferFromHexString(packet2));
|
||||
assert.ok(gotData);
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a fragmented masked text message of 300 bytes with a ping in the middle, which is delievered over sevaral tcp packets': function() {
|
||||
var p = new Parser();
|
||||
var message = 'A';
|
||||
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
|
||||
|
||||
var msgpiece1 = message.substr(0, 150);
|
||||
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
|
||||
|
||||
var pingMessage = 'Hello';
|
||||
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
|
||||
|
||||
var msgpiece2 = message.substr(150);
|
||||
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('data', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(message, data);
|
||||
});
|
||||
var gotPing = false;
|
||||
p.on('ping', function(data) {
|
||||
gotPing = true;
|
||||
assert.equal(pingMessage, data);
|
||||
});
|
||||
|
||||
var buffers = [];
|
||||
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet1)));
|
||||
buffers = buffers.concat(splitBuffer(getBufferFromHexString(pingPacket)));
|
||||
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet2)));
|
||||
for (var i = 0; i < buffers.length; ++i) {
|
||||
p.add(buffers[i]);
|
||||
}
|
||||
assert.ok(gotData);
|
||||
assert.ok(gotPing);
|
||||
},
|
||||
'can parse a 100 byte long masked binary message': function() {
|
||||
var p = new Parser();
|
||||
var length = 100;
|
||||
var message = new Buffer(length);
|
||||
for (var i = 0; i < length; ++i) message[i] = i % 256;
|
||||
var originalMessage = getHexStringFromBuffer(message);
|
||||
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('binary', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(originalMessage, getHexStringFromBuffer(data));
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a 256 byte long masked binary message': function() {
|
||||
var p = new Parser();
|
||||
var length = 256;
|
||||
var message = new Buffer(length);
|
||||
for (var i = 0; i < length; ++i) message[i] = i % 256;
|
||||
var originalMessage = getHexStringFromBuffer(message);
|
||||
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('binary', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(originalMessage, getHexStringFromBuffer(data));
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a 200kb long masked binary message': function() {
|
||||
var p = new Parser();
|
||||
var length = 200 * 1024;
|
||||
var message = new Buffer(length);
|
||||
for (var i = 0; i < length; ++i) message[i] = i % 256;
|
||||
var originalMessage = getHexStringFromBuffer(message);
|
||||
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
|
||||
|
||||
var gotData = false;
|
||||
p.on('binary', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(originalMessage, getHexStringFromBuffer(data));
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
'can parse a 200kb long unmasked binary message': function() {
|
||||
var p = new Parser();
|
||||
var length = 200 * 1024;
|
||||
var message = new Buffer(length);
|
||||
for (var i = 0; i < length; ++i) message[i] = i % 256;
|
||||
var originalMessage = getHexStringFromBuffer(message);
|
||||
var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message);
|
||||
|
||||
var gotData = false;
|
||||
p.on('binary', function(data) {
|
||||
gotData = true;
|
||||
assert.equal(originalMessage, getHexStringFromBuffer(data));
|
||||
});
|
||||
|
||||
p.add(getBufferFromHexString(packet));
|
||||
assert.ok(gotData);
|
||||
},
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user