mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-11 07:58:13 -05:00
Compare commits
863 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a10dc8d92d | ||
|
|
2b216902e1 | ||
|
|
832b8fc6d9 | ||
|
|
a0056904c1 | ||
|
|
3367eaa948 | ||
|
|
6c0705f733 | ||
|
|
1980fb4a03 | ||
|
|
0d07c47f81 | ||
|
|
a086588747 | ||
|
|
87b06ad362 | ||
|
|
199eec648e | ||
|
|
f1b39a6b1d | ||
|
|
240b154960 | ||
|
|
c5b7738730 | ||
|
|
03f3bc9ab3 | ||
|
|
e40accf7a1 | ||
|
|
01a4623613 | ||
|
|
2d5b0026c5 | ||
|
|
5ae06e6285 | ||
|
|
4d8f68c7dc | ||
|
|
5b79ab1af1 | ||
|
|
54ff591b07 | ||
|
|
e1facd5155 | ||
|
|
3b92cc2b26 | ||
|
|
3d695c60f1 | ||
|
|
3b5f4339a7 | ||
|
|
23c9dd34d5 | ||
|
|
e28b475428 | ||
|
|
f7eed6e397 | ||
|
|
988852986a | ||
|
|
8eaba085de | ||
|
|
2258a6a6e3 | ||
|
|
ad658b8cc2 | ||
|
|
e24434a0a0 | ||
|
|
b754cff2b4 | ||
|
|
706ca2a0c1 | ||
|
|
accd0bd64a | ||
|
|
131a2befec | ||
|
|
0be865f614 | ||
|
|
01b262fbe0 | ||
|
|
f260439933 | ||
|
|
e04de3c2c8 | ||
|
|
a27802e19e | ||
|
|
c46d4481bd | ||
|
|
d82190016f | ||
|
|
255b845596 | ||
|
|
1f59e4526a | ||
|
|
0a7afa85ea | ||
|
|
1e31769062 | ||
|
|
797c9a3498 | ||
|
|
4f93a0b429 | ||
|
|
3c98130f15 | ||
|
|
9c23308c6e | ||
|
|
955e5e0d91 | ||
|
|
0ef55b26d4 | ||
|
|
4d8e2d342c | ||
|
|
d48f848bb4 | ||
|
|
57b386385e | ||
|
|
9e7567daee | ||
|
|
2e36799b17 | ||
|
|
9bb5e9de2f | ||
|
|
ff2c15de68 | ||
|
|
a483658607 | ||
|
|
4c5dbd8824 | ||
|
|
e14a10b7ce | ||
|
|
5a123beea5 | ||
|
|
2ed5f0f5fb | ||
|
|
e9f980c475 | ||
|
|
6f44f3a8ef | ||
|
|
04fc0f3677 | ||
|
|
d026c00d05 | ||
|
|
fdf64cc38f | ||
|
|
5badb6436e | ||
|
|
c20e0b26c9 | ||
|
|
5c10c5439b | ||
|
|
8182ecc61c | ||
|
|
ccd3376627 | ||
|
|
58a73d39e9 | ||
|
|
e60bd5a4da | ||
|
|
21dffa4b58 | ||
|
|
94852e3d23 | ||
|
|
b8c60506a6 | ||
|
|
ecc76f48bc | ||
|
|
c94058f9b0 | ||
|
|
43d9a4b55d | ||
|
|
df916172dd | ||
|
|
271c625243 | ||
|
|
628fe8f1b2 | ||
|
|
db62e1bf67 | ||
|
|
ba4c7921ef | ||
|
|
30ea0b8d7d | ||
|
|
2d141aff7c | ||
|
|
40763d3962 | ||
|
|
2a092bd2fb | ||
|
|
4137eb5c43 | ||
|
|
e3207005da | ||
|
|
42aa77614e | ||
|
|
1491a96c95 | ||
|
|
dcca01f5a4 | ||
|
|
3b58fa04d5 | ||
|
|
0c6d50d9c0 | ||
|
|
881f16553c | ||
|
|
e5a8d4d2d9 | ||
|
|
fb0253edea | ||
|
|
3c5f5a0864 | ||
|
|
a23d26a617 | ||
|
|
910b5d77a6 | ||
|
|
438ad63cdf | ||
|
|
7e9a67d8ee | ||
|
|
0ae070885d | ||
|
|
36d99d8d84 | ||
|
|
0e63b0910e | ||
|
|
3c7350fa58 | ||
|
|
aadd5da655 | ||
|
|
6d4128750b | ||
|
|
6edcd1c6ba | ||
|
|
6b2394e612 | ||
|
|
677af3fa11 | ||
|
|
a1ff739b36 | ||
|
|
de5b588e17 | ||
|
|
5a20c1195b | ||
|
|
5253bc400b | ||
|
|
1245a5639e | ||
|
|
88161539a1 | ||
|
|
d99d4d15ae | ||
|
|
06ecfe5444 | ||
|
|
e90b4eba1e | ||
|
|
c7de1a1adf | ||
|
|
7bae6ac636 | ||
|
|
355b5156fe | ||
|
|
3e168ee0b8 | ||
|
|
ed9ab77dcb | ||
|
|
1104cd135e | ||
|
|
0be915cd0f | ||
|
|
c6dd41b915 | ||
|
|
0f14312d7b | ||
|
|
97bd95f036 | ||
|
|
11e0f19272 | ||
|
|
e388a3319d | ||
|
|
b551ce9835 | ||
|
|
0bac96a6b2 | ||
|
|
1d07b10339 | ||
|
|
398b5479f0 | ||
|
|
d9eb729eab | ||
|
|
f9db72997f | ||
|
|
1293505dc2 | ||
|
|
f4e9e71c56 | ||
|
|
045674de97 | ||
|
|
7091acf24c | ||
|
|
19341051e8 | ||
|
|
e141e09aaf | ||
|
|
5fabe4e780 | ||
|
|
7760a71d90 | ||
|
|
c077357eff | ||
|
|
2f26a2bdb4 | ||
|
|
13af610f6d | ||
|
|
abcedf53ec | ||
|
|
24456fdcbe | ||
|
|
25116ab179 | ||
|
|
a116d52e30 | ||
|
|
cecf5f127e | ||
|
|
1c467e15e6 | ||
|
|
a110542563 | ||
|
|
7d55724468 | ||
|
|
1d84c55743 | ||
|
|
b3fc530abe | ||
|
|
d0dfa54dcb | ||
|
|
198b836759 | ||
|
|
a75f46f06f | ||
|
|
2a91fd57c7 | ||
|
|
fe891293b2 | ||
|
|
6e450f75b4 | ||
|
|
22c985cae6 | ||
|
|
fea3d79a2c | ||
|
|
d3dde130b7 | ||
|
|
899de7f873 | ||
|
|
f1a3e8db2a | ||
|
|
dad82c3343 | ||
|
|
a6cbf6b205 | ||
|
|
580100d491 | ||
|
|
acb9cb421d | ||
|
|
6d5d7e4411 | ||
|
|
ddb3445f3d | ||
|
|
6eb9807f17 | ||
|
|
abe2394a24 | ||
|
|
7012ba6c64 | ||
|
|
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 | ||
|
|
e7d7582f84 | ||
|
|
df5f23d309 | ||
|
|
e018ba91eb | ||
|
|
c59aa6ff2c | ||
|
|
9431709298 | ||
|
|
a29525e043 | ||
|
|
5312e154b3 | ||
|
|
480b86f382 | ||
|
|
c0e2c3012f | ||
|
|
97b04c4152 | ||
|
|
c8306e207d | ||
|
|
de5c0b3554 | ||
|
|
57a0b24060 | ||
|
|
204576c006 | ||
|
|
66ac425bf7 | ||
|
|
a01e7e2256 | ||
|
|
47cfa5aadf | ||
|
|
ddd7f804af | ||
|
|
8c1c7a24ef | ||
|
|
09b130f4cf | ||
|
|
b662704b0b | ||
|
|
304a4285ff |
26
.github/ISSUE_TEMPLATE.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
*Note*: for support questions, please use one of these channels: [stackoverflow](http://stackoverflow.com/questions/tagged/socket.io) or [slack](https://socketio.slack.com)
|
||||
|
||||
### You want to:
|
||||
|
||||
* [x] report a *bug*
|
||||
* [ ] request a *feature*
|
||||
|
||||
### Current behaviour
|
||||
|
||||
|
||||
### Steps to reproduce (if the current behaviour is a bug)
|
||||
|
||||
**Note**: the best way to get a quick answer is to provide a failing test case, by forking the following [fiddle](https://github.com/darrachequesne/socket.io-fiddle) for example.
|
||||
|
||||
### Expected behaviour
|
||||
|
||||
|
||||
### Setup
|
||||
- OS:
|
||||
- browser:
|
||||
- socket.io version:
|
||||
|
||||
### Other information (e.g. stacktraces, related issues, suggestions how to fix)
|
||||
|
||||
|
||||
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
### The kind of change this PR does introduce
|
||||
|
||||
* [x] a bug fix
|
||||
* [ ] a new feature
|
||||
* [ ] an update to the documentation
|
||||
* [ ] a code change that improves performance
|
||||
* [ ] other
|
||||
|
||||
### Current behaviour
|
||||
|
||||
|
||||
### New behaviour
|
||||
|
||||
|
||||
### Other information (e.g. related issues)
|
||||
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,3 +8,6 @@ lib-cov
|
||||
*.pid
|
||||
benchmarks/*.png
|
||||
node_modules
|
||||
coverage
|
||||
.idea
|
||||
dist
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
support
|
||||
test
|
||||
examples
|
||||
@@ -1,7 +1,12 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.4
|
||||
- 0.6
|
||||
- "4"
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
notifications:
|
||||
irc: "irc.freenode.org#socket.io"
|
||||
|
||||
525
History.md
525
History.md
@@ -1,4 +1,529 @@
|
||||
|
||||
2.0.2 / 2017-06-01
|
||||
===================
|
||||
|
||||
* [fix] Fix timing issues with middleware (#2948)
|
||||
|
||||
2.0.1 / 2017-05-09
|
||||
===================
|
||||
|
||||
* [fix] Update path of client file (#2934)
|
||||
|
||||
2.0.0 / 2017-05-09
|
||||
===================
|
||||
|
||||
* [feat] Move binary detection to the parser (#2923)
|
||||
* [feat] Allow to join several rooms at once (#2879)
|
||||
* [feat] Merge Engine.IO and Socket.IO handshake packets (#2833)
|
||||
* [feat] Allow the use of custom parsers (#2829)
|
||||
|
||||
* [fix] Use path.resolve by default and require.resolve as a fallback (#2797)
|
||||
* [fix] Properly close the connection on error (#2681)
|
||||
* [fix] Prevent null from being accepted as argument (#2606)
|
||||
|
||||
* [perf] Use shared instance of the encoder (#2825)
|
||||
* [perf] Reset properties instead of deleting them (#2826)
|
||||
* [perf] micro-optimisations (#2793)
|
||||
|
||||
* [chore] Merge history of 1.7.x and 0.9.x branches (#2930)
|
||||
* [chore] Added backers and sponsors on the README (#2933)
|
||||
* [chore] Bump dependencies (#2926)
|
||||
* [chore] Bump socket.io-adapter to version 1.0.0 (#2867)
|
||||
* [chore] Bump engine.io to version 2.0.2 (#2864)
|
||||
* [chore] Bump engine.io to version 2.0.0 (#2832)
|
||||
* [chore] Update issue template with fiddle (#2811)
|
||||
* [chore] Update copyright year LICENSE to 2017 (#2803)
|
||||
|
||||
* [docs] Add an example of custom parser (#2929)
|
||||
* [docs] Replace non-breaking space with proper whitespace (#2913)
|
||||
* [docs] Update emit cheatsheet (#2906)
|
||||
* [docs] Explicitly document that Server extends EventEmitter (#2874)
|
||||
* [docs] Add server.engine.generateId attribute (#2880)
|
||||
* [docs] Fix wrong space character in README (#2900)
|
||||
* [docs] Fix documentation for 'connect' event (#2898)
|
||||
* [docs] Add webpack build example (#2828)
|
||||
* [docs] Update the wording to match the code example (#2853)
|
||||
* [docs] Small addition to the Express Readme Part (#2846)
|
||||
* [docs] Add a 'Features' section in the README (#2824)
|
||||
* [docs] Add httpd cluster example (#2819)
|
||||
* [docs] Add haproxy cluster example (#2818)
|
||||
* [docs] Add nginx cluster example (#2817)
|
||||
* [docs] Implement whiteboard example (#2810)
|
||||
* [docs] Fix documentation for `local` flag (#2816)
|
||||
* [docs] Add emit cheatsheet (#2815)
|
||||
* [docs] Add pingInterval/pingTimeout/transports options in the API documentation (#2814)
|
||||
* [docs] Add an example for socket.join() method (#2813)
|
||||
* [docs] Fix a typo on `clients` method in the API documentation (#2812)
|
||||
* [docs] Fix wrong argument name in API.md (#2802)
|
||||
* [docs] Add install script on Readme.md (#2780)
|
||||
* [docs] API documentation (#2784)
|
||||
|
||||
1.7.4 / 2017-05-07
|
||||
===================
|
||||
|
||||
* [chore] Bump engine.io to version 1.8.4
|
||||
|
||||
0.9.18 / 2017-05-07
|
||||
===================
|
||||
|
||||
* Remove process.EventEmitter usage for Node 7.x
|
||||
|
||||
1.7.3 / 2017-02-17
|
||||
===================
|
||||
|
||||
* [chore] Bump engine.io to version 1.8.3
|
||||
|
||||
1.7.2 / 2016-12-11
|
||||
===================
|
||||
|
||||
* [chore] Bump engine.io to version 1.8.2 (#2782)
|
||||
* [fix] Fixes socket.use error packet (#2772)
|
||||
|
||||
1.7.1 / 2016-11-28
|
||||
===================
|
||||
|
||||
1.7.0 / 2016-11-27
|
||||
===================
|
||||
|
||||
* [docs] Comment connected socket availability for adapters (#2081)
|
||||
* [docs] Fixed grammar issues in the README.md (#2159)
|
||||
* [feature] serve sourcemap for socket.io-client (#2482)
|
||||
* [feature] Add a `local` flag (#2628)
|
||||
* [chore] Bump engine.io to version 1.8.1 (#2765)
|
||||
* [chore] Update client location and serve minified file (#2766)
|
||||
|
||||
1.6.0 / 2016-11-20
|
||||
==================
|
||||
|
||||
* [fix] Make ETag header comply with standard. (#2603)
|
||||
* [feature] Loading client script on demand. (#2567)
|
||||
* [test] Fix leaking clientSocket (#2721)
|
||||
* [feature] Add support for all event emitter methods (#2601)
|
||||
* [chore] Update year to 2016 (#2456)
|
||||
* [feature] Add support for socket middleware (#2306)
|
||||
* [feature] add support for Server#close(callback) (#2748)
|
||||
* [fix] Don't drop query variables on handshake (#2745)
|
||||
* [example] Add disconnection/reconnection logs to the chat example (#2675)
|
||||
* [perf] Minor code optimizations (#2219)
|
||||
* [chore] Bump debug to version 2.3.3 (#2754)
|
||||
* [chore] Bump engine.io to version 1.8.0 (#2755)
|
||||
* [chore] Bump socket.io-adapter to version 0.5.0 (#2756)
|
||||
|
||||
1.5.1 / 2016-10-24
|
||||
==================
|
||||
|
||||
* [fix] Avoid swallowing exceptions thrown by user event handlers (#2682)
|
||||
* [test] Use client function to unify `client` in test script (#2731)
|
||||
* [docs] Add link to LICENSE (#2221)
|
||||
* [docs] Fix JSDoc of optional parameters (#2465)
|
||||
* [docs] Fix typo (#2724)
|
||||
* [docs] Link readme npm package badge to npm registry page (#2612)
|
||||
* [docs] Minor fixes (#2526)
|
||||
* [chore] Bump socket.io-parser to 2.3.0 (#2730)
|
||||
* [chore] Add Github issue and PR templates (#2733)
|
||||
* [chore] Bump engine.io to 1.7.2 (#2729)
|
||||
* [chore] Bump socket.io-parser to 2.3.1 (#2734)
|
||||
|
||||
1.5.0 / 2016-10-06
|
||||
==================
|
||||
|
||||
* [feature] stop append /# before id when no namespace (#2508)
|
||||
* [feature] Add a 'disconnecting' event to access to socket.rooms upon disconnection (#2332)
|
||||
* [fix] Fix query string management (#2422)
|
||||
* [fix] add quote to exec paths, prevent error when spaces in path (#2508)
|
||||
* [docs] Prevent mixup for new programmers (#2599)
|
||||
* [example] Fix chat display in Firefox (#2477)
|
||||
* [chore] Add gulp & babel in the build process (#2471)
|
||||
* [chore] Bump engine.io to 1.7.0 (#2707)
|
||||
* [chore] Remove unused zuul-ngrok dependency (#2708)
|
||||
* [chore] Point towards current master of socket.io-client (#2710)
|
||||
* [chore] Restrict files included in npm package (#2709)
|
||||
* [chore] Link build badge to master branch (#2549)
|
||||
|
||||
1.4.8 / 2016-06-23
|
||||
==================
|
||||
|
||||
* package: bump `engine.io`
|
||||
|
||||
1.4.7 / 2016-06-23
|
||||
==================
|
||||
|
||||
* package: bump `engine.io`
|
||||
|
||||
1.4.6 / 2016-05-02
|
||||
==================
|
||||
|
||||
* package: bump engine.io
|
||||
|
||||
1.4.5 / 2016-01-26
|
||||
==================
|
||||
|
||||
* fix closing the underlying `http.Server`
|
||||
|
||||
1.4.4 / 2016-01-10
|
||||
==================
|
||||
|
||||
* package: bump `engine.io`
|
||||
|
||||
1.4.3 / 2016-01-08
|
||||
==================
|
||||
|
||||
* bump `socket.io-client`
|
||||
|
||||
1.4.2 / 2016-01-07
|
||||
==================
|
||||
|
||||
* bump `engine.io`
|
||||
|
||||
1.4.1 / 2016-01-07
|
||||
==================
|
||||
|
||||
* version bump
|
||||
|
||||
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]
|
||||
* changed type of `Client#sockets`, `Namespace#sockets` and `Socket#rooms` to maps (instead of arrays)
|
||||
|
||||
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
|
||||
==================
|
||||
|
||||
* More sensible close `timeout default` (fixes disconnect issue)
|
||||
|
||||
0.9.1-1 / 2012-03-02
|
||||
====================
|
||||
|
||||
* Bumped client with NPM dependency fix.
|
||||
|
||||
0.9.1 / 2012-03-02
|
||||
==================
|
||||
|
||||
* Changed heartbeat timeout and interval defaults (60 and 25 seconds)
|
||||
* Make tests work both on 0.4 and 0.6
|
||||
* Updated client (improvements + bug fixes).
|
||||
|
||||
0.9.0 / 2012-02-26
|
||||
==================
|
||||
|
||||
* Make it possible to use a regexp to match the socket.io resource URL.
|
||||
We need this because we have to prefix the socket.io URL with a variable ID.
|
||||
* Supplemental fix to gavinuhma/authfix, it looks like the same Access-Control-Origin logic is needed in the http and xhr-polling transports
|
||||
* Updated express dep for windows compatibility.
|
||||
* Combine two substr calls into one in decodePayload to improve performance
|
||||
* Minor documentation fix
|
||||
* Minor. Conform to style of other files.
|
||||
* Switching setting to 'match origin protocol'
|
||||
* Revert "Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect()."
|
||||
* Revert "Handle leaked dispatch:[id] subscription."
|
||||
* Merge pull request #667 from dshaw/patch/redis-disconnect
|
||||
* Handle leaked dispatch:[id] subscription.
|
||||
* Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect().
|
||||
* Prevent memory leaking on uncompleted requests & add max post size limitation
|
||||
* Fix for testcase
|
||||
* Set Access-Control-Allow-Credentials true, regardless of cookie
|
||||
* Remove assertvarnish from package as it breaks on 0.6
|
||||
* Correct irc channel
|
||||
* Added proper return after reserved field error
|
||||
* Fixes manager.js failure to close connection after transport error has happened
|
||||
* Added implicit port 80 for origin checks. fixes #638
|
||||
* Fixed bug #432 in 0.8.7
|
||||
* Set Access-Control-Allow-Origin header to origin to enable withCredentials
|
||||
* Adding configuration variable matchOriginProtocol
|
||||
* Fixes location mismatch error in Safari.
|
||||
* Use tty to detect if we should add colors or not by default.
|
||||
* Updated the package location.
|
||||
|
||||
0.8.7 / 2011-11-05
|
||||
==================
|
||||
|
||||
|
||||
22
LICENSE
Normal file
22
LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014-2017 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.
|
||||
29
Makefile
29
Makefile
@@ -1,31 +1,8 @@
|
||||
|
||||
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)
|
||||
|
||||
test:
|
||||
@$(MAKE) NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests
|
||||
@./node_modules/.bin/gulp test
|
||||
|
||||
test-cov:
|
||||
@TESTFLAGS=--cov $(MAKE) test
|
||||
@./node_modules/.bin/gulp test-cov
|
||||
|
||||
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
|
||||
|
||||
493
Readme.md
493
Readme.md
@@ -1,343 +1,242 @@
|
||||
# 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
|
||||
[](#backers) [](#sponsors)
|
||||
[](https://travis-ci.org/socketio/socket.io)
|
||||
[](https://david-dm.org/socketio/socket.io)
|
||||
[](https://david-dm.org/socketio/socket.io#info=devDependencies)
|
||||
[](https://www.npmjs.com/package/socket.io)
|
||||

|
||||
[](http://slack.socket.io)
|
||||
|
||||
npm install socket.io
|
||||
## Features
|
||||
|
||||
Socket.IO enables real-time bidirectional event-based communication. It consists in:
|
||||
|
||||
- a Node.js server (this repository)
|
||||
- a [Javascript client library](https://github.com/socketio/socket.io-client) for the browser (or a Node.js client)
|
||||
|
||||
Some implementations in other languages are also available:
|
||||
|
||||
- [Java](https://github.com/socketio/socket.io-client-java)
|
||||
- [C++](https://github.com/socketio/socket.io-client-cpp)
|
||||
- [Swift](https://github.com/socketio/socket.io-client-swift)
|
||||
|
||||
Its main features are:
|
||||
|
||||
#### Reliability
|
||||
|
||||
Connections are established even in the presence of:
|
||||
- proxies and load balancers.
|
||||
- personal firewall and antivirus software.
|
||||
|
||||
For this purpose, it relies on [Engine.IO](https://github.com/socketio/engine.io), which first establishes a long-polling connection, then tries to upgrade to better transports that are "tested" on the side, like WebSocket. Please see the [Goals](https://github.com/socketio/engine.io#goals) section for more information.
|
||||
|
||||
#### Auto-reconnection support
|
||||
|
||||
Unless instructed otherwise a disconnected client will try to reconnect forever, until the server is available again. Please see the available reconnection options [here](https://github.com/socketio/socket.io-client/blob/master/docs/API.md#new-managerurl-options).
|
||||
|
||||
#### Disconnection detection
|
||||
|
||||
An heartbeat mechanism is implemented at the Engine.IO level, allowing both the server and the client to know when the other one is not responding anymore.
|
||||
|
||||
That functionality is achieved with timers set on both the server and the client, with timeout values (the `pingInterval` and `pingTimeout` parameters) shared during the connection handshake. Those timers require any subsequent client calls to be directed to the same server, hence the `sticky-session` requirement when using multiples nodes.
|
||||
|
||||
#### Binary support
|
||||
|
||||
Any serializable data structures can be emitted, including:
|
||||
|
||||
- [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) and [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) in the browser
|
||||
- [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) and [Buffer](https://nodejs.org/api/buffer.html) in Node.js
|
||||
|
||||
#### Simple and convenient API
|
||||
|
||||
Sample code:
|
||||
|
||||
```js
|
||||
io.on('connection', function(socket){
|
||||
socket.emit('request', /* */); // emit an event to the socket
|
||||
io.emit('broadcast', /* */); // emit an event to all connected sockets
|
||||
socket.on('reply', function(){ /* */ }); // listen to the event
|
||||
});
|
||||
```
|
||||
|
||||
#### Cross-browser
|
||||
|
||||
Browser support is tested in Saucelabs:
|
||||
|
||||
[](https://saucelabs.com/u/socket)
|
||||
|
||||
#### Multiplexing support
|
||||
|
||||
In order to create separation of concerns within your application (for example per module, or based on permissions), Socket.IO allows you to create several `Namespaces`, which will act as separate communication channels but will share the same underlying connection.
|
||||
|
||||
#### Room support
|
||||
|
||||
Within each `Namespace`, you can define arbitrary channels, called `Rooms`, that sockets can join and leave. You can then broadcast to any given room, reaching every socket that has joined it.
|
||||
|
||||
This is a useful feature to send notifications to a group of users, or to a given user connected on several devices for example.
|
||||
|
||||
|
||||
**Note:** Socket.IO is not a WebSocket implementation. Although Socket.IO indeed uses WebSocket as a transport when possible, it adds some metadata to each packet: the packet type, the namespace and the ack id when a message acknowledgement is needed. That is why a WebSocket client will not be able to successfully connect to a Socket.IO server, and a Socket.IO client will not be able to connect to a WebSocket server (like `ws://echo.websocket.org`) either. Please see the protocol specification [here](https://github.com/socketio/socket.io-protocol).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install socket.io --save
|
||||
```
|
||||
|
||||
## 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(client){
|
||||
client.on('event', function(data){});
|
||||
client.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(client){});
|
||||
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. Also make sure to call `.listen` on the `server`, not the `app`.
|
||||
|
||||
```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 chat = io
|
||||
.of('/chat')
|
||||
.on('connection', function (socket) {
|
||||
socket.emit('a message', { that: 'only', '/chat': 'will get' });
|
||||
chat.emit('a message', { everyone: 'in', '/chat': 'will get' });
|
||||
});
|
||||
|
||||
var news = io
|
||||
.of('/news');
|
||||
.on('connection', function (socket) {
|
||||
socket.emit('item', { news: 'item' });
|
||||
});
|
||||
var app = require('koa')();
|
||||
var server = require('http').createServer(app.callback());
|
||||
var io = require('socket.io')(server);
|
||||
io.on('connection', function(){ /* … */ });
|
||||
server.listen(3000);
|
||||
```
|
||||
|
||||
#### Client side:
|
||||
## Documentation
|
||||
|
||||
```html
|
||||
<script>
|
||||
var chat = io.connect('http://localhost/chat')
|
||||
, news = io.connect('http://localhost/news');
|
||||
Please see the documentation [here](/docs/README.md). Contributions are welcome!
|
||||
|
||||
chat.on('connect', function () {
|
||||
chat.emit('hi!');
|
||||
});
|
||||
## Debug / logging
|
||||
|
||||
news.on('news', function () {
|
||||
news.emit('woot');
|
||||
});
|
||||
</script>
|
||||
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
|
||||
```
|
||||
|
||||
### Sending volatile messages.
|
||||
## Testing
|
||||
|
||||
Sometimes certain messages can be dropped. Let's say you have an app that
|
||||
shows realtime tweets for the keyword `bieber`.
|
||||
|
||||
If a certain client is not ready to receive messages (because of network slowness
|
||||
or other issues, or because he's connected through long polling and is in the
|
||||
middle of a request-response cycle), if he doesn't receive ALL the tweets related
|
||||
to bieber your application won't suffer.
|
||||
|
||||
In that case, you might want to send those messages as volatile messages.
|
||||
|
||||
#### Server side
|
||||
|
||||
```js
|
||||
var io = require('socket.io').listen(80);
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
var tweets = setInterval(function () {
|
||||
getBieberTweet(function (tweet) {
|
||||
socket.volatile.emit('bieber tweet', tweet);
|
||||
});
|
||||
}, 100);
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
clearInterval(tweets);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### Client side
|
||||
|
||||
In the client side, messages are received the same way whether they're volatile
|
||||
or not.
|
||||
|
||||
### Getting acknowledgements
|
||||
|
||||
Sometimes, you might want to get a callback when the client confirmed the message
|
||||
reception.
|
||||
|
||||
To do this, simply pass a function as the last parameter of `.send` or `.emit`.
|
||||
What's more, when you use `.emit`, the acknowledgement is done by you, which
|
||||
means you can also pass data along:
|
||||
|
||||
#### Server side
|
||||
|
||||
```js
|
||||
var io = require('socket.io').listen(80);
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.on('ferret', function (name, fn) {
|
||||
fn('woot');
|
||||
});
|
||||
});
|
||||
npm test
|
||||
```
|
||||
This runs the `gulp` task `test`. By default the test will be run with the source code in `lib` directory.
|
||||
|
||||
#### Client side
|
||||
Set the environmental variable `TEST_VERSION` to `compat` to test the transpiled es5-compat version of the code.
|
||||
|
||||
```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'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
```
|
||||
The `gulp` task `test` will always transpile the source code into es5 and export to `dist` first before running the test.
|
||||
|
||||
### Broadcasting messages
|
||||
|
||||
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.
|
||||
## Backers
|
||||
|
||||
#### Server side
|
||||
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/socketio#backer)]
|
||||
|
||||
```js
|
||||
var io = require('socket.io').listen(80);
|
||||
<a href="https://opencollective.com/socketio/backer/0/website" target="_blank"><img src="https://opencollective.com/socketio/backer/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/1/website" target="_blank"><img src="https://opencollective.com/socketio/backer/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/2/website" target="_blank"><img src="https://opencollective.com/socketio/backer/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/3/website" target="_blank"><img src="https://opencollective.com/socketio/backer/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/4/website" target="_blank"><img src="https://opencollective.com/socketio/backer/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/5/website" target="_blank"><img src="https://opencollective.com/socketio/backer/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/6/website" target="_blank"><img src="https://opencollective.com/socketio/backer/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/7/website" target="_blank"><img src="https://opencollective.com/socketio/backer/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/8/website" target="_blank"><img src="https://opencollective.com/socketio/backer/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/9/website" target="_blank"><img src="https://opencollective.com/socketio/backer/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/10/website" target="_blank"><img src="https://opencollective.com/socketio/backer/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/11/website" target="_blank"><img src="https://opencollective.com/socketio/backer/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/12/website" target="_blank"><img src="https://opencollective.com/socketio/backer/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/13/website" target="_blank"><img src="https://opencollective.com/socketio/backer/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/14/website" target="_blank"><img src="https://opencollective.com/socketio/backer/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/15/website" target="_blank"><img src="https://opencollective.com/socketio/backer/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/16/website" target="_blank"><img src="https://opencollective.com/socketio/backer/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/17/website" target="_blank"><img src="https://opencollective.com/socketio/backer/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/18/website" target="_blank"><img src="https://opencollective.com/socketio/backer/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/19/website" target="_blank"><img src="https://opencollective.com/socketio/backer/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/20/website" target="_blank"><img src="https://opencollective.com/socketio/backer/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/21/website" target="_blank"><img src="https://opencollective.com/socketio/backer/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/22/website" target="_blank"><img src="https://opencollective.com/socketio/backer/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/23/website" target="_blank"><img src="https://opencollective.com/socketio/backer/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/24/website" target="_blank"><img src="https://opencollective.com/socketio/backer/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/25/website" target="_blank"><img src="https://opencollective.com/socketio/backer/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/26/website" target="_blank"><img src="https://opencollective.com/socketio/backer/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/27/website" target="_blank"><img src="https://opencollective.com/socketio/backer/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/28/website" target="_blank"><img src="https://opencollective.com/socketio/backer/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/backer/29/website" target="_blank"><img src="https://opencollective.com/socketio/backer/29/avatar.svg"></a>
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.broadcast.emit('user connected');
|
||||
socket.broadcast.json.send({ a: 'message' });
|
||||
});
|
||||
```
|
||||
|
||||
### Rooms
|
||||
## Sponsors
|
||||
|
||||
Sometimes you want to put certain sockets in the same room, so that it's easy
|
||||
to broadcast to all of them together.
|
||||
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/socketio#sponsor)]
|
||||
|
||||
Think of this as built-in channels for sockets. Sockets `join` and `leave`
|
||||
rooms in each socket.
|
||||
<a href="https://opencollective.com/socketio/sponsor/0/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/1/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/2/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/3/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/4/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/5/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/6/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/7/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/8/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/9/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/10/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/11/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/12/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/13/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/14/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/15/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/16/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/17/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/18/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/19/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/20/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/21/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/22/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/23/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/24/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/25/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/26/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/27/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/28/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/socketio/sponsor/29/website" target="_blank"><img src="https://opencollective.com/socketio/sponsor/29/avatar.svg"></a>
|
||||
|
||||
#### Server side
|
||||
|
||||
```js
|
||||
var io = require('socket.io').listen(80);
|
||||
## License
|
||||
|
||||
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](LICENSE)
|
||||
|
||||
@@ -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();
|
||||
619
docs/API.md
Normal file
619
docs/API.md
Normal file
@@ -0,0 +1,619 @@
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Class: Server](#server)
|
||||
- [new Server(httpServer[, options])](#new-serverhttpserver-options)
|
||||
- [new Server(port[, options])](#new-serverport-options)
|
||||
- [new Server(options)](#new-serveroptions)
|
||||
- [server.sockets](#serversockets)
|
||||
- [server.engine.generateId](#serverenginegenerateid)
|
||||
- [server.serveClient([value])](#serverserveclientvalue)
|
||||
- [server.path([value])](#serverpathvalue)
|
||||
- [server.adapter([value])](#serveradaptervalue)
|
||||
- [server.origins([value])](#serveroriginsvalue)
|
||||
- [server.origins(fn)](#serveroriginsfn)
|
||||
- [server.attach(httpServer[, options])](#serverattachhttpserver-options)
|
||||
- [server.attach(port[, options])](#serverattachport-options)
|
||||
- [server.listen(httpServer[, options])](#serverlistenhttpserver-options)
|
||||
- [server.listen(port[, options])](#serverlistenport-options)
|
||||
- [server.bind(engine)](#serverbindengine)
|
||||
- [server.onconnection(socket)](#serveronconnectionsocket)
|
||||
- [server.of(nsp)](#serverofnsp)
|
||||
- [server.close([callback])](#serverclosecallback)
|
||||
- [Class: Namespace](#namespace)
|
||||
- [namespace.name](#namespacename)
|
||||
- [namespace.connected](#namespaceconnected)
|
||||
- [namespace.emit(eventName[, ...args])](#namespaceemiteventname-args)
|
||||
- [namespace.clients(callback)](#namespaceclientscallback)
|
||||
- [namespace.use(fn)](#namespaceusefn)
|
||||
- [Event: 'connect'](#event-connect)
|
||||
- [Event: 'connection'](#event-connect)
|
||||
- [Flag: 'volatile'](#flag-volatile)
|
||||
- [Flag: 'local'](#flag-local)
|
||||
- [Class: Socket](#socket)
|
||||
- [socket.id](#socketid)
|
||||
- [socket.rooms](#socketrooms)
|
||||
- [socket.client](#socketclient)
|
||||
- [socket.conn](#socketconn)
|
||||
- [socket.request](#socketrequest)
|
||||
- [socket.use(fn)](#socketusefn)
|
||||
- [socket.send([...args][, ack])](#socketsendargs-ack)
|
||||
- [socket.emit(eventName[, ...args][, ack])](#socketemiteventname-args-ack)
|
||||
- [socket.on(eventName, callback)](#socketoneventname-callback)
|
||||
- [socket.once(eventName, listener)](#socketonceeventname-listener)
|
||||
- [socket.removeListener(eventName, listener)](#socketremovelistenereventname-listener)
|
||||
- [socket.removeAllListeners([eventName])](#socketremovealllistenerseventname)
|
||||
- [socket.eventNames()](#socketeventnames)
|
||||
- [socket.join(room[, callback])](#socketjoinroom-callback)
|
||||
- [socket.join(rooms[, callback])](#socketjoinrooms-callback)
|
||||
- [socket.leave(room[, callback])](#socketleaveroom-callback)
|
||||
- [socket.to(room)](#sockettoroom)
|
||||
- [socket.in(room)](#socketinroom)
|
||||
- [socket.compress(value)](#socketcompressvalue)
|
||||
- [socket.disconnect(close)](#socketdisconnectclose)
|
||||
- [Flag: 'broadcast'](#flag-broadcast)
|
||||
- [Flag: 'volatile'](#flag-volatile-1)
|
||||
- [Event: 'disconnect'](#event-disconnect)
|
||||
- [Event: 'error'](#event-error)
|
||||
- [Event: 'disconnecting'](#event-disconnecting)
|
||||
- [Class: Client](#client)
|
||||
- [client.conn](#clientconn)
|
||||
- [client.request](#clientrequest)
|
||||
|
||||
|
||||
### Server
|
||||
|
||||
Exposed by `require('socket.io')`.
|
||||
|
||||
#### new Server(httpServer[, options])
|
||||
|
||||
- `httpServer` _(http.Server)_ the server to bind to.
|
||||
- `options` _(Object)_
|
||||
- `path` _(String)_: name of the path to capture (`/socket.io`)
|
||||
- `serveClient` _(Boolean)_: whether to serve the client files (`true`)
|
||||
- `adapter` _(Adapter)_: the adapter to use. 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)
|
||||
- `origins` _(String)_: the allowed origins (`*`)
|
||||
- `allowRequest` _(Function)_: A function that receives a given handshake or upgrade request as its first parameter, and can decide whether to continue or not. The second argument is a function that needs to be called with the decided information: `fn(err, success)`, where `success` is a boolean value where false means that the request is rejected, and err is an error code.
|
||||
- `parser` _(Parser)_: the parser to use. Defaults to an instance of the `Parser` that ships with socket.io. See [socket.io-parser](https://github.com/socketio/socket.io-parser).
|
||||
|
||||
Works with and without `new`:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
// or
|
||||
var Server = require('socket.io');
|
||||
var io = new Server();
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Among those options:
|
||||
|
||||
- `pingTimeout` _(Number)_: how many ms without a pong packet to consider the connection closed (`60000`)
|
||||
- `pingInterval` _(Number)_: how many ms before sending a new ping packet (`25000`).
|
||||
|
||||
Those two parameters will impact the delay before a client knows the server is not available anymore. For example, if the underlying TCP connection is not closed properly due to a network issue, a client may have to wait up to `pingTimeout + pingInterval` ms before getting a `disconnect` event.
|
||||
|
||||
- `transports` _(Array<String>)_: transports to allow connections to (`['polling', 'websocket']`).
|
||||
|
||||
**Note:** The order is important. By default, a long-polling connection is established first, and then upgraded to WebSocket if possible. Using `['websocket']` means there will be no fallback if a WebSocket connection cannot be opened.
|
||||
|
||||
#### new Server(port[, options])
|
||||
|
||||
- `port` _(Number)_ a port to listen to (a new `http.Server` will be created)
|
||||
- `options` _(Object)_
|
||||
|
||||
See [above](#new-serverhttpserver-options) for available options.
|
||||
|
||||
#### new Server(options)
|
||||
|
||||
- `options` _(Object)_
|
||||
|
||||
See [above](#new-serverhttpserver-options) for available options.
|
||||
|
||||
#### server.sockets
|
||||
|
||||
* _(Namespace)_
|
||||
|
||||
The default (`/`) namespace.
|
||||
|
||||
#### server.serveClient([value])
|
||||
|
||||
- `value` _(Boolean)_
|
||||
- **Returns** `Server|Boolean`
|
||||
|
||||
If `value` 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. If no arguments are supplied this method returns the current value.
|
||||
|
||||
```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);
|
||||
```
|
||||
|
||||
#### server.path([value])
|
||||
|
||||
- `value` _(String)_
|
||||
- **Returns** `Server|String`
|
||||
|
||||
Sets the path `value` 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([value])
|
||||
|
||||
- `value` _(Adapter)_
|
||||
- **Returns** `Server|Adapter`
|
||||
|
||||
Sets the adapter `value`. 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([value])
|
||||
|
||||
- `value` _(String)_
|
||||
- **Returns** `Server|String`
|
||||
|
||||
Sets the allowed origins `value`. Defaults to any origins being allowed. If no arguments are supplied this method returns the current value.
|
||||
|
||||
#### server.origins(fn)
|
||||
|
||||
- `fn` _(Function)_
|
||||
- **Returns** `Server`
|
||||
|
||||
Provides a function taking 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.attach(httpServer[, options])
|
||||
|
||||
- `httpServer` _(http.Server)_ the server to attach to
|
||||
- `options` _(Object)_
|
||||
|
||||
Attaches the `Server` to an engine.io instance on `httpServer` with the supplied `options` (optionally).
|
||||
|
||||
### server.attach(port[, options])
|
||||
|
||||
- `port` _(Number)_ the port to listen on
|
||||
- `options` _(Object)_
|
||||
|
||||
Attaches the `Server` to an engine.io instance on a new http.Server with the supplied `options` (optionally).
|
||||
|
||||
#### server.listen(httpServer[, options])
|
||||
|
||||
Synonym of [server.attach(httpServer[, options])](#serverattachhttpserver-options).
|
||||
|
||||
#### server.listen(port[, options])
|
||||
|
||||
Synonym of [server.attach(port[, options])](#serverattachport-options).
|
||||
|
||||
#### server.bind(engine)
|
||||
|
||||
- `engine` _(engine.Server)_
|
||||
- **Returns** `Server`
|
||||
|
||||
Advanced use only. Binds the server to a specific engine.io `Server` (or compatible API) instance.
|
||||
|
||||
#### server.onconnection(socket)
|
||||
|
||||
- `socket` _(engine.Socket)_
|
||||
- **Returns** `Server`
|
||||
|
||||
Advanced use only. Creates a new `socket.io` client from the incoming engine.io (or compatible API) `Socket`.
|
||||
|
||||
#### server.of(nsp)
|
||||
|
||||
- `nsp` _(String)_
|
||||
- **Returns** `Namespace`
|
||||
|
||||
Initializes and retrieves the given `Namespace` by its pathname identifier `nsp`. If the namespace was already initialized it returns it immediately.
|
||||
|
||||
#### server.close([callback])
|
||||
|
||||
- `callback` _(Function)_
|
||||
|
||||
Closes the socket.io server. The `callback` argument is optional and will be called when all connections are closed.
|
||||
|
||||
```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.engine.generateId
|
||||
|
||||
Overwrites the default method to generate your custom socket id.
|
||||
|
||||
The function is called with a node request object (`http.IncomingMessage`) as first parameter.
|
||||
|
||||
```js
|
||||
io.engine.generateId = function (req) {
|
||||
return "custom:id:" + custom_id++; // custom id must be unique
|
||||
}
|
||||
```
|
||||
|
||||
### Namespace
|
||||
|
||||
Represents a pool of sockets connected under a given scope identified
|
||||
by a pathname (eg: `/chat`).
|
||||
|
||||
By default the client always connects to `/`.
|
||||
|
||||
#### namespace.name
|
||||
|
||||
* _(String)_
|
||||
|
||||
The namespace identifier property.
|
||||
|
||||
#### namespace.connected
|
||||
|
||||
* _(Object<Socket>)_
|
||||
|
||||
The hash of `Socket` objects that are connected to this namespace, indexed by `id`.
|
||||
|
||||
#### namespace.emit(eventName[, ...args])
|
||||
|
||||
- `eventName` _(String)_
|
||||
- `args`
|
||||
|
||||
Emits an event to all connected clients. The following two are equivalent:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
|
||||
io.emit('an event sent to all connected clients'); // main namespace
|
||||
|
||||
var chat = io.of('/chat');
|
||||
chat.emit('an event sent to all connected clients in chat namespace');
|
||||
```
|
||||
|
||||
#### namespace.clients(callback)
|
||||
|
||||
- `callback` _(Function)_
|
||||
|
||||
Gets a list of client IDs connected to this namespace (across all nodes if applicable).
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.of('/chat').clients(function(error, clients){
|
||||
if (error) throw error;
|
||||
console.log(clients); // => [PZDoMHjiu8PYfRiKAAAF, Anw2LatarvGVVXEIAAAD]
|
||||
});
|
||||
```
|
||||
|
||||
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]
|
||||
});
|
||||
```
|
||||
|
||||
As with broadcasting, the default is all clients from the default namespace ('/'):
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.clients(function(error, clients){
|
||||
if (error) throw error;
|
||||
console.log(clients); // => [6em3d4TJP8Et9EMNAAAA, G5p55dHhGgUnLUctAAAB]
|
||||
});
|
||||
```
|
||||
|
||||
#### namespace.use(fn)
|
||||
|
||||
- `fn` _(Function)_
|
||||
|
||||
Registers a middleware, which is a function that gets executed for every incoming `Socket`, and receives as parameters the socket and a function to optionally defer execution to the next registered middleware.
|
||||
|
||||
Errors passed to middleware callbacks are sent as special `error` packets to clients.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.use(function(socket, next){
|
||||
if (socket.request.headers.cookie) return next();
|
||||
next(new Error('Authentication error'));
|
||||
});
|
||||
```
|
||||
|
||||
#### Event: 'connect'
|
||||
|
||||
- `socket` _(Socket)_ socket connection with client
|
||||
|
||||
Fired upon a connection from client.
|
||||
|
||||
#### Event: 'connection'
|
||||
|
||||
Synonym of [Event: 'connect'](#event-connect).
|
||||
|
||||
#### Flag: 'volatile'
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event data may be lost if the clients are not ready to receive messages (because of network slowness or other issues, or because they’re connected through long polling and is in the middle of a request-response cycle).
|
||||
|
||||
```js
|
||||
io.volatile.emit('an event', { some: 'data' }); // the clients may or may not receive it
|
||||
```
|
||||
|
||||
#### Flag: 'local'
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event data will only be _broadcast_ to the current node (when the [Redis adapter](https://github.com/socketio/socket.io-redis) is used).
|
||||
|
||||
```js
|
||||
io.local.emit('an event', { some: 'data' });
|
||||
```
|
||||
|
||||
### Socket
|
||||
|
||||
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.
|
||||
|
||||
It should be noted the `Socket` doesn't relate directly to the actual underlying TCP/IP `socket` and it is only the name of the class.
|
||||
|
||||
Within each `Namespace`, you can also define arbitrary channels (called `room`) that the `Socket` can join and leave. That provides a convenient way to broadcast to a group of `Socket`s (see `Socket#to` below).
|
||||
|
||||
The `Socket` class inherits from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). The `Socket` class overrides the `emit` method, and does not modify any other `EventEmitter` method. All methods documented here which also appear as `EventEmitter` methods (apart from `emit`) are implemented by `EventEmitter`, and documentation for `EventEmitter` applies.
|
||||
|
||||
#### socket.id
|
||||
|
||||
* _(String)_
|
||||
|
||||
A unique identifier for the session, that comes from the underlying `Client`.
|
||||
|
||||
#### socket.rooms
|
||||
|
||||
* _(Object)_
|
||||
|
||||
A hash of strings identifying the rooms this client is in, indexed by room name.
|
||||
|
||||
#### socket.client
|
||||
|
||||
* _(Client)_
|
||||
|
||||
A reference to the underlying `Client` object.
|
||||
|
||||
#### socket.conn
|
||||
|
||||
* _(engine.Socket)_
|
||||
|
||||
A reference to the underlying `Client` transport connection (engine.io `Socket` object). This allows access to the IO transport layer, which still (mostly) abstracts the actual TCP/IP socket.
|
||||
|
||||
#### 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.use(fn)
|
||||
|
||||
- `fn` _(Function)_
|
||||
|
||||
Registers a middleware, which is a function that gets executed for every incoming `Packet` and receives as parameter the packet and a function to optionally defer execution to the next registered middleware.
|
||||
|
||||
Errors passed to middleware callbacks are sent as special `error` packets to clients.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.use(function(packet, next){
|
||||
if (packet.doge === true) return next();
|
||||
next(new Error('Not a doge error'));
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### socket.send([...args][, ack])
|
||||
|
||||
- `args`
|
||||
- `ack` _(Function)_
|
||||
- **Returns** `Socket`
|
||||
|
||||
Sends a `message` event. See [socket.emit(eventName[, ...args][, ack])](#socketemiteventname-args-ack).
|
||||
|
||||
#### socket.emit(eventName[, ...args][, ack])
|
||||
|
||||
*(overrides `EventEmitter.emit`)*
|
||||
- `eventName` _(String)_
|
||||
- `args`
|
||||
- `ack` _(Function)_
|
||||
- **Returns** `Socket`
|
||||
|
||||
Emits an event to the socket identified by the string name. Any other parameters can be included. All serializable datastructures are supported, including `Buffer`.
|
||||
|
||||
```js
|
||||
socket.emit('hello', 'world');
|
||||
socket.emit('with-binary', 1, '2', { 3: '4', 5: new Buffer(6) });
|
||||
```
|
||||
|
||||
The `ack` argument is optional and will be called with the client's answer.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(client){
|
||||
client.emit('an event', { some: 'data' });
|
||||
|
||||
client.emit('ferret', 'tobi', function (data) {
|
||||
console.log(data); // data will be 'woot'
|
||||
});
|
||||
|
||||
// the client code
|
||||
// client.on('ferret', function (name, fn) {
|
||||
// fn('woot');
|
||||
// });
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
#### socket.on(eventName, callback)
|
||||
|
||||
*(inherited from `EventEmitter`)*
|
||||
- `eventName` _(String)_
|
||||
- `callback` _(Function)_
|
||||
- **Returns** `Socket`
|
||||
|
||||
Register a new handler for the given event.
|
||||
|
||||
```js
|
||||
socket.on('news', function (data) {
|
||||
console.log(data);
|
||||
});
|
||||
```
|
||||
|
||||
#### socket.once(eventName, listener)
|
||||
#### socket.removeListener(eventName, listener)
|
||||
#### socket.removeAllListeners([eventName])
|
||||
#### socket.eventNames()
|
||||
|
||||
Inherited from `EventEmitter` (along with other methods not mentioned here). See Node.js documentation for the `events` module.
|
||||
|
||||
#### socket.join(room[, callback])
|
||||
|
||||
- `room` _(String)_
|
||||
- `callback` _(Function)_
|
||||
- **Returns** `Socket` for chaining
|
||||
|
||||
Adds the client to the `room`, and fires optionally a callback with `err` signature (if any).
|
||||
|
||||
```js
|
||||
io.on('connection', function(socket){
|
||||
socket.join('room 237', function(){
|
||||
console.log(socket.rooms); // [ <socket.id>, 'room 237' ]
|
||||
io.to('room 237', 'a new user has joined the room'); // broadcast to everyone in the room
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
For your convenience, each socket automatically joins a room identified by this id (see `Socket#id`). This makes it easy to broadcast messages to other sockets:
|
||||
|
||||
```js
|
||||
io.on('connection', function(client){
|
||||
client.on('say to someone', function(id, msg){
|
||||
// send a private message to the socket with the given id
|
||||
client.broadcast.to(id).emit('my message', msg);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### socket.join(rooms[, callback])
|
||||
|
||||
- `rooms` _(Array)_
|
||||
- `callback` _(Function)_
|
||||
- **Returns** `Socket` for chaining
|
||||
|
||||
Adds the client to the list of room, and fires optionally a callback with `err` signature (if any).
|
||||
|
||||
#### socket.leave(room[, callback])
|
||||
|
||||
- `room` _(String)_
|
||||
- `callback` _(Function)_
|
||||
- **Returns** `Socket` for chaining
|
||||
|
||||
Removes the client from `room`, and fires optionally a callback with `err` signature (if any).
|
||||
|
||||
**Rooms are left automatically upon disconnection**.
|
||||
|
||||
#### socket.to(room)
|
||||
|
||||
- `room` _(String)_
|
||||
- **Returns** `Socket` for chaining
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event will only be _broadcasted_ to clients 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(client){
|
||||
// to one room
|
||||
client.to('others').emit('an event', { some: 'data' });
|
||||
// to multiple rooms
|
||||
client.to('room1').to('room2').emit('hello');
|
||||
});
|
||||
```
|
||||
|
||||
#### socket.in(room)
|
||||
|
||||
Synonym of [socket.to(room)](#sockettoroom).
|
||||
|
||||
#### socket.compress(value)
|
||||
|
||||
- `value` _(Boolean)_ whether to following packet will be compressed
|
||||
- **Returns** `Socket` for chaining
|
||||
|
||||
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.
|
||||
|
||||
#### socket.disconnect(close)
|
||||
|
||||
- `close` _(Boolean)_ whether to close the underlying connection
|
||||
- **Returns** `Socket`
|
||||
|
||||
Disconnects this client. If value of close is `true`, closes the underlying connection. Otherwise, it just disconnects the namespace.
|
||||
|
||||
#### Flag: 'broadcast'
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event data will only be _broadcast_ to every sockets but the sender.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.broadcast.emit('an event', { some: 'data' }); // everyone gets it but the sender
|
||||
});
|
||||
```
|
||||
|
||||
#### Flag: 'volatile'
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to receive messages (because of network slowness or other issues, or because they’re connected through long polling and is in the middle of a request-response cycle).
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.volatile.emit('an event', { some: 'data' }); // the client may or may not receive it
|
||||
});
|
||||
```
|
||||
|
||||
#### Event: 'disconnect'
|
||||
|
||||
- `reason` _(String)_ the reason of the disconnection (either client or server-side)
|
||||
|
||||
Fired upon disconnection.
|
||||
|
||||
#### Event: 'error'
|
||||
|
||||
- `error` _(Object)_ error object
|
||||
|
||||
Fired when an error occurs.
|
||||
|
||||
#### Event: 'disconnecting'
|
||||
|
||||
- `reason` _(String)_ the reason of the disconnection (either client or server-side)
|
||||
|
||||
Fired when the client is going to be disconnected (but hasn't left its `rooms` yet).
|
||||
|
||||
These are reserved events (along with `connect`, `newListener` and `removeListener`) which cannot be used as event names.
|
||||
|
||||
### Client
|
||||
|
||||
The `Client` class represents an incoming transport (engine.io) connection. A `Client` can be associated with many multiplexed `Socket`s that belong to different `Namespace`s.
|
||||
|
||||
#### client.conn
|
||||
|
||||
* _(engine.Socket)_
|
||||
|
||||
A reference to the underlying `engine.io` `Socket` connection.
|
||||
|
||||
#### client.request
|
||||
|
||||
* _(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`.
|
||||
15
docs/README.md
Normal file
15
docs/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
## Table of Contents
|
||||
|
||||
#### Getting started
|
||||
|
||||
- [Write a chat application](http://socket.io/get-started/chat/)
|
||||
|
||||
#### API Reference
|
||||
|
||||
- [Server API](API.md)
|
||||
- [Client API](https://github.com/socketio/socket.io-client/blob/master/docs/API.md)
|
||||
|
||||
#### Advanced topics
|
||||
|
||||
- [Emit cheatsheet](emit.md)
|
||||
58
docs/emit.md
Normal file
58
docs/emit.md
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
## Emit cheatsheet
|
||||
|
||||
```js
|
||||
|
||||
io.on('connect', onConnect);
|
||||
|
||||
function onConnect(socket){
|
||||
|
||||
// sending to the client
|
||||
socket.emit('hello', 'can you hear me?', 1, 2, 'abc');
|
||||
|
||||
// sending to all clients except sender
|
||||
socket.broadcast.emit('broadcast', 'hello friends!');
|
||||
|
||||
// sending to all clients in 'game' room except sender
|
||||
socket.to('game').emit('nice game', "let's play a game");
|
||||
|
||||
// sending to all clients in 'game1' and/or in 'game2' room, except sender
|
||||
socket.to('game1').to('game2').emit('nice game', "let's play a game (too)");
|
||||
|
||||
// sending to all clients in 'game' room, including sender
|
||||
io.in('game').emit('big-announcement', 'the game will start soon');
|
||||
|
||||
// sending to all clients in namespace 'myNamespace', including sender
|
||||
io.of('myNamespace').emit('bigger-announcement', 'the tournament will start soon');
|
||||
|
||||
// sending to a specific room in a specific namespace, including sender
|
||||
io.of('myNamespace').to('room').emit('event', 'message');
|
||||
|
||||
// sending to individual socketid (private message)
|
||||
socket.to(<socketid>).emit('hey', 'I just met you');
|
||||
|
||||
// sending with acknowledgement
|
||||
socket.emit('question', 'do you think so?', function (answer) {});
|
||||
|
||||
// sending without compression
|
||||
socket.compress(false).emit('uncompressed', "that's rough");
|
||||
|
||||
// sending a message that might be dropped if the client is not ready to receive messages
|
||||
socket.volatile.emit('maybe', 'do you really need it?');
|
||||
|
||||
// sending to all clients on this node (when using multiple nodes)
|
||||
io.local.emit('hi', 'my lovely babies');
|
||||
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
**Note:** The following events are reserved and should not be used as event names by your application:
|
||||
- `error`
|
||||
- `connect`
|
||||
- `disconnect`
|
||||
- `disconnecting`
|
||||
- `newListener`
|
||||
- `removeListener`
|
||||
- `ping`
|
||||
- `pong`
|
||||
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
|
||||
$ npm start
|
||||
```
|
||||
|
||||
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,15 @@
|
||||
{
|
||||
"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": "4.13.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
282
examples/chat/public/main.js
Normal file
282
examples/chat/public/main.js
Normal file
@@ -0,0 +1,282 @@
|
||||
$(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);
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
log('you have been disconnected');
|
||||
});
|
||||
|
||||
socket.on('reconnect', function () {
|
||||
log('you have been reconnected');
|
||||
if (username) {
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('reconnect_error', function () {
|
||||
log('attempt to reconnect has failed');
|
||||
});
|
||||
|
||||
});
|
||||
149
examples/chat/public/style.css
Normal file
149
examples/chat/public/style.css
Normal file
@@ -0,0 +1,149 @@
|
||||
/* 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 {
|
||||
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()
|
||||
31
examples/cluster-haproxy/README.md
Normal file
31
examples/cluster-haproxy/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
# Socket.IO Chat with haproxy & redis
|
||||
|
||||
A simple chat demo for socket.io
|
||||
|
||||
## How to use
|
||||
|
||||
Install [Docker Compose](https://docs.docker.com/compose/install/), then:
|
||||
|
||||
```
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
And then point your browser to `http://localhost:3000`.
|
||||
|
||||
This will start four Socket.IO nodes, behind a haproxy instance which will loadbalance the requests (using a cookie for sticky sessions, see [cookie](https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#4.2-cookie)).
|
||||
|
||||
Each node connects to the redis backend, which will enable to broadcast to every client, no matter which node it is currently connected to.
|
||||
|
||||
```
|
||||
# you can kill a given node, the client should reconnect to another node
|
||||
$ docker-compose stop server-george
|
||||
```
|
||||
|
||||
## 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.
|
||||
51
examples/cluster-haproxy/docker-compose.yml
Normal file
51
examples/cluster-haproxy/docker-compose.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
haproxy:
|
||||
build: ./haproxy
|
||||
links:
|
||||
- server-john
|
||||
- server-paul
|
||||
- server-george
|
||||
- server-ringo
|
||||
ports:
|
||||
- "3000:80"
|
||||
|
||||
server-john:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=John
|
||||
|
||||
server-paul:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=Paul
|
||||
|
||||
server-george:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=George
|
||||
|
||||
server-ringo:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=Ringo
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
expose:
|
||||
- "6379"
|
||||
2
examples/cluster-haproxy/haproxy/Dockerfile
Normal file
2
examples/cluster-haproxy/haproxy/Dockerfile
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM haproxy:1.7-alpine
|
||||
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
|
||||
31
examples/cluster-haproxy/haproxy/haproxy.cfg
Normal file
31
examples/cluster-haproxy/haproxy/haproxy.cfg
Normal file
@@ -0,0 +1,31 @@
|
||||
# Reference: http://blog.haproxy.com/2012/11/07/websockets-load-balancing-with-haproxy/
|
||||
|
||||
global
|
||||
daemon
|
||||
maxconn 4096
|
||||
nbproc 2
|
||||
|
||||
defaults
|
||||
mode http
|
||||
balance roundrobin
|
||||
option http-server-close
|
||||
timeout connect 5s
|
||||
timeout client 30s
|
||||
timeout client-fin 30s
|
||||
timeout server 30s
|
||||
timeout tunnel 1h
|
||||
default-server inter 1s rise 2 fall 1 on-marked-down shutdown-sessions
|
||||
option forwardfor
|
||||
|
||||
listen chat
|
||||
bind *:80
|
||||
default_backend nodes
|
||||
|
||||
backend nodes
|
||||
option httpchk HEAD /health
|
||||
http-check expect status 200
|
||||
cookie serverid insert
|
||||
server john server-john:3000 cookie john check
|
||||
server paul server-paul:3000 cookie paul check
|
||||
server george server-george:3000 cookie george check
|
||||
server ringo server-ringo:3000 cookie ringo check
|
||||
15
examples/cluster-haproxy/server/Dockerfile
Normal file
15
examples/cluster-haproxy/server/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM mhart/alpine-node:6
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "npm", "start" ]
|
||||
87
examples/cluster-haproxy/server/index.js
Normal file
87
examples/cluster-haproxy/server/index.js
Normal file
@@ -0,0 +1,87 @@
|
||||
// Setup basic express server
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var server = require('http').createServer(app);
|
||||
var io = require('socket.io')(server);
|
||||
var redis = require('socket.io-redis');
|
||||
var port = process.env.PORT || 3000;
|
||||
var serverName = process.env.NAME || 'Unknown';
|
||||
|
||||
io.adapter(redis({ host: 'redis', port: 6379 }));
|
||||
|
||||
server.listen(port, function () {
|
||||
console.log('Server listening at port %d', port);
|
||||
console.log('Hello, I\'m %s, how can I help?', serverName);
|
||||
});
|
||||
|
||||
// Routing
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// Health check
|
||||
app.head('/health', function (req, res) {
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
// Chatroom
|
||||
|
||||
var numUsers = 0;
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.emit('my-name-is', serverName);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
17
examples/cluster-haproxy/server/package.json
Normal file
17
examples/cluster-haproxy/server/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"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": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
28
examples/cluster-haproxy/server/public/index.html
Normal file
28
examples/cluster-haproxy/server/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>
|
||||
286
examples/cluster-haproxy/server/public/main.js
Normal file
286
examples/cluster-haproxy/server/public/main.js
Normal file
@@ -0,0 +1,286 @@
|
||||
$(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);
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
log('you have been disconnected');
|
||||
});
|
||||
|
||||
socket.on('reconnect', function () {
|
||||
log('you have been reconnected');
|
||||
if (username) {
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('reconnect_error', function () {
|
||||
log('attempt to reconnect has failed');
|
||||
});
|
||||
|
||||
socket.on('my-name-is', function (serverName) {
|
||||
log('host is now ' + serverName);
|
||||
})
|
||||
|
||||
});
|
||||
149
examples/cluster-haproxy/server/public/style.css
Normal file
149
examples/cluster-haproxy/server/public/style.css
Normal file
@@ -0,0 +1,149 @@
|
||||
/* 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 {
|
||||
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%;
|
||||
}
|
||||
31
examples/cluster-httpd/README.md
Normal file
31
examples/cluster-httpd/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
# Socket.IO Chat with httpd & redis
|
||||
|
||||
A simple chat demo for socket.io
|
||||
|
||||
## How to use
|
||||
|
||||
Install [Docker Compose](https://docs.docker.com/compose/install/), then:
|
||||
|
||||
```
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
And then point your browser to `http://localhost:3000`.
|
||||
|
||||
This will start four Socket.IO nodes, behind a httpd proxy which will loadbalance the requests (using a cookie for sticky sessions, see [cookie](http://httpd.apache.org/docs/2.4/fr/mod/mod_proxy_balancer.html)).
|
||||
|
||||
Each node connects to the redis backend, which will enable to broadcast to every client, no matter which node it is currently connected to.
|
||||
|
||||
```
|
||||
# you can kill a given node, the client should reconnect to another node
|
||||
$ docker-compose stop server-george
|
||||
```
|
||||
|
||||
## 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.
|
||||
51
examples/cluster-httpd/docker-compose.yml
Normal file
51
examples/cluster-httpd/docker-compose.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
httpd:
|
||||
build: ./httpd
|
||||
links:
|
||||
- server-john
|
||||
- server-paul
|
||||
- server-george
|
||||
- server-ringo
|
||||
ports:
|
||||
- "3000:80"
|
||||
|
||||
server-john:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=John
|
||||
|
||||
server-paul:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=Paul
|
||||
|
||||
server-george:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=George
|
||||
|
||||
server-ringo:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=Ringo
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
expose:
|
||||
- "6379"
|
||||
2
examples/cluster-httpd/httpd/Dockerfile
Normal file
2
examples/cluster-httpd/httpd/Dockerfile
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM httpd:2.4-alpine
|
||||
COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf
|
||||
52
examples/cluster-httpd/httpd/httpd.conf
Normal file
52
examples/cluster-httpd/httpd/httpd.conf
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
Listen 80
|
||||
|
||||
ServerName localhost
|
||||
|
||||
LoadModule authn_file_module modules/mod_authn_file.so
|
||||
LoadModule authn_core_module modules/mod_authn_core.so
|
||||
LoadModule authz_host_module modules/mod_authz_host.so
|
||||
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
|
||||
LoadModule authz_user_module modules/mod_authz_user.so
|
||||
LoadModule authz_core_module modules/mod_authz_core.so
|
||||
|
||||
LoadModule headers_module modules/mod_headers.so
|
||||
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
|
||||
LoadModule proxy_module modules/mod_proxy.so
|
||||
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
|
||||
LoadModule proxy_http_module modules/mod_proxy_http.so
|
||||
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
|
||||
LoadModule unixd_module modules/mod_unixd.so
|
||||
|
||||
User daemon
|
||||
Group daemon
|
||||
|
||||
ErrorLog /proc/self/fd/2
|
||||
|
||||
Header add Set-Cookie "SERVERID=sticky.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
|
||||
|
||||
<Proxy "balancer://nodes_polling">
|
||||
BalancerMember "http://server-john:3000" route=john
|
||||
BalancerMember "http://server-paul:3000" route=paul
|
||||
BalancerMember "http://server-george:3000" route=george
|
||||
BalancerMember "http://server-ringo:3000" route=ringo
|
||||
ProxySet stickysession=SERVERID
|
||||
</Proxy>
|
||||
|
||||
<Proxy "balancer://nodes_ws">
|
||||
BalancerMember "ws://server-john:3000" route=john
|
||||
BalancerMember "ws://server-paul:3000" route=paul
|
||||
BalancerMember "ws://server-george:3000" route=george
|
||||
BalancerMember "ws://server-ringo:3000" route=ringo
|
||||
ProxySet stickysession=SERVERID
|
||||
</Proxy>
|
||||
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:Upgrade} =websocket [NC]
|
||||
RewriteRule /(.*) balancer://nodes_ws/$1 [P,L]
|
||||
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
|
||||
RewriteRule /(.*) balancer://nodes_polling/$1 [P,L]
|
||||
|
||||
ProxyTimeout 3
|
||||
15
examples/cluster-httpd/server/Dockerfile
Normal file
15
examples/cluster-httpd/server/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM mhart/alpine-node:6
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "npm", "start" ]
|
||||
82
examples/cluster-httpd/server/index.js
Normal file
82
examples/cluster-httpd/server/index.js
Normal file
@@ -0,0 +1,82 @@
|
||||
// Setup basic express server
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var server = require('http').createServer(app);
|
||||
var io = require('socket.io')(server);
|
||||
var redis = require('socket.io-redis');
|
||||
var port = process.env.PORT || 3000;
|
||||
var serverName = process.env.NAME || 'Unknown';
|
||||
|
||||
io.adapter(redis({ host: 'redis', port: 6379 }));
|
||||
|
||||
server.listen(port, function () {
|
||||
console.log('Server listening at port %d', port);
|
||||
console.log('Hello, I\'m %s, how can I help?', serverName);
|
||||
});
|
||||
|
||||
// Routing
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// Chatroom
|
||||
|
||||
var numUsers = 0;
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.emit('my-name-is', serverName);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
17
examples/cluster-httpd/server/package.json
Normal file
17
examples/cluster-httpd/server/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"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": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
28
examples/cluster-httpd/server/public/index.html
Normal file
28
examples/cluster-httpd/server/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>
|
||||
286
examples/cluster-httpd/server/public/main.js
Normal file
286
examples/cluster-httpd/server/public/main.js
Normal file
@@ -0,0 +1,286 @@
|
||||
$(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);
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
log('you have been disconnected');
|
||||
});
|
||||
|
||||
socket.on('reconnect', function () {
|
||||
log('you have been reconnected');
|
||||
if (username) {
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('reconnect_error', function () {
|
||||
log('attempt to reconnect has failed');
|
||||
});
|
||||
|
||||
socket.on('my-name-is', function (serverName) {
|
||||
log('host is now ' + serverName);
|
||||
})
|
||||
|
||||
});
|
||||
149
examples/cluster-httpd/server/public/style.css
Normal file
149
examples/cluster-httpd/server/public/style.css
Normal file
@@ -0,0 +1,149 @@
|
||||
/* 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 {
|
||||
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%;
|
||||
}
|
||||
31
examples/cluster-nginx/README.md
Normal file
31
examples/cluster-nginx/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
# Socket.IO Chat with nginx & redis
|
||||
|
||||
A simple chat demo for socket.io
|
||||
|
||||
## How to use
|
||||
|
||||
Install [Docker Compose](https://docs.docker.com/compose/install/), then:
|
||||
|
||||
```
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
And then point your browser to `http://localhost:3000`.
|
||||
|
||||
This will start four Socket.IO nodes, behind a nginx proxy which will loadbalance the requests (using the IP of the client, see [ip_hash](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#ip_hash)).
|
||||
|
||||
Each node connects to the redis backend, which will enable to broadcast to every client, no matter which node it is currently connected to.
|
||||
|
||||
```
|
||||
# you can kill a given node, the client should reconnect to another node
|
||||
$ docker-compose stop server-george
|
||||
```
|
||||
|
||||
## 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.
|
||||
51
examples/cluster-nginx/docker-compose.yml
Normal file
51
examples/cluster-nginx/docker-compose.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
nginx:
|
||||
build: ./nginx
|
||||
links:
|
||||
- server-john
|
||||
- server-paul
|
||||
- server-george
|
||||
- server-ringo
|
||||
ports:
|
||||
- "3000:80"
|
||||
|
||||
server-john:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=John
|
||||
|
||||
server-paul:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=Paul
|
||||
|
||||
server-george:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=George
|
||||
|
||||
server-ringo:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=Ringo
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
expose:
|
||||
- "6379"
|
||||
3
examples/cluster-nginx/nginx/Dockerfile
Normal file
3
examples/cluster-nginx/nginx/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
35
examples/cluster-nginx/nginx/nginx.conf
Normal file
35
examples/cluster-nginx/nginx/nginx.conf
Normal file
@@ -0,0 +1,35 @@
|
||||
# Reference: https://www.nginx.com/resources/wiki/start/topics/examples/full/
|
||||
|
||||
worker_processes 4;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
proxy_pass http://nodes;
|
||||
|
||||
# enable WebSockets
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
|
||||
upstream nodes {
|
||||
# enable sticky session
|
||||
ip_hash;
|
||||
|
||||
server server-john:3000;
|
||||
server server-paul:3000;
|
||||
server server-george:3000;
|
||||
server server-ringo:3000;
|
||||
}
|
||||
}
|
||||
15
examples/cluster-nginx/server/Dockerfile
Normal file
15
examples/cluster-nginx/server/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM mhart/alpine-node:6
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "npm", "start" ]
|
||||
82
examples/cluster-nginx/server/index.js
Normal file
82
examples/cluster-nginx/server/index.js
Normal file
@@ -0,0 +1,82 @@
|
||||
// Setup basic express server
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var server = require('http').createServer(app);
|
||||
var io = require('socket.io')(server);
|
||||
var redis = require('socket.io-redis');
|
||||
var port = process.env.PORT || 3000;
|
||||
var serverName = process.env.NAME || 'Unknown';
|
||||
|
||||
io.adapter(redis({ host: 'redis', port: 6379 }));
|
||||
|
||||
server.listen(port, function () {
|
||||
console.log('Server listening at port %d', port);
|
||||
console.log('Hello, I\'m %s, how can I help?', serverName);
|
||||
});
|
||||
|
||||
// Routing
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// Chatroom
|
||||
|
||||
var numUsers = 0;
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.emit('my-name-is', serverName);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
17
examples/cluster-nginx/server/package.json
Normal file
17
examples/cluster-nginx/server/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"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": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
28
examples/cluster-nginx/server/public/index.html
Normal file
28
examples/cluster-nginx/server/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>
|
||||
286
examples/cluster-nginx/server/public/main.js
Normal file
286
examples/cluster-nginx/server/public/main.js
Normal file
@@ -0,0 +1,286 @@
|
||||
$(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);
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
log('you have been disconnected');
|
||||
});
|
||||
|
||||
socket.on('reconnect', function () {
|
||||
log('you have been reconnected');
|
||||
if (username) {
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('reconnect_error', function () {
|
||||
log('attempt to reconnect has failed');
|
||||
});
|
||||
|
||||
socket.on('my-name-is', function (serverName) {
|
||||
log('host is now ' + serverName);
|
||||
})
|
||||
|
||||
});
|
||||
149
examples/cluster-nginx/server/public/style.css
Normal file
149
examples/cluster-nginx/server/public/style.css
Normal file
@@ -0,0 +1,149 @@
|
||||
/* 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 {
|
||||
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%;
|
||||
}
|
||||
50
examples/custom-parsers/README.md
Normal file
50
examples/custom-parsers/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
# Socket.IO custom parsers
|
||||
|
||||
Since Socket.IO version 2.0.0, you can provide your custom parser, according to the needs of your application.
|
||||
|
||||
Several parsers are showcased here:
|
||||
|
||||
- the default one: [socket.io-parser](https://github.com/socketio/socket.io-parser)
|
||||
- one based on msgpack: [socket.io-msgpack-parser](https://github.com/darrachequesne/socket.io-msgpack-parser)
|
||||
- one based on native JSON: [socket.io-json-parser](https://github.com/darrachequesne/socket.io-json-parser)
|
||||
- a custom one based on [schemapack](https://github.com/phretaddin/schemapack)
|
||||
|
||||
They are tested with various payloads:
|
||||
|
||||
- string: `['1', '2', ... '1000']`
|
||||
- numeric: `[1, 2, ... 1000]`
|
||||
- binary: `new Buffer(1000), where buf[i] = i`
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ npm i && npm start
|
||||
```
|
||||
|
||||
## Results
|
||||
|
||||
| bytes / packet | CONNECT packet | string | numeric | binary |
|
||||
|----------------|----------------|--------|---------|-----------|
|
||||
| default | 1 | 5903 | 3904 | 43 + 1000 |
|
||||
| msgpack | 20 | 3919 | 2646 | 1029 |
|
||||
| JSON | 20 | 5930 | 3931 | 3625 |
|
||||
| schemapack | 20 | 3895 | 2005 | 1005 |
|
||||
|
||||
## Comparison
|
||||
|
||||
`default parser`
|
||||
- supports any serializable datastructure, including Blob and File
|
||||
- **but** binary payload is encoded as 2 packets
|
||||
|
||||
`msgpack`
|
||||
- the size of payloads containing mostly numeric values will be greatly reduced
|
||||
- **but** rely on [ArrayBuffer](https://caniuse.com/#feat=typedarrays) in the browser (IE > 9)
|
||||
|
||||
`JSON`
|
||||
- optimized
|
||||
- **but** does not support binary payloads
|
||||
|
||||
`schemapack`
|
||||
- the most efficient in both speed and size
|
||||
- **but** you have to provide a schema for each packet
|
||||
21
examples/custom-parsers/package.json
Normal file
21
examples/custom-parsers/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "parsers",
|
||||
"version": "1.0.0",
|
||||
"description": "Various socket.io parsers",
|
||||
"scripts": {
|
||||
"build": "webpack --config ./support/webpack.config.js",
|
||||
"start": "npm run build && node ./src/server.js"
|
||||
},
|
||||
"author": "Damien Arrachequesne",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"component-emitter": "^1.2.1",
|
||||
"express": "^4.15.2",
|
||||
"schemapack": "^1.4.2",
|
||||
"socket.io": "socketio/socket.io",
|
||||
"socket.io-client": "socketio/socket.io-client",
|
||||
"socket.io-json-parser": "^1.0.0",
|
||||
"socket.io-msgpack-parser": "^1.0.0",
|
||||
"webpack": "^2.4.1"
|
||||
}
|
||||
}
|
||||
13
examples/custom-parsers/public/index.html
Normal file
13
examples/custom-parsers/public/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO custom parsers</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="client1.bundle.js"></script>
|
||||
<script src="client2.bundle.js"></script>
|
||||
<script src="client3.bundle.js"></script>
|
||||
<script src="client4.bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
examples/custom-parsers/src/client1.js
Normal file
8
examples/custom-parsers/src/client1.js
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
const socket = require('socket.io-client')('localhost:3001', {});
|
||||
|
||||
socket.io.engine.on('data', (data) => console.log('[default]' + ' size= ' + (typeof data === 'string' ? data.length : data.byteLength)));
|
||||
|
||||
socket.on('string', (data) => console.log('[default] [string]', data));
|
||||
socket.on('numeric', (data) => console.log('[default] [numeric]', data));
|
||||
socket.on('binary', (data) => console.log('[default] [binary]', data));
|
||||
11
examples/custom-parsers/src/client2.js
Normal file
11
examples/custom-parsers/src/client2.js
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
const customParser = require('socket.io-msgpack-parser');
|
||||
const socket = require('socket.io-client')('http://localhost:3002', {
|
||||
parser: customParser
|
||||
});
|
||||
|
||||
socket.io.engine.on('data', (data) => console.log('[msgpack]' + ' size= ' + (typeof data === 'string' ? data.length : data.byteLength)));
|
||||
|
||||
socket.on('string', (data) => console.log('[msgpack] [string]', data));
|
||||
socket.on('numeric', (data) => console.log('[msgpack] [numeric]', data));
|
||||
socket.on('binary', (data) => console.log('[msgpack] [binary]', data));
|
||||
11
examples/custom-parsers/src/client3.js
Normal file
11
examples/custom-parsers/src/client3.js
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
const customParser = require('socket.io-json-parser');
|
||||
const socket = require('socket.io-client')('localhost:3003', {
|
||||
parser: customParser
|
||||
});
|
||||
|
||||
socket.io.engine.on('data', (data) => console.log('[json]' + ' size= ' + (typeof data === 'string' ? data.length : data.byteLength)));
|
||||
|
||||
socket.on('string', (data) => console.log('[json] [string]', data));
|
||||
socket.on('numeric', (data) => console.log('[json] [numeric]', data));
|
||||
socket.on('binary', (data) => console.log('[json] [binary]', data));
|
||||
11
examples/custom-parsers/src/client4.js
Normal file
11
examples/custom-parsers/src/client4.js
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
const customParser = require('./custom-parser');
|
||||
const socket = require('socket.io-client')('localhost:3004', {
|
||||
parser: customParser
|
||||
});
|
||||
|
||||
socket.io.engine.on('data', (data) => console.log('[custom]' + ' size= ' + (typeof data === 'string' ? data.length : data.byteLength)));
|
||||
|
||||
socket.on('string', (data) => console.log('[custom] [string]', data));
|
||||
socket.on('numeric', (data) => console.log('[custom] [numeric]', data));
|
||||
socket.on('binary', (data) => console.log('[custom] [binary]', data));
|
||||
125
examples/custom-parsers/src/custom-parser.js
Normal file
125
examples/custom-parsers/src/custom-parser.js
Normal file
@@ -0,0 +1,125 @@
|
||||
|
||||
const Emitter = require('component-emitter');
|
||||
const schemapack = require('schemapack');
|
||||
|
||||
/**
|
||||
* Packet types (see https://github.com/socketio/socket.io-protocol)
|
||||
*/
|
||||
|
||||
const TYPES = {
|
||||
CONNECT: 0,
|
||||
DISCONNECT: 1,
|
||||
EVENT: 2,
|
||||
ACK: 3,
|
||||
ERROR: 4,
|
||||
BINARY_EVENT: 5,
|
||||
BINARY_ACK: 6
|
||||
};
|
||||
|
||||
const stringSchema = schemapack.build({
|
||||
_id: 'uint8',
|
||||
data: [ 'string' ],
|
||||
nsp: 'string'
|
||||
});
|
||||
|
||||
const numericSchema = schemapack.build({
|
||||
_id: 'uint8',
|
||||
data: [ 'uint16' ],
|
||||
nsp: 'string'
|
||||
});
|
||||
|
||||
const binarySchema = schemapack.build({
|
||||
_id: 'uint8',
|
||||
data: 'buffer',
|
||||
nsp: 'string'
|
||||
});
|
||||
|
||||
const errorPacket = {
|
||||
type: TYPES.ERROR,
|
||||
data: 'parser error'
|
||||
};
|
||||
|
||||
class Encoder {
|
||||
encode (packet, callback) {
|
||||
switch (packet.type) {
|
||||
case TYPES.EVENT:
|
||||
return callback([ this.pack(packet) ]);
|
||||
default:
|
||||
return callback([ JSON.stringify(packet) ]);
|
||||
}
|
||||
}
|
||||
pack (packet) {
|
||||
let eventName = packet.data[0];
|
||||
let flatPacket = {
|
||||
data: packet.data[1],
|
||||
nsp: packet.nsp
|
||||
};
|
||||
switch (eventName) {
|
||||
case 'string':
|
||||
flatPacket._id = 1;
|
||||
return stringSchema.encode(flatPacket);
|
||||
case 'numeric':
|
||||
flatPacket._id = 2;
|
||||
return numericSchema.encode(flatPacket);
|
||||
case 'binary':
|
||||
flatPacket._id = 3;
|
||||
return binarySchema.encode(flatPacket);
|
||||
default:
|
||||
throw new Error('unknown event name: ' + eventName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Decoder extends Emitter {
|
||||
add (obj) {
|
||||
if (typeof obj === 'string') {
|
||||
this.parseJSON(obj);
|
||||
} else {
|
||||
this.parseBinary(obj);
|
||||
}
|
||||
}
|
||||
parseJSON (obj) {
|
||||
try {
|
||||
let decoded = JSON.parse(obj);
|
||||
this.emit('decoded', decoded);
|
||||
} catch (e) {
|
||||
this.emit('decoded', errorPacket);
|
||||
}
|
||||
}
|
||||
parseBinary (obj) {
|
||||
let view = new Uint8Array(obj);
|
||||
let packetId = view[0];
|
||||
try {
|
||||
let packet = {
|
||||
type: TYPES.EVENT
|
||||
};
|
||||
let decoded;
|
||||
switch (packetId) {
|
||||
case 1:
|
||||
decoded = stringSchema.decode(obj);
|
||||
packet.data = [ 'string', decoded.data ];
|
||||
packet.nsp = decoded.nsp;
|
||||
break;
|
||||
case 2:
|
||||
decoded = numericSchema.decode(obj);
|
||||
packet.data = [ 'numeric', decoded.data ];
|
||||
packet.nsp = decoded.nsp;
|
||||
break;
|
||||
case 3:
|
||||
decoded = binarySchema.decode(obj);
|
||||
packet.data = [ 'binary', decoded.data.buffer ];
|
||||
packet.nsp = decoded.nsp;
|
||||
break;
|
||||
default:
|
||||
throw new Error('unknown type');
|
||||
}
|
||||
this.emit('decoded', packet);
|
||||
} catch (e) {
|
||||
this.emit('decoded', errorPacket);
|
||||
}
|
||||
}
|
||||
destroy () {}
|
||||
}
|
||||
|
||||
exports.Encoder = Encoder;
|
||||
exports.Decoder = Decoder;
|
||||
55
examples/custom-parsers/src/server.js
Normal file
55
examples/custom-parsers/src/server.js
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const server = require('http').createServer(app);
|
||||
const path = require('path');
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../public')));
|
||||
|
||||
server.listen(port, () => console.log('>>> http://localhost:' + port));
|
||||
|
||||
const io = require('socket.io');
|
||||
const msgpackParser = require('socket.io-msgpack-parser');
|
||||
const jsonParser = require('socket.io-json-parser');
|
||||
const customParser = require('./custom-parser');
|
||||
|
||||
let server1 = io(3001, {});
|
||||
let server2 = io(3002, {
|
||||
parser: msgpackParser
|
||||
});
|
||||
let server3 = io(3003, {
|
||||
parser: jsonParser
|
||||
});
|
||||
let server4 = io(3004, {
|
||||
parser: customParser
|
||||
});
|
||||
|
||||
let string = [];
|
||||
let numeric = [];
|
||||
let binary = new Buffer(1e3);
|
||||
for (var i = 0; i < 1e3; i++) {
|
||||
string.push('' + i);
|
||||
numeric.push(i);
|
||||
binary[i] = i;
|
||||
}
|
||||
|
||||
server1.on('connect', onConnect(1000));
|
||||
server2.on('connect', onConnect(2000));
|
||||
server3.on('connect', onConnect(3000));
|
||||
server4.on('connect', onConnect(4000));
|
||||
|
||||
function onConnect (delay) {
|
||||
return function (socket) {
|
||||
console.log('connect ' + socket.id);
|
||||
|
||||
setTimeout(() => {
|
||||
socket.emit('string', string);
|
||||
socket.emit('numeric', numeric);
|
||||
socket.emit('binary', binary);
|
||||
}, delay);
|
||||
|
||||
socket.on('disconnect', () => console.log('disconnect ' + socket.id));
|
||||
};
|
||||
}
|
||||
|
||||
15
examples/custom-parsers/support/webpack.config.js
Normal file
15
examples/custom-parsers/support/webpack.config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
client1: './src/client1.js',
|
||||
client2: './src/client2.js',
|
||||
client3: './src/client3.js',
|
||||
client4: './src/client4.js'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../public'),
|
||||
filename: '[name].bundle.js'
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
18
examples/webpack-build-server/README.md
Normal file
18
examples/webpack-build-server/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
# Socket.IO WebPack build
|
||||
|
||||
A sample Webpack build for the server.
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ npm i
|
||||
$ npm run build
|
||||
$ npm start
|
||||
```
|
||||
|
||||
**Note:**
|
||||
|
||||
- the `bufferutil` and `utf-8-validate` are optional dependencies from `ws`, compiled from native code, which are meant to improve performance ([ref](https://github.com/websockets/ws#opt-in-for-performance)). You can also omit them, as they have their JS fallback, and ignore the WebPack warning.
|
||||
|
||||
- the server is initiated with `serveClient` set to `false`, so it will not serve the client file.
|
||||
15
examples/webpack-build-server/lib/index.js
Normal file
15
examples/webpack-build-server/lib/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
const server = require('http').createServer();
|
||||
const io = require('socket.io')(server, {
|
||||
// serveClient: false // do not serve the client file, in that case the brfs loader is not needed
|
||||
});
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
io.on('connect', onConnect);
|
||||
server.listen(port, () => console.log('server listening on port ' + port));
|
||||
|
||||
function onConnect(socket){
|
||||
console.log('connect ' + socket.id);
|
||||
|
||||
socket.on('disconnect', () => console.log('disconnect ' + socket.id));
|
||||
}
|
||||
23
examples/webpack-build-server/package.json
Normal file
23
examples/webpack-build-server/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "webpack-build-server",
|
||||
"version": "1.0.0",
|
||||
"description": "A sample Webpack build (for the server)",
|
||||
"scripts": {
|
||||
"start": "node dist/server.js",
|
||||
"build": "webpack --config ./support/webpack.config.js"
|
||||
},
|
||||
"author": "Damien Arrachequesne",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"brfs": "^1.4.3",
|
||||
"bufferutil": "^1.3.0",
|
||||
"socket.io": "^1.7.2",
|
||||
"transform-loader": "^0.2.3",
|
||||
"utf-8-validate": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"json-loader": "^0.5.4",
|
||||
"null-loader": "^0.1.1",
|
||||
"webpack": "^1.14.0"
|
||||
}
|
||||
}
|
||||
25
examples/webpack-build-server/support/webpack.config.js
Normal file
25
examples/webpack-build-server/support/webpack.config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
target: 'node',
|
||||
output: {
|
||||
path: './dist',
|
||||
filename: 'server.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /(\.md|\.map)$/,
|
||||
loader: 'null'
|
||||
},
|
||||
{
|
||||
test: /\.json$/,
|
||||
loader: 'json'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: "transform-loader?brfs"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
20
examples/webpack-build/README.md
Normal file
20
examples/webpack-build/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
# Socket.IO WebPack build
|
||||
|
||||
A sample Webpack build for the browser.
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ npm i
|
||||
$ npm run build-all
|
||||
```
|
||||
|
||||
There are two WebPack configuration:
|
||||
|
||||
- the minimal configuration, just bundling the application and its dependencies. The `app.js` file in the `dist` folder is the result of that build.
|
||||
|
||||
- a slimmer one, where:
|
||||
- the JSON polyfill needed for IE6/IE7 support has been removed.
|
||||
- the `debug` calls and import have been removed (the [debug](https://github.com/visionmedia/debug) library is included in the build by default).
|
||||
- the source has been uglified (dropping IE8 support), and an associated SourceMap has been generated.
|
||||
13
examples/webpack-build/index.html
Normal file
13
examples/webpack-build/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO WebPack Example</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- <script src="dist/app.js"></script> -->
|
||||
<script src="dist/app.slim.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
10
examples/webpack-build/lib/index.js
Normal file
10
examples/webpack-build/lib/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
var socket = require('socket.io-client')('http://localhost:3000');
|
||||
|
||||
console.log('init');
|
||||
|
||||
socket.on('connect', onConnect);
|
||||
|
||||
function onConnect(){
|
||||
console.log('connect ' + socket.id);
|
||||
}
|
||||
19
examples/webpack-build/package.json
Normal file
19
examples/webpack-build/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "webpack-build",
|
||||
"version": "1.0.0",
|
||||
"description": "A sample Webpack build",
|
||||
"scripts": {
|
||||
"build": "webpack --config ./support/webpack.config.js",
|
||||
"build-slim": "webpack --config ./support/webpack.config.slim.js",
|
||||
"build-all": "npm run build && npm run build-slim"
|
||||
},
|
||||
"author": "Damien Arrachequesne",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"socket.io-client": "^1.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"strip-loader": "^0.1.2",
|
||||
"webpack": "^1.14.0"
|
||||
}
|
||||
}
|
||||
2
examples/webpack-build/support/noop.js
Normal file
2
examples/webpack-build/support/noop.js
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
module.exports = function () { return function () {}; };
|
||||
8
examples/webpack-build/support/webpack.config.js
Normal file
8
examples/webpack-build/support/webpack.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
path: './dist',
|
||||
filename: 'app.js'
|
||||
},
|
||||
};
|
||||
35
examples/webpack-build/support/webpack.config.slim.js
Normal file
35
examples/webpack-build/support/webpack.config.slim.js
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
var webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
path: './dist',
|
||||
filename: 'app.slim.js'
|
||||
},
|
||||
externals: {
|
||||
// replace JSON polyfill (IE6/IE7) with global JSON object
|
||||
json3: 'JSON'
|
||||
},
|
||||
// generate sourcemap
|
||||
devtool: 'source-map',
|
||||
plugins: [
|
||||
// replace require('debug')() with an noop function
|
||||
new webpack.NormalModuleReplacementPlugin(/debug/, process.cwd() + '/support/noop.js'),
|
||||
// use uglifyJS (IE9+ support)
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
})
|
||||
],
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
// strip `debug()` calls
|
||||
test: /\.js$/,
|
||||
loader: 'strip-loader?strip[]=debug'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
17
examples/whiteboard/README.md
Normal file
17
examples/whiteboard/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
# Socket.IO Collaborative Whiteboard
|
||||
|
||||
A simple collaborative whiteboard for socket.io
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ npm i && npm start
|
||||
```
|
||||
|
||||
And point your browser to `http://localhost:3000`. Optionally, specify
|
||||
a port by supplying the `PORT` env variable.
|
||||
|
||||
## Features
|
||||
|
||||
- draw on the whiteboard and all other users will see you drawings live
|
||||
16
examples/whiteboard/index.js
Normal file
16
examples/whiteboard/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const http = require('http').Server(app);
|
||||
const io = require('socket.io')(http);
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
function onConnection(socket){
|
||||
socket.on('drawing', (data) => socket.broadcast.emit('drawing', data));
|
||||
}
|
||||
|
||||
io.on('connection', onConnection);
|
||||
|
||||
http.listen(port, () => console.log('listening on port ' + port));
|
||||
19
examples/whiteboard/package.json
Normal file
19
examples/whiteboard/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "whiteboard",
|
||||
"version": "1.0.0",
|
||||
"description": "A simple collaborative whiteboard using socket.io",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
"socket.io",
|
||||
"whiteboard"
|
||||
],
|
||||
"dependencies": {
|
||||
"express": "4.9.x",
|
||||
"socket.io": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index"
|
||||
},
|
||||
"author": "Damien Arrachequesne",
|
||||
"license": "MIT"
|
||||
}
|
||||
23
examples/whiteboard/public/index.html
Normal file
23
examples/whiteboard/public/index.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO whiteboard</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<canvas class="whiteboard" ></canvas>
|
||||
|
||||
<div class="colors">
|
||||
<div class="color black"></div>
|
||||
<div class="color red"></div>
|
||||
<div class="color green"></div>
|
||||
<div class="color blue"></div>
|
||||
<div class="color yellow"></div>
|
||||
</div>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
100
examples/whiteboard/public/main.js
Normal file
100
examples/whiteboard/public/main.js
Normal file
@@ -0,0 +1,100 @@
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
|
||||
var socket = io();
|
||||
var canvas = document.getElementsByClassName('whiteboard')[0];
|
||||
var colors = document.getElementsByClassName('color');
|
||||
var context = canvas.getContext('2d');
|
||||
|
||||
var current = {
|
||||
color: 'black'
|
||||
};
|
||||
var drawing = false;
|
||||
|
||||
canvas.addEventListener('mousedown', onMouseDown, false);
|
||||
canvas.addEventListener('mouseup', onMouseUp, false);
|
||||
canvas.addEventListener('mouseout', onMouseUp, false);
|
||||
canvas.addEventListener('mousemove', throttle(onMouseMove, 10), false);
|
||||
|
||||
for (var i = 0; i < colors.length; i++){
|
||||
colors[i].addEventListener('click', onColorUpdate, false);
|
||||
}
|
||||
|
||||
socket.on('drawing', onDrawingEvent);
|
||||
|
||||
window.addEventListener('resize', onResize, false);
|
||||
onResize();
|
||||
|
||||
|
||||
function drawLine(x0, y0, x1, y1, color, emit){
|
||||
context.beginPath();
|
||||
context.moveTo(x0, y0);
|
||||
context.lineTo(x1, y1);
|
||||
context.strokeStyle = color;
|
||||
context.lineWidth = 2;
|
||||
context.stroke();
|
||||
context.closePath();
|
||||
|
||||
if (!emit) { return; }
|
||||
var w = canvas.width;
|
||||
var h = canvas.height;
|
||||
|
||||
socket.emit('drawing', {
|
||||
x0: x0 / w,
|
||||
y0: y0 / h,
|
||||
x1: x1 / w,
|
||||
y1: y1 / h,
|
||||
color: color
|
||||
});
|
||||
}
|
||||
|
||||
function onMouseDown(e){
|
||||
drawing = true;
|
||||
current.x = e.clientX;
|
||||
current.y = e.clientY;
|
||||
}
|
||||
|
||||
function onMouseUp(e){
|
||||
if (!drawing) { return; }
|
||||
drawing = false;
|
||||
drawLine(current.x, current.y, e.clientX, e.clientY, current.color, true);
|
||||
}
|
||||
|
||||
function onMouseMove(e){
|
||||
if (!drawing) { return; }
|
||||
drawLine(current.x, current.y, e.clientX, e.clientY, current.color, true);
|
||||
current.x = e.clientX;
|
||||
current.y = e.clientY;
|
||||
}
|
||||
|
||||
function onColorUpdate(e){
|
||||
current.color = e.target.className.split(' ')[1];
|
||||
}
|
||||
|
||||
// limit the number of events per second
|
||||
function throttle(callback, delay) {
|
||||
var previousCall = new Date().getTime();
|
||||
return function() {
|
||||
var time = new Date().getTime();
|
||||
|
||||
if ((time - previousCall) >= delay) {
|
||||
previousCall = time;
|
||||
callback.apply(null, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function onDrawingEvent(data){
|
||||
var w = canvas.width;
|
||||
var h = canvas.height;
|
||||
drawLine(data.x0 * w, data.y0 * h, data.x1 * w, data.y1 * h, data.color);
|
||||
}
|
||||
|
||||
// make the canvas fill its parent
|
||||
function onResize() {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
}
|
||||
|
||||
})();
|
||||
44
examples/whiteboard/public/style.css
Normal file
44
examples/whiteboard/public/style.css
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
/**
|
||||
* Fix user-agent
|
||||
*/
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Canvas
|
||||
*/
|
||||
|
||||
.whiteboard {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.colors {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.color {
|
||||
display: inline-block;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.color.black { background-color: black; }
|
||||
.color.red { background-color: red; }
|
||||
.color.green { background-color: green; }
|
||||
.color.blue { background-color: blue; }
|
||||
.color.yellow { background-color: yellow; }
|
||||
69
gulpfile.js
Normal file
69
gulpfile.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const gulp = require('gulp');
|
||||
const mocha = require('gulp-mocha');
|
||||
const babel = require("gulp-babel");
|
||||
const istanbul = require('gulp-istanbul');
|
||||
const help = require('gulp-task-listing');
|
||||
const del = require('del');
|
||||
|
||||
gulp.task('help', help);
|
||||
|
||||
gulp.task('default', ['transpile']);
|
||||
|
||||
const TRANSPILE_DEST_DIR = './dist';
|
||||
|
||||
// By default, individual js files are transformed by babel and exported to /dist
|
||||
gulp.task('transpile', function () {
|
||||
return gulp.src("lib/*.js")
|
||||
.pipe(babel({ "presets": ["es2015"] }))
|
||||
.pipe(gulp.dest(TRANSPILE_DEST_DIR));
|
||||
});
|
||||
|
||||
gulp.task('clean', function () {
|
||||
return del([TRANSPILE_DEST_DIR]);
|
||||
})
|
||||
|
||||
gulp.task('test', ['transpile'], function(){
|
||||
return gulp.src('test/socket.io.js', {read: false})
|
||||
.pipe(mocha({
|
||||
slow: 200,
|
||||
reporter: 'spec',
|
||||
bail: true,
|
||||
timeout: 10000
|
||||
}))
|
||||
.once('error', function (err) {
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
})
|
||||
.once('end', function () {
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('set-compat-node-env', function() {
|
||||
process.env.TEST_VERSION = 'compat';
|
||||
});
|
||||
|
||||
gulp.task('test-compat', ['set-compat-node-env', 'test']);
|
||||
|
||||
gulp.task('istanbul-pre-test', function () {
|
||||
return gulp.src(['lib/**/*.js'])
|
||||
// Covering files
|
||||
.pipe(istanbul())
|
||||
// Force `require` to return covered files
|
||||
.pipe(istanbul.hookRequire());
|
||||
});
|
||||
|
||||
gulp.task('test-cov', ['istanbul-pre-test'], function(){
|
||||
return gulp.src('test/socket.io.js', {read: false})
|
||||
.pipe(mocha({
|
||||
reporter: 'dot'
|
||||
}))
|
||||
.pipe(istanbul.writeReports())
|
||||
.once('error', function (err){
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
})
|
||||
.once('end', function (){
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
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');
|
||||
252
lib/client.js
Normal file
252
lib/client.js
Normal file
@@ -0,0 +1,252 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var parser = require('socket.io-parser');
|
||||
var debug = require('debug')('socket.io:client');
|
||||
var url = require('url');
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = Client;
|
||||
|
||||
/**
|
||||
* Client constructor.
|
||||
*
|
||||
* @param {Server} server instance
|
||||
* @param {Socket} conn
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Client(server, conn){
|
||||
this.server = server;
|
||||
this.conn = conn;
|
||||
this.encoder = server.encoder;
|
||||
this.decoder = new server.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} name namespace
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.connect = function(name, query){
|
||||
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, query, 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} opts
|
||||
* @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, writeToEngine); // encode, then write results to engine
|
||||
} 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(url.parse(packet.nsp).pathname, url.parse(packet.nsp, true).query);
|
||||
} else {
|
||||
var socket = this.nsps[packet.nsp];
|
||||
if (socket) {
|
||||
process.nextTick(function() {
|
||||
socket.onpacket(packet);
|
||||
});
|
||||
} else {
|
||||
debug('no socket for namespace %s', packet.nsp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles an error.
|
||||
*
|
||||
* @param {Object} err 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.conn.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
461
lib/index.js
Normal file
461
lib/index.js
Normal file
@@ -0,0 +1,461 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http');
|
||||
var read = require('fs').readFileSync;
|
||||
var path = require('path');
|
||||
var exists = require('fs').existsSync;
|
||||
var engine = require('engine.io');
|
||||
var client = require('socket.io-client');
|
||||
var clientVersion = require('socket.io-client/package').version;
|
||||
var Client = require('./client');
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var Namespace = require('./namespace');
|
||||
var Adapter = require('socket.io-adapter');
|
||||
var parser = require('socket.io-parser');
|
||||
var debug = require('debug')('socket.io:server');
|
||||
var url = require('url');
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = Server;
|
||||
|
||||
/**
|
||||
* Socket.IO client source.
|
||||
*/
|
||||
|
||||
var clientSource = undefined;
|
||||
var clientSourceMap = undefined;
|
||||
|
||||
/**
|
||||
* Server constructor.
|
||||
*
|
||||
* @param {http.Server|Number|Object} srv http server, port or options
|
||||
* @param {Object} [opts]
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Server(srv, opts){
|
||||
if (!(this instanceof Server)) return new Server(srv, opts);
|
||||
if ('object' == typeof srv && srv instanceof Object && !srv.listen) {
|
||||
opts = srv;
|
||||
srv = null;
|
||||
}
|
||||
opts = opts || {};
|
||||
this.nsps = {};
|
||||
this.path(opts.path || '/socket.io');
|
||||
this.serveClient(false !== opts.serveClient);
|
||||
this.parser = opts.parser || parser;
|
||||
this.encoder = new this.parser.Encoder();
|
||||
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} req request
|
||||
* @param {Function} fn 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} v 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;
|
||||
var resolvePath = function(file){
|
||||
var filepath = path.resolve(__dirname, './../../', file);
|
||||
if (exists(filepath)) {
|
||||
return filepath;
|
||||
}
|
||||
return require.resolve(file);
|
||||
};
|
||||
if (v && !clientSource) {
|
||||
clientSource = read(resolvePath( 'socket.io-client/dist/socket.io.js'), 'utf-8');
|
||||
try {
|
||||
clientSourceMap = read(resolvePath( 'socket.io-client/dist/socket.io.js.map'), 'utf-8');
|
||||
} catch(err) {
|
||||
debug('could not load sourcemap file');
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Old settings for backwards compatibility
|
||||
*/
|
||||
|
||||
var oldSettings = {
|
||||
"transports": "transports",
|
||||
"heartbeat timeout": "pingTimeout",
|
||||
"heartbeat interval": "pingInterval",
|
||||
"destroy buffer size": "maxHttpBufferSize"
|
||||
};
|
||||
|
||||
/**
|
||||
* Backwards compatibility.
|
||||
*
|
||||
* @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} v 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} v 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} v 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);
|
||||
|
||||
var self = this;
|
||||
|
||||
var connectPacket = { type: parser.CONNECT, nsp: '/' };
|
||||
this.encoder.encode(connectPacket, function (encodedPacket){
|
||||
// the CONNECT packet will be merged with Engine.IO handshake,
|
||||
// to reduce the number of round trips
|
||||
opts.initialPacket = encodedPacket;
|
||||
|
||||
// initialize engine
|
||||
debug('creating engine.io instance with opts %j', opts);
|
||||
self.eio = engine.attach(srv, opts);
|
||||
|
||||
// attach static file serving
|
||||
if (self._serveClient) self.attachServe(srv);
|
||||
|
||||
// Export http server
|
||||
self.httpServer = srv;
|
||||
|
||||
// bind to engine events
|
||||
self.bind(self.eio);
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches the static file serving.
|
||||
*
|
||||
* @param {Function|http.Server} srv http server
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.attachServe = function(srv){
|
||||
debug('attaching client serving req handler');
|
||||
var url = this._path + '/socket.io.js';
|
||||
var urlMap = this._path + '/socket.io.js.map';
|
||||
var evs = srv.listeners('request').slice(0);
|
||||
var self = this;
|
||||
srv.removeAllListeners('request');
|
||||
srv.on('request', function(req, res) {
|
||||
if (0 === req.url.indexOf(urlMap)) {
|
||||
self.serveMap(req, res);
|
||||
} else 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){
|
||||
// Per the standard, ETags must be quoted:
|
||||
// https://tools.ietf.org/html/rfc7232#section-2.3
|
||||
var expectedEtag = '"' + clientVersion + '"';
|
||||
|
||||
var etag = req.headers['if-none-match'];
|
||||
if (etag) {
|
||||
if (expectedEtag == etag) {
|
||||
debug('serve client 304');
|
||||
res.writeHead(304);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
debug('serve client source');
|
||||
res.setHeader('Content-Type', 'application/javascript');
|
||||
res.setHeader('ETag', expectedEtag);
|
||||
res.writeHead(200);
|
||||
res.end(clientSource);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a request serving `/socket.io.js.map`
|
||||
*
|
||||
* @param {http.Request} req
|
||||
* @param {http.Response} res
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.serveMap = function(req, res){
|
||||
// Per the standard, ETags must be quoted:
|
||||
// https://tools.ietf.org/html/rfc7232#section-2.3
|
||||
var expectedEtag = '"' + clientVersion + '"';
|
||||
|
||||
var etag = req.headers['if-none-match'];
|
||||
if (etag) {
|
||||
if (expectedEtag == etag) {
|
||||
debug('serve client 304');
|
||||
res.writeHead(304);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
debug('serve client sourcemap');
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('ETag', expectedEtag);
|
||||
res.writeHead(200);
|
||||
res.end(clientSourceMap);
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds socket.io to an engine.io instance.
|
||||
*
|
||||
* @param {engine.Server} engine 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} conn
|
||||
* @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} name nsp name
|
||||
* @param {Function} [fn] 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
|
||||
*
|
||||
* @param {Function} [fn] optional, called as `fn([err])` on error OR all conns closed
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.close = function(fn){
|
||||
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(fn);
|
||||
} else {
|
||||
fn && fn();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose main namespace (/).
|
||||
*/
|
||||
|
||||
var emitterMethods = Object.keys(Emitter.prototype).filter(function(key){
|
||||
return typeof Emitter.prototype[key] === 'function';
|
||||
});
|
||||
|
||||
emitterMethods.concat(['to', 'in', 'use', 'send', 'write', 'clients', 'compress']).forEach(function(fn){
|
||||
Server.prototype[fn] = function(){
|
||||
return this.sockets[fn].apply(this.sockets, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
Namespace.flags.forEach(function(flag){
|
||||
Object.defineProperty(Server.prototype, flag, {
|
||||
get: 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)));
|
||||
};
|
||||
});
|
||||
968
lib/manager.js
968
lib/manager.js
@@ -1,968 +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': 25
|
||||
, 'heartbeat timeout': 15
|
||||
, 'heartbeat interval': 20
|
||||
, '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);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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');
|
||||
|
||||
if (req.url.substr(0, resource.length) == resource) {
|
||||
var uri = url.parse(req.url.substr(resource.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');
|
||||
|
||||
/**
|
||||
* 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',
|
||||
'local'
|
||||
];
|
||||
|
||||
/**
|
||||
* `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.rooms = [];
|
||||
this.flags = {};
|
||||
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){
|
||||
Object.defineProperty(Namespace.prototype, flag, {
|
||||
get: function() {
|
||||
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){
|
||||
debug('removing initial packet');
|
||||
delete this.server.eio.initialPacket;
|
||||
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} fn 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){
|
||||
if (!~this.rooms.indexOf(name)) this.rooms.push(name);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new client.
|
||||
*
|
||||
* @return {Socket}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Namespace.prototype.add = function(client, query, fn){
|
||||
debug('adding socket to nsp %s', this.name);
|
||||
var socket = new Socket(this, client, query);
|
||||
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 packet = { type: parser.EVENT, 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
|
||||
});
|
||||
|
||||
this.rooms = [];
|
||||
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);
|
||||
// reset rooms for scenario:
|
||||
// .in('room').clients() (GH-1978)
|
||||
this.rooms = [];
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the compress flag.
|
||||
*
|
||||
* @param {Boolean} compress if `true`, compresses the sending data
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Namespace.prototype.compress = function(compress){
|
||||
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.8.7';
|
||||
|
||||
/**
|
||||
* 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');
|
||||
762
lib/socket.js
762
lib/socket.js
@@ -1,362 +1,554 @@
|
||||
|
||||
/*!
|
||||
* 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 assign = require('object-assign');
|
||||
|
||||
/**
|
||||
* 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',
|
||||
'disconnecting',
|
||||
'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, query){
|
||||
this.nsp = nsp;
|
||||
this.server = nsp.server;
|
||||
this.adapter = this.nsp.adapter;
|
||||
this.id = nsp.name !== '/' ? nsp.name + '#' + client.id : 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(query);
|
||||
this.fns = [];
|
||||
this.flags = {};
|
||||
this._rooms = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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){
|
||||
Object.defineProperty(Socket.prototype, flag, {
|
||||
get: function() {
|
||||
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 shortcut.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.__defineGetter__('json', function () {
|
||||
this.flags.json = true;
|
||||
return this;
|
||||
Object.defineProperty(Socket.prototype, 'request', {
|
||||
get: 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(query){
|
||||
var self = this;
|
||||
function buildQuery(){
|
||||
var requestQuery = url.parse(self.request.url, true).query;
|
||||
//if socket-specific query exist, replace query strings in requestQuery
|
||||
return assign({}, query, requestQuery);
|
||||
}
|
||||
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: buildQuery()
|
||||
};
|
||||
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 = {
|
||||
type: parser.EVENT,
|
||||
data: args
|
||||
};
|
||||
|
||||
this.dispatch(packet, this.flags.volatile);
|
||||
}
|
||||
// access last argument to see if it's an ACK callback
|
||||
if (typeof args[args.length - 1] === 'function') {
|
||||
if (this._rooms.length || this.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.length || this.flags.broadcast) {
|
||||
this.adapter.broadcast(packet, {
|
||||
except: [this.id],
|
||||
rooms: this._rooms,
|
||||
flags: this.flags
|
||||
});
|
||||
} else {
|
||||
this.manager.onClientDisconnect(this.id);
|
||||
this.manager.store.publish('disconnect:' + this.id);
|
||||
// dispatch packet
|
||||
this.packet(packet, this.flags);
|
||||
}
|
||||
}
|
||||
|
||||
// reset flags
|
||||
this._rooms = [];
|
||||
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){
|
||||
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} opts 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|Array} room or array of rooms
|
||||
* @param {Function} fn optional, callback
|
||||
* @return {Socket} self
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.join = function(rooms, fn){
|
||||
debug('joining room %s', rooms);
|
||||
var self = this;
|
||||
if (!Array.isArray(rooms)) {
|
||||
rooms = [rooms];
|
||||
}
|
||||
rooms = rooms.filter(function (room) {
|
||||
return !self.rooms.hasOwnProperty(room);
|
||||
});
|
||||
if (!rooms.length) {
|
||||
fn && fn(null);
|
||||
return this;
|
||||
}
|
||||
this.adapter.addAll(this.id, rooms, function(err){
|
||||
if (err) return fn && fn(err);
|
||||
debug('joined room %s', rooms);
|
||||
rooms.forEach(function (room) {
|
||||
self.rooms[room] = room;
|
||||
});
|
||||
fn && fn(null);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Leaves a room.
|
||||
*
|
||||
* @param {String} room
|
||||
* @param {Function} fn 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 successful
|
||||
* middleware execution (ie: authorization).
|
||||
* Socket is added to namespace array before
|
||||
* call to join, so adapters can access it.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onconnect = function(){
|
||||
debug('socket connected - writing packet');
|
||||
this.nsp.connected[this.id] = this;
|
||||
this.join(this.id);
|
||||
var skip = this.nsp.name === '/' && this.nsp.fns.length === 0;
|
||||
if (skip) {
|
||||
debug('packet already sent in initial handshake');
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
|
||||
this.dispatch(args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Produces an ack callback to emit with an event.
|
||||
*
|
||||
* @param {Number} id 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);
|
||||
|
||||
self.packet({
|
||||
id: id,
|
||||
type: parser.ACK,
|
||||
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
|
||||
* @throw {Error} optional error object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onclose = function(reason){
|
||||
if (!this.connected) return this;
|
||||
debug('closing socket - reason %s', reason);
|
||||
this.emit('disconnecting', 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} err error object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.error = function(err){
|
||||
this.packet({ type: parser.ERROR, data: err });
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnects this client.
|
||||
*
|
||||
* @param {Boolean} close 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} compress if `true`, compresses the sending data
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.compress = function(compress){
|
||||
this.flags.compress = compress;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatch incoming event to socket listeners.
|
||||
*
|
||||
* @param {Array} event that will get emitted
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.dispatch = function(event){
|
||||
debug('dispatching an event %j', event);
|
||||
var self = this;
|
||||
function dispatchSocket(err) {
|
||||
process.nextTick(function(){
|
||||
if (err) {
|
||||
return self.error(err.data || err.message);
|
||||
}
|
||||
emit.apply(self, event);
|
||||
});
|
||||
}
|
||||
this.run(event, dispatchSocket);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up socket middleware.
|
||||
*
|
||||
* @param {Function} middleware function (event, next)
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.use = function(fn){
|
||||
this.fns.push(fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the middleware for an incoming event.
|
||||
*
|
||||
* @param {Array} event that will get emitted
|
||||
* @param {Function} last fn call in the middleware
|
||||
* @api private
|
||||
*/
|
||||
Socket.prototype.run = function(event, fn){
|
||||
var fns = this.fns.slice(0);
|
||||
if (!fns.length) return fn(null);
|
||||
|
||||
function run(i){
|
||||
fns[i](event, function(err){
|
||||
// upon error, short-circuit
|
||||
if (err) return fn(err);
|
||||
|
||||
// if no middleware left, summon callback
|
||||
if (!fns[i + 1]) return fn(null);
|
||||
|
||||
// go on to next
|
||||
run(i + 1);
|
||||
});
|
||||
}
|
||||
|
||||
run(0);
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user