mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-11 07:58:13 -05:00
Compare commits
1290 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88b2cdb6ab | ||
|
|
d30630ba10 | ||
|
|
f927ba29ef | ||
|
|
baa6804440 | ||
|
|
f223178eb6 | ||
|
|
226cc16165 | ||
|
|
05e1278cfa | ||
|
|
22d4bdf00d | ||
|
|
dfded53593 | ||
|
|
e6b869738c | ||
|
|
a169050947 | ||
|
|
873fdc55ed | ||
|
|
f78a575f66 | ||
|
|
d33a619905 | ||
|
|
3951a79359 | ||
|
|
6fa026fc94 | ||
|
|
47161a65d4 | ||
|
|
cf39362014 | ||
|
|
4d01b2c84c | ||
|
|
82271921db | ||
|
|
1150eb50e9 | ||
|
|
9c1e73c752 | ||
|
|
df05b73bb9 | ||
|
|
b00ae50be6 | ||
|
|
d3c653d876 | ||
|
|
a7fbd1ac4a | ||
|
|
190d22b46e | ||
|
|
7b8fba7ea2 | ||
|
|
e5f0ceaee0 | ||
|
|
7e35f901b8 | ||
|
|
2dbec77a38 | ||
|
|
d97d873aee | ||
|
|
e0b2cb0c5a | ||
|
|
1decae341c | ||
|
|
0279c47c8c | ||
|
|
2917942b3e | ||
|
|
db831a3de4 | ||
|
|
ac945d1eba | ||
|
|
ad0c052eff | ||
|
|
1f1d64bab6 | ||
|
|
f4fc517e0f | ||
|
|
be61ba0a20 | ||
|
|
c0c79f019e | ||
|
|
dea5214f21 | ||
|
|
b1941d5dfe | ||
|
|
a23007a635 | ||
|
|
f48a06c040 | ||
|
|
0539a2c4fd | ||
|
|
c06ac071d0 | ||
|
|
52b09609db | ||
|
|
1c108a35e4 | ||
|
|
f333479080 | ||
|
|
3f611654f2 | ||
|
|
e26b71c78e | ||
|
|
3386e155a8 | ||
|
|
3684d590f5 | ||
|
|
dd69abbeee | ||
|
|
1f0e64a6da | ||
|
|
9d170a75d0 | ||
|
|
7199d1b6ef | ||
|
|
bf7afb14cb | ||
|
|
410f5bcb8e | ||
|
|
65ece01135 | ||
|
|
db0c69969e | ||
|
|
94df7bcdfd | ||
|
|
9a014e2df4 | ||
|
|
2b10f1b3a4 | ||
|
|
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 | ||
|
|
6074795b19 | ||
|
|
a139809a97 | ||
|
|
8ff2edd79c | ||
|
|
ebd25676ee | ||
|
|
d9d529cb17 | ||
|
|
b37666a8e8 | ||
|
|
cc2270bb90 | ||
|
|
36fc7b07ea | ||
|
|
8eab3a87e7 | ||
|
|
94d513c85a | ||
|
|
70abe7aada | ||
|
|
9a8c1c4ae7 | ||
|
|
c110036f75 | ||
|
|
d5ab46d662 | ||
|
|
eeaca6d9ac | ||
|
|
a7f45fe6c0 | ||
|
|
b59fd61d56 | ||
|
|
7948619609 | ||
|
|
6e8166d039 | ||
|
|
8fc3e37ca1 | ||
|
|
17d0f4d489 | ||
|
|
61e7e8955a | ||
|
|
4c17f7f83b | ||
|
|
0f29d786b2 | ||
|
|
5ee6b43921 | ||
|
|
f211f78019 | ||
|
|
eeb2a73f16 | ||
|
|
3887633e35 | ||
|
|
db8cf7673b | ||
|
|
dfb852151b | ||
|
|
7b6c85030e | ||
|
|
2d5dcc1a8a | ||
|
|
2b28c46400 | ||
|
|
27714d7286 | ||
|
|
ffef944dd5 | ||
|
|
f4b434a6a5 | ||
|
|
e4a9342e8b | ||
|
|
3ed6b79781 | ||
|
|
6f2270add6 | ||
|
|
220f8d5bf5 | ||
|
|
946418e70e | ||
|
|
2bb60ac40b | ||
|
|
e20777d21d | ||
|
|
311ef7e7e7 | ||
|
|
9e92075cbb | ||
|
|
63043b3d5d | ||
|
|
92a3cce272 | ||
|
|
8339c96e84 | ||
|
|
a125fcb1a4 | ||
|
|
97f634f18f | ||
|
|
82266cf202 | ||
|
|
be94641651 | ||
|
|
703d1d778e | ||
|
|
70c61fa84d | ||
|
|
abd0326b06 | ||
|
|
3e0b4488f8 | ||
|
|
86908c3b4d | ||
|
|
5491c2798e | ||
|
|
a9def6e209 | ||
|
|
c7a2dc45c8 | ||
|
|
cf76b13145 | ||
|
|
db2a17f279 | ||
|
|
3a07cc29bd | ||
|
|
89a5134b66 | ||
|
|
6ca42fdc8f | ||
|
|
a9f81a59c2 | ||
|
|
8a90bf5234 | ||
|
|
357a9cb870 | ||
|
|
8253ed573a | ||
|
|
3f55d82bf7 | ||
|
|
08467d4e12 | ||
|
|
cb70f7873f | ||
|
|
57b0ce73c7 | ||
|
|
25f97431d4 | ||
|
|
f931af5758 | ||
|
|
c88ea9ed61 | ||
|
|
2bdee1b28f | ||
|
|
a1f0b6c361 | ||
|
|
c1e64b90a4 | ||
|
|
79c3d84a98 | ||
|
|
f6c376d087 | ||
|
|
bc15077ecc | ||
|
|
28bf55e572 | ||
|
|
f213d69e17 | ||
|
|
aa6f228ccf | ||
|
|
553b9e9d68 | ||
|
|
ec6e43d7ee | ||
|
|
48140cf8a0 | ||
|
|
796bc9e95b | ||
|
|
67495ad8a9 | ||
|
|
bee1efb11c | ||
|
|
120924f626 | ||
|
|
acdbacb25e | ||
|
|
cde6a38218 | ||
|
|
e69c185e17 | ||
|
|
8cab86af1c | ||
|
|
00557f663a | ||
|
|
54c22aea96 | ||
|
|
a9929c916f | ||
|
|
1d66b6b5da | ||
|
|
a75670c1c2 | ||
|
|
373c729e66 | ||
|
|
7800003c5e | ||
|
|
b662f2e14e | ||
|
|
709c172444 | ||
|
|
6d5ffa0d33 | ||
|
|
f8c7ff2782 | ||
|
|
d9049f69c1 | ||
|
|
10ffbd59e9 | ||
|
|
175fe8573b | ||
|
|
0224e4ac5f | ||
|
|
ecd20b0e1f | ||
|
|
b8f6dc7810 | ||
|
|
0339e745fd | ||
|
|
b3740e9ab6 | ||
|
|
0b7ed64082 | ||
|
|
07b84f4400 | ||
|
|
59e4c3b46c | ||
|
|
0e3bbd0e16 | ||
|
|
763fdd1c4e | ||
|
|
61bd23f0f9 | ||
|
|
8107c1a1e2 | ||
|
|
fa5b518110 | ||
|
|
b3df2836e9 | ||
|
|
08568ee49e | ||
|
|
aba2d5e0ef | ||
|
|
dfebed38ab | ||
|
|
51782fc5d7 | ||
|
|
11f1a7c491 | ||
|
|
5573f7fcdf | ||
|
|
1d743cfc84 | ||
|
|
21c01558fd | ||
|
|
6d57445167 | ||
|
|
52f6a5b124 | ||
|
|
2c3c73f045 | ||
|
|
54fc513fc9 | ||
|
|
0b1e43cb87 | ||
|
|
c8dabb225c | ||
|
|
245dc12ade | ||
|
|
7a405232a5 | ||
|
|
00f7ca1d02 | ||
|
|
f1cea7e788 | ||
|
|
050fcf7a83 | ||
|
|
dfa350bea7 | ||
|
|
e5d5b99f0e | ||
|
|
ed7cedd78f | ||
|
|
a22eb70cfb | ||
|
|
2a81b25a5b | ||
|
|
7db146df47 | ||
|
|
6182dfff39 | ||
|
|
1ccd8cea6b | ||
|
|
f9ea04eb6b | ||
|
|
ab9a5a1578 | ||
|
|
3364a73a97 | ||
|
|
d02a7f415b | ||
|
|
1874fd7c30 | ||
|
|
3c4a04ea02 | ||
|
|
1468917743 | ||
|
|
6df152cc5d | ||
|
|
c6b3549b61 | ||
|
|
f80ab2aae8 | ||
|
|
b2f9f19d99 | ||
|
|
cb7304837c | ||
|
|
9e6f58fe27 | ||
|
|
e3fb39da3d | ||
|
|
cc275813b5 | ||
|
|
9d57245d65 | ||
|
|
9a05b3597e | ||
|
|
41f38b60e8 | ||
|
|
a9bbc38919 | ||
|
|
e282ab0e63 | ||
|
|
546d5203d4 | ||
|
|
69941e602b | ||
|
|
7ac9c2e888 | ||
|
|
fa1f50d173 | ||
|
|
20ddd5f11a | ||
|
|
e1891fd615 | ||
|
|
cc0a96a8d9 | ||
|
|
7b2b302022 | ||
|
|
4d66f78ca2 | ||
|
|
6db6db41a2 | ||
|
|
713baa40e1 | ||
|
|
7c196f5b32 | ||
|
|
bd360a15ef | ||
|
|
ae7f25332a | ||
|
|
63fc15d276 | ||
|
|
d88575dadf | ||
|
|
93c963e30f | ||
|
|
72a79e5cec | ||
|
|
140ed41907 | ||
|
|
12fc168516 | ||
|
|
2c3dc42ae8 | ||
|
|
48dadd8e10 | ||
|
|
ce4c46b37d | ||
|
|
c8938a99b2 | ||
|
|
b7998e815a | ||
|
|
444229a9dc | ||
|
|
b69dac6f4d | ||
|
|
94fdbadaec | ||
|
|
c4b23246b4 | ||
|
|
5186788969 | ||
|
|
169ad8245f | ||
|
|
71e013a197 | ||
|
|
7e60d37171 | ||
|
|
a9e9e64eab | ||
|
|
39ae8d4629 | ||
|
|
a075870308 | ||
|
|
186649102d | ||
|
|
bb2e100e7f | ||
|
|
2c5fa40c0d | ||
|
|
0b61eda84c | ||
|
|
23ba929f3f | ||
|
|
13647075f2 | ||
|
|
4c9414c4c1 | ||
|
|
ec88f95722 | ||
|
|
f377cd631e | ||
|
|
e41aab84f8 | ||
|
|
0a6c78cbb8 | ||
|
|
004130cb11 | ||
|
|
a300223122 | ||
|
|
0769c40368 | ||
|
|
355203afdb | ||
|
|
46bfcd0d83 | ||
|
|
46b2f86372 | ||
|
|
e5c86178f5 | ||
|
|
0c31d6eabf | ||
|
|
0e08d67e48 | ||
|
|
42904cb3d7 | ||
|
|
8efb1bc6e2 | ||
|
|
8bdf221935 | ||
|
|
899fb7faa1 | ||
|
|
34622b74ef | ||
|
|
d327976064 | ||
|
|
07b9c4696d | ||
|
|
dd30de3c5a | ||
|
|
7a5913b8a6 | ||
|
|
fbb268fbce | ||
|
|
a31c267e83 | ||
|
|
b82fd79f57 | ||
|
|
e269fcaf0d | ||
|
|
a8c61b0001 | ||
|
|
4708480e7d | ||
|
|
00b75759f1 | ||
|
|
f85ce74a1f | ||
|
|
30284944b1 | ||
|
|
65f1399a44 | ||
|
|
559d36601d | ||
|
|
894ec9f84e | ||
|
|
d86ffcf06d | ||
|
|
ab5beaff63 | ||
|
|
f0ef33b45f | ||
|
|
1fa158c663 | ||
|
|
a51fe07420 | ||
|
|
a631f86b3b | ||
|
|
a4ec5aafa6 | ||
|
|
984639ba67 | ||
|
|
9923c1dee9 | ||
|
|
1b0a4849df | ||
|
|
4fc43f322f | ||
|
|
5c0f78ab02 | ||
|
|
d8c7060cc8 | ||
|
|
b0335b0a61 | ||
|
|
a1797ccd4b | ||
|
|
a1c997bc58 | ||
|
|
0b9b28d251 | ||
|
|
a79b2fa761 | ||
|
|
195eba74de | ||
|
|
3edebe5d61 | ||
|
|
b56389fbc8 | ||
|
|
203293db0b | ||
|
|
2b7ea448c4 | ||
|
|
c627f1b7d0 | ||
|
|
831f1baa4a | ||
|
|
4c20afd4b7 | ||
|
|
f689434f61 | ||
|
|
b694ee68c9 | ||
|
|
8b22ca2ffd | ||
|
|
5c50c4844f | ||
|
|
4fcad6e4bc | ||
|
|
3b2316e0d8 | ||
|
|
a821cce390 | ||
|
|
abe142ac66 | ||
|
|
5eff0e5ca7 | ||
|
|
c06242efd3 | ||
|
|
f69f387e1d | ||
|
|
f5c10aec7f | ||
|
|
34bd9d9092 | ||
|
|
c30151d03a | ||
|
|
f784c477f0 | ||
|
|
0d3441d8b3 | ||
|
|
23e14223bd | ||
|
|
4b94f2b8bf | ||
|
|
f88eedc3c8 | ||
|
|
5c24bf8c1d | ||
|
|
9cd51b1f6b | ||
|
|
2f8eb63557 | ||
|
|
4bdc30734c | ||
|
|
4743744efc | ||
|
|
c2d98bde72 | ||
|
|
c899c98f31 | ||
|
|
9e97e6c691 | ||
|
|
afdfdb815e | ||
|
|
d043c33351 | ||
|
|
53f0f4d66d | ||
|
|
168b207c6d | ||
|
|
f6ebb7b8d6 | ||
|
|
c826fadb9f | ||
|
|
dfebed6c2f | ||
|
|
3a2545b497 | ||
|
|
097094cd7a | ||
|
|
c3fa1bf5af | ||
|
|
8798cfbced | ||
|
|
81d71ebb08 | ||
|
|
074e74c6c5 | ||
|
|
65b8272724 | ||
|
|
ca3f3379cb | ||
|
|
faad10baee | ||
|
|
d3eac92eaa | ||
|
|
fb5b9bc0b1 | ||
|
|
34505071f4 | ||
|
|
0a2d0b9d0b | ||
|
|
4495f5987a | ||
|
|
aad29d5d92 | ||
|
|
ad8452035d | ||
|
|
ffa17e1205 | ||
|
|
59e250b186 | ||
|
|
b2ffed891b | ||
|
|
9bf10ed051 | ||
|
|
9cfdb8ed97 | ||
|
|
27ab98dca4 | ||
|
|
a8ca11cb47 | ||
|
|
d39d1401c4 | ||
|
|
159c75096d | ||
|
|
a70347b15f | ||
|
|
1a2c8aa31f | ||
|
|
15e1e68cfd | ||
|
|
167da44211 | ||
|
|
1372838092 | ||
|
|
7257e1ec36 | ||
|
|
af5960bc28 | ||
|
|
206cfdf9c6 | ||
|
|
65d7229079 | ||
|
|
c18aa40ba6 | ||
|
|
249f33da16 | ||
|
|
b9a7c8be90 | ||
|
|
e4ac72a316 | ||
|
|
dff9cbfe1b | ||
|
|
489bc860d2 | ||
|
|
29a8fff576 | ||
|
|
e66a68f0fa | ||
|
|
4058eacbd4 | ||
|
|
8cbd1544b9 | ||
|
|
f302744fec | ||
|
|
a3ba4e2c10 | ||
|
|
a483f9cafd | ||
|
|
b43c82f3db | ||
|
|
84f0099c1d | ||
|
|
c6a883c8a9 | ||
|
|
af2d9f285b | ||
|
|
40c1f8da2b |
32
.github/ISSUE_TEMPLATE.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
**Note**: for support questions, please use one of these channels: [stackoverflow](http://stackoverflow.com/questions/tagged/socket.io) or [slack](https://socketio.slack.com)
|
||||
|
||||
For bug reports and feature requests for the **Swift client**, please open an issue [there](https://github.com/socketio/socket.io-client-swift).
|
||||
|
||||
For bug reports and feature requests for the **Java client**, please open an issue [there](https://github.com/socketio/socket.io-client-java).
|
||||
|
||||
### You want to:
|
||||
|
||||
* [x] report a *bug*
|
||||
* [ ] request a *feature*
|
||||
|
||||
### Current behaviour
|
||||
|
||||
*What is actually happening?*
|
||||
|
||||
### Steps to reproduce (if the current behaviour is a bug)
|
||||
|
||||
**Note**: the best way (and by that we mean **the only way**) to get a quick answer is to provide a failing test case by forking the following [fiddle](https://github.com/socketio/socket.io-fiddle).
|
||||
|
||||
### Expected behaviour
|
||||
|
||||
*What is expected?*
|
||||
|
||||
### 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)
|
||||
|
||||
|
||||
24
.github/workflows/ci.yml
vendored
Normal file
24
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test-node:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x, 14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
env:
|
||||
CI: true
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -6,4 +6,9 @@ lib-cov
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
benchmarks/*.png
|
||||
node_modules
|
||||
coverage
|
||||
.idea
|
||||
dist
|
||||
.nyc_output
|
||||
@@ -1,3 +0,0 @@
|
||||
support
|
||||
test
|
||||
examples
|
||||
36
CHANGELOG.md
Normal file
36
CHANGELOG.md
Normal file
@@ -0,0 +1,36 @@
|
||||
## [2.5.1](https://github.com/socketio/socket.io/compare/2.5.0...2.5.1) (2024-06-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add a noop handler for the error event ([d30630b](https://github.com/socketio/socket.io/commit/d30630ba10562bf987f4d2b42440fc41a828119c))
|
||||
|
||||
|
||||
|
||||
# [2.5.0](https://github.com/socketio/socket.io/compare/2.4.1...2.5.0) (2022-06-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix race condition in dynamic namespaces ([05e1278](https://github.com/socketio/socket.io/commit/05e1278cfa99f3ecf3f8f0531ffe57d850e9a05b))
|
||||
* ignore packet received after disconnection ([22d4bdf](https://github.com/socketio/socket.io/commit/22d4bdf00d1a03885dc0171125faddfaef730066))
|
||||
* only set 'connected' to true after middleware execution ([226cc16](https://github.com/socketio/socket.io/commit/226cc16165f9fe60f16ff4d295fb91c8971cde35))
|
||||
* prevent the socket from joining a room after disconnection ([f223178](https://github.com/socketio/socket.io/commit/f223178eb655a7713303b21a78f9ef9e161d6458))
|
||||
|
||||
|
||||
|
||||
## [2.4.1](https://github.com/socketio/socket.io/compare/2.4.0...2.4.1) (2021-01-07)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* fix(security): do not allow all origins by default ([a169050](https://github.com/socketio/socket.io/commit/a1690509470e9dd5559cec4e60908ca6c23e9ba0))
|
||||
|
||||
|
||||
# [2.4.0](https://github.com/socketio/socket.io/compare/2.3.0...2.4.0) (2021-01-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **security:** do not allow all origins by default ([f78a575](https://github.com/socketio/socket.io/commit/f78a575f66ab693c3ea96ea88429ddb1a44c86c7))
|
||||
* properly overwrite the query sent in the handshake ([d33a619](https://github.com/socketio/socket.io/commit/d33a619905a4905c153d4fec337c74da5b533a9e))
|
||||
34
History.md
34
History.md
@@ -1,34 +0,0 @@
|
||||
|
||||
0.7.3 / 2011-06-30
|
||||
==================
|
||||
|
||||
* Exposed handshake data to clients.
|
||||
* Refactored dispatcher interface.
|
||||
* Changed; Moved id generation method into the manager.
|
||||
* Added sub-namespace authorization. [3rd-Eden]
|
||||
* Changed; normalized SocketNamespace local eventing [dvv]
|
||||
* Changed; Use packet.reason or default to 'packet' [3rd-Eden]
|
||||
* Changed console.error to console.log.
|
||||
* Fixed; bind both servers at the same time do that the test never times out.
|
||||
* Added 304 support.
|
||||
* Removed `Transport#name` for abstract interface.
|
||||
* Changed; lazily require http and https module only when needed. [3rd-Eden]
|
||||
|
||||
0.7.2 / 2011-06-22
|
||||
==================
|
||||
|
||||
* Make sure to write a packet (of type `noop`) when closing a poll.
|
||||
This solves a problem with cross-domain requests being flagged as aborted and
|
||||
reconnection being triggered.
|
||||
* Added `noop` message type.
|
||||
|
||||
0.7.1 / 2011-06-21
|
||||
==================
|
||||
|
||||
* Fixed cross-domain XHR.
|
||||
* Added CORS test to xhr-polling suite.
|
||||
|
||||
0.7.0 / 2010-06-21
|
||||
==================
|
||||
|
||||
* http://socket.io/announcement.html
|
||||
22
LICENSE
Normal file
22
LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014-2018 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.
|
||||
19
Makefile
19
Makefile
@@ -1,19 +0,0 @@
|
||||
|
||||
ALL_TESTS = $(shell find test/ -name '*.test.js')
|
||||
|
||||
run-tests:
|
||||
@npm link > /dev/null --local
|
||||
@./node_modules/.bin/expresso \
|
||||
-I support \
|
||||
-I lib \
|
||||
--serial \
|
||||
$(TESTFLAGS) \
|
||||
$(TESTS)
|
||||
|
||||
test:
|
||||
@$(MAKE) TESTS="$(ALL_TESTS)" run-tests
|
||||
|
||||
test-cov:
|
||||
@TESTFLAGS=--cov $(MAKE) test
|
||||
|
||||
.PHONY: test
|
||||
494
Readme.md
494
Readme.md
@@ -1,343 +1,243 @@
|
||||
# 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://github.com/socketio/socket.io/actions)
|
||||
[](https://david-dm.org/socketio/socket.io)
|
||||
[](https://david-dm.org/socketio/socket.io#info=devDependencies)
|
||||
[](https://www.npmjs.com/package/socket.io)
|
||||

|
||||
[](https://slackin-socketio.now.sh)
|
||||
|
||||
npm install socket.io
|
||||
## Features
|
||||
|
||||
Socket.IO enables real-time bidirectional event-based communication. It consists of:
|
||||
|
||||
- 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)
|
||||
- [Dart](https://github.com/rikulo/socket.io-client-dart)
|
||||
|
||||
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
|
||||
|
||||
A 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', socket => {
|
||||
socket.emit('request', /* … */); // emit an event to the socket
|
||||
io.emit('broadcast', /* … */); // emit an event to all connected sockets
|
||||
socket.on('reply', () => { /* … */ }); // 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
|
||||
```
|
||||
|
||||
## 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);
|
||||
});
|
||||
const server = require('http').createServer();
|
||||
const io = require('socket.io')(server);
|
||||
io.on('connection', client => {
|
||||
client.on('event', data => { /* … */ });
|
||||
client.on('disconnect', () => { /* … */ });
|
||||
});
|
||||
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 () {
|
||||
sockets.emit('user disconnected');
|
||||
});
|
||||
});
|
||||
const io = require('socket.io')();
|
||||
io.on('connection', 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
const app = require('express')();
|
||||
const server = require('http').createServer(app);
|
||||
const io = require('socket.io')(server);
|
||||
io.on('connection', () => { /* … */ });
|
||||
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' });
|
||||
});
|
||||
const app = require('koa')();
|
||||
const server = require('http').createServer(app.callback());
|
||||
const io = require('socket.io')(server);
|
||||
io.on('connection', () => { /* … */ });
|
||||
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('connection', function () {
|
||||
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)
|
||||
|
||||
880
docs/API.md
Normal file
880
docs/API.md
Normal file
@@ -0,0 +1,880 @@
|
||||
|
||||
## 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.adapter](#namespaceadapter)
|
||||
- [namespace.to(room)](#namespacetoroom)
|
||||
- [namespace.in(room)](#namespaceinroom)
|
||||
- [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)
|
||||
- [Flag: 'binary'](#flag-binary)
|
||||
- [Class: Socket](#socket)
|
||||
- [socket.id](#socketid)
|
||||
- [socket.rooms](#socketrooms)
|
||||
- [socket.client](#socketclient)
|
||||
- [socket.conn](#socketconn)
|
||||
- [socket.request](#socketrequest)
|
||||
- [socket.handshake](#sockethandshake)
|
||||
- [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)
|
||||
- [Flag: 'binary'](#flag-binary-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 (`*:*`)
|
||||
- `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
|
||||
const io = require('socket.io')();
|
||||
// or
|
||||
const Server = require('socket.io');
|
||||
const 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.
|
||||
|
||||
```js
|
||||
const server = require('http').createServer();
|
||||
|
||||
const io = require('socket.io')(server, {
|
||||
path: '/test',
|
||||
serveClient: false,
|
||||
// below are engine.IO options
|
||||
pingInterval: 10000,
|
||||
pingTimeout: 5000,
|
||||
cookie: false
|
||||
});
|
||||
|
||||
server.listen(3000);
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
```js
|
||||
const server = require('http').createServer();
|
||||
|
||||
const io = require('socket.io')(3000, {
|
||||
path: '/test',
|
||||
serveClient: false,
|
||||
// below are engine.IO options
|
||||
pingInterval: 10000,
|
||||
pingTimeout: 5000,
|
||||
cookie: false
|
||||
});
|
||||
```
|
||||
|
||||
#### new Server(options)
|
||||
|
||||
- `options` _(Object)_
|
||||
|
||||
See [above](#new-serverhttpserver-options) for available options.
|
||||
|
||||
```js
|
||||
const io = require('socket.io')({
|
||||
path: '/test',
|
||||
serveClient: false,
|
||||
});
|
||||
|
||||
// either
|
||||
const server = require('http').createServer();
|
||||
|
||||
io.attach(server, {
|
||||
pingInterval: 10000,
|
||||
pingTimeout: 5000,
|
||||
cookie: false
|
||||
});
|
||||
|
||||
server.listen(3000);
|
||||
|
||||
// or
|
||||
io.attach(3000, {
|
||||
pingInterval: 10000,
|
||||
pingTimeout: 5000,
|
||||
cookie: false
|
||||
});
|
||||
```
|
||||
|
||||
#### 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
|
||||
const io = require('socket.io')(http, { serveClient: false });
|
||||
|
||||
// or pass no server and then you can call the method
|
||||
const 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.
|
||||
|
||||
```js
|
||||
const io = require('socket.io')();
|
||||
io.path('/myownpath');
|
||||
|
||||
// client-side
|
||||
const socket = io({
|
||||
path: '/myownpath'
|
||||
});
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
```js
|
||||
const io = require('socket.io')(3000);
|
||||
const redis = require('socket.io-redis');
|
||||
io.adapter(redis({ host: 'localhost', port: 6379 }));
|
||||
```
|
||||
|
||||
#### server.origins([value])
|
||||
|
||||
- `value` _(String|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.
|
||||
|
||||
```js
|
||||
io.origins(['https://foo.example.com:443']);
|
||||
```
|
||||
|
||||
#### 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. If `success` is set to `false`, `error` must be provided as a string value that will be appended to the server response, e.g. "Origin not allowed".
|
||||
|
||||
__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 you can use [cors](https://github.com/expressjs/cors).
|
||||
|
||||
```js
|
||||
io.origins((origin, callback) => {
|
||||
if (origin !== 'https://foo.example.com') {
|
||||
return callback('origin not allowed', false);
|
||||
}
|
||||
callback(null, true);
|
||||
});
|
||||
```
|
||||
|
||||
#### 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|RegExp|Function)_
|
||||
- **Returns** `Namespace`
|
||||
|
||||
Initializes and retrieves the given `Namespace` by its pathname identifier `nsp`. If the namespace was already initialized it returns it immediately.
|
||||
|
||||
```js
|
||||
const adminNamespace = io.of('/admin');
|
||||
```
|
||||
|
||||
A regex or a function can also be provided, in order to create namespace in a dynamic way:
|
||||
|
||||
```js
|
||||
const dynamicNsp = io.of(/^\/dynamic-\d+$/).on('connect', (socket) => {
|
||||
const newNamespace = socket.nsp; // newNamespace.name === '/dynamic-101'
|
||||
|
||||
// broadcast to all clients in the given sub-namespace
|
||||
newNamespace.emit('hello');
|
||||
});
|
||||
|
||||
// client-side
|
||||
const socket = io('/dynamic-101');
|
||||
|
||||
// broadcast to all clients in each sub-namespace
|
||||
dynamicNsp.emit('hello');
|
||||
|
||||
// use a middleware for each sub-namespace
|
||||
dynamicNsp.use((socket, next) => { /* ... */ });
|
||||
```
|
||||
|
||||
With a function:
|
||||
|
||||
```js
|
||||
io.of((name, query, next) => {
|
||||
next(null, checkToken(query.token));
|
||||
}).on('connect', (socket) => { /* ... */ });
|
||||
```
|
||||
|
||||
#### 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
|
||||
const Server = require('socket.io');
|
||||
const PORT = 3030;
|
||||
const server = require('http').Server();
|
||||
|
||||
const 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 = (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`).
|
||||
|
||||
A client always connects to `/` (the main namespace), then potentially connect to other namespaces (while using the same underlying connection).
|
||||
|
||||
#### 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.adapter
|
||||
|
||||
* _(Adapter)_
|
||||
|
||||
The `Adapter` used for the namespace. Useful when using the `Adapter` based on [Redis](https://github.com/socketio/socket.io-redis), as it exposes methods to manage sockets and rooms accross your cluster.
|
||||
|
||||
**Note:** the adapter of the main namespace can be accessed with `io.of('/').adapter`.
|
||||
|
||||
#### namespace.to(room)
|
||||
|
||||
- `room` _(String)_
|
||||
- **Returns** `Namespace` 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
|
||||
const io = require('socket.io')();
|
||||
const adminNamespace = io.of('/admin');
|
||||
|
||||
adminNamespace.to('level1').emit('an event', { some: 'data' });
|
||||
```
|
||||
|
||||
#### namespace.in(room)
|
||||
|
||||
Synonym of [namespace.to(room)](#namespacetoroom).
|
||||
|
||||
#### namespace.emit(eventName[, ...args])
|
||||
|
||||
- `eventName` _(String)_
|
||||
- `args`
|
||||
|
||||
Emits an event to all connected clients. The following two are equivalent:
|
||||
|
||||
```js
|
||||
const io = require('socket.io')();
|
||||
io.emit('an event sent to all connected clients'); // main namespace
|
||||
|
||||
const chat = io.of('/chat');
|
||||
chat.emit('an event sent to all connected clients in chat namespace');
|
||||
```
|
||||
|
||||
**Note:** acknowledgements are not supported when emitting from namespace.
|
||||
|
||||
#### namespace.clients(callback)
|
||||
|
||||
- `callback` _(Function)_
|
||||
|
||||
Gets a list of client IDs connected to this namespace (across all nodes if applicable).
|
||||
|
||||
```js
|
||||
const io = require('socket.io')();
|
||||
io.of('/chat').clients((error, clients) => {
|
||||
if (error) throw error;
|
||||
console.log(clients); // => [PZDoMHjiu8PYfRiKAAAF, Anw2LatarvGVVXEIAAAD]
|
||||
});
|
||||
```
|
||||
|
||||
An example to get all clients in namespace's room:
|
||||
|
||||
```js
|
||||
io.of('/chat').in('general').clients((error, clients) => {
|
||||
if (error) throw error;
|
||||
console.log(clients); // => [Anw2LatarvGVVXEIAAAD]
|
||||
});
|
||||
```
|
||||
|
||||
As with broadcasting, the default is all clients from the default namespace ('/'):
|
||||
|
||||
```js
|
||||
io.clients((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
|
||||
io.use((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.
|
||||
|
||||
```js
|
||||
io.on('connect', (socket) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
io.of('/admin').on('connect', (socket) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
#### 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: 'binary'
|
||||
|
||||
Specifies whether there is binary data in the emitted data. Increases performance when specified. Can be `true` or `false`.
|
||||
|
||||
```js
|
||||
io.binary(false).emit('an event', { some: 'data' });
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
```js
|
||||
io.on('connection', (socket) => {
|
||||
socket.join('room 237', () => {
|
||||
let rooms = Object.keys(socket.rooms);
|
||||
console.log(rooms); // [ <socket.id>, 'room 237' ]
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### 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.handshake
|
||||
|
||||
* _(Object)_
|
||||
|
||||
The handshake details:
|
||||
|
||||
```js
|
||||
{
|
||||
headers: /* the headers sent as part of the handshake */,
|
||||
time: /* the date of creation (as string) */,
|
||||
address: /* the ip of the client */,
|
||||
xdomain: /* whether the connection is cross-domain */,
|
||||
secure: /* whether the connection is secure */,
|
||||
issued: /* the date of creation (as unix timestamp) */,
|
||||
url: /* the request URL string */,
|
||||
query: /* the query object */
|
||||
}
|
||||
```
|
||||
|
||||
Usage:
|
||||
|
||||
```js
|
||||
io.use((socket, next) => {
|
||||
let handshake = socket.handshake;
|
||||
// ...
|
||||
});
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
let handshake = socket.handshake;
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
#### 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
|
||||
io.on('connection', (socket) => {
|
||||
socket.use((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: Buffer.alloc(6) });
|
||||
```
|
||||
|
||||
The `ack` argument is optional and will be called with the client's answer.
|
||||
|
||||
```js
|
||||
io.on('connection', (socket) => {
|
||||
socket.emit('an event', { some: 'data' });
|
||||
|
||||
socket.emit('ferret', 'tobi', (data) => {
|
||||
console.log(data); // data will be 'woot'
|
||||
});
|
||||
|
||||
// the client code
|
||||
// client.on('ferret', (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', (data) => {
|
||||
console.log(data);
|
||||
});
|
||||
// with several arguments
|
||||
socket.on('news', (arg1, arg2, arg3) => {
|
||||
// ...
|
||||
});
|
||||
// or with acknowledgement
|
||||
socket.on('news', (data, callback) => {
|
||||
callback(0);
|
||||
});
|
||||
```
|
||||
|
||||
#### 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', (socket) => {
|
||||
socket.join('room 237', () => {
|
||||
let rooms = Object.keys(socket.rooms);
|
||||
console.log(rooms); // [ <socket.id>, 'room 237' ]
|
||||
io.to('room 237').emit('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 its id (see `Socket#id`). This makes it easy to broadcast messages to other sockets:
|
||||
|
||||
```js
|
||||
io.on('connection', (socket) => {
|
||||
socket.on('say to someone', (id, msg) => {
|
||||
// send a private message to the socket with the given id
|
||||
socket.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` (the socket itself being excluded).
|
||||
|
||||
To emit to multiple rooms, you can call `to` several times.
|
||||
|
||||
```js
|
||||
io.on('connection', (socket) => {
|
||||
// to one room
|
||||
socket.to('others').emit('an event', { some: 'data' });
|
||||
// to multiple rooms
|
||||
socket.to('room1').to('room2').emit('hello');
|
||||
// a private message to another socket
|
||||
socket.to(/* another socket id */).emit('hey');
|
||||
});
|
||||
```
|
||||
|
||||
**Note:** acknowledgements are not supported when broadcasting.
|
||||
|
||||
#### 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.
|
||||
|
||||
```js
|
||||
io.on('connection', (socket) => {
|
||||
socket.compress(false).emit('uncompressed', "that's rough");
|
||||
});
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
```js
|
||||
io.on('connection', (socket) => {
|
||||
setTimeout(() => socket.disconnect(true), 5000);
|
||||
});
|
||||
```
|
||||
|
||||
#### 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
|
||||
io.on('connection', (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
|
||||
io.on('connection', (socket) => {
|
||||
socket.volatile.emit('an event', { some: 'data' }); // the client may or may not receive it
|
||||
});
|
||||
```
|
||||
|
||||
#### Flag: 'binary'
|
||||
|
||||
Specifies whether there is binary data in the emitted data. Increases performance when specified. Can be `true` or `false`.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.binary(false).emit('an event', { some: 'data' }); // The data to send has no binary data
|
||||
});
|
||||
```
|
||||
|
||||
#### Event: 'disconnect'
|
||||
|
||||
- `reason` _(String)_ the reason of the disconnection (either client or server-side)
|
||||
|
||||
Fired upon disconnection.
|
||||
|
||||
```js
|
||||
io.on('connection', (socket) => {
|
||||
socket.on('disconnect', (reason) => {
|
||||
// ...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### Event: 'error'
|
||||
|
||||
- `error` _(Object)_ error object
|
||||
|
||||
Fired when an error occurs.
|
||||
|
||||
```js
|
||||
io.on('connection', (socket) => {
|
||||
socket.on('error', (error) => {
|
||||
// ...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### 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).
|
||||
|
||||
```js
|
||||
io.on('connection', (socket) => {
|
||||
socket.on('disconnecting', (reason) => {
|
||||
let rooms = Object.keys(socket.rooms);
|
||||
// ...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
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)
|
||||
64
docs/emit.md
Normal file
64
docs/emit.md
Normal file
@@ -0,0 +1,64 @@
|
||||
|
||||
## 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)
|
||||
io.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?');
|
||||
|
||||
// specifying whether the data to send has binary data
|
||||
socket.binary(false).emit('what', 'I have no binaries!');
|
||||
|
||||
// sending to all clients on this node (when using multiple nodes)
|
||||
io.local.emit('hi', 'my lovely babies');
|
||||
|
||||
// sending to all connected clients
|
||||
io.emit('an event sent to all connected clients');
|
||||
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
**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,95 +0,0 @@
|
||||
|
||||
/**
|
||||
* Bootstrap app.
|
||||
*/
|
||||
|
||||
require.paths.unshift(__dirname + '/../../lib/');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('express')
|
||||
, stylus = require('stylus')
|
||||
, nib = require('nib')
|
||||
, sio = require('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.set('transports', [
|
||||
'websocket'
|
||||
, 'flashsocket'
|
||||
, 'htmlfile'
|
||||
, 'xhr-polling'
|
||||
, 'jsonp-polling'
|
||||
]);
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
socket.on('user message', function (msg) {
|
||||
socket.broadcast.emit('user message', socket.nickname, msg);
|
||||
});
|
||||
|
||||
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
|
||||
76
examples/chat/index.js
Normal file
76
examples/chat/index.js
Normal file
@@ -0,0 +1,76 @@
|
||||
// Setup basic express server
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var path = require('path');
|
||||
var server = require('http').createServer(app);
|
||||
var io = require('../..')(server);
|
||||
var port = process.env.PORT || 3000;
|
||||
|
||||
server.listen(port, () => {
|
||||
console.log('Server listening at port %d', port);
|
||||
});
|
||||
|
||||
// Routing
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
// Chatroom
|
||||
|
||||
var numUsers = 0;
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
var addedUser = false;
|
||||
|
||||
// when the client emits 'new message', this listens and executes
|
||||
socket.on('new message', (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', (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', () => {
|
||||
socket.broadcast.emit('typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'stop typing', we broadcast it to others
|
||||
socket.on('stop typing', () => {
|
||||
socket.broadcast.emit('stop typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the user disconnects.. perform this
|
||||
socket.on('disconnect', () => {
|
||||
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.3.11"
|
||||
, "jade": "0.12.1"
|
||||
, "stylus": "0.13.3"
|
||||
, "nib": "0.0.8"
|
||||
}
|
||||
"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();
|
||||
|
||||
const 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
|
||||
const 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
|
||||
const 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
|
||||
const log = (message, options) => {
|
||||
var $el = $('<li>').addClass('log').text(message);
|
||||
addMessageElement($el, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat message to the message list
|
||||
const 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
|
||||
const addChatTyping = (data) => {
|
||||
data.typing = true;
|
||||
data.message = 'is typing';
|
||||
addChatMessage(data);
|
||||
}
|
||||
|
||||
// Removes the visual chat typing message
|
||||
const 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)
|
||||
const 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
|
||||
const cleanInput = (input) => {
|
||||
return $('<div/>').text(input).html();
|
||||
}
|
||||
|
||||
// Updates the typing event
|
||||
const updateTyping = () => {
|
||||
if (connected) {
|
||||
if (!typing) {
|
||||
typing = true;
|
||||
socket.emit('typing');
|
||||
}
|
||||
lastTypingTime = (new Date()).getTime();
|
||||
|
||||
setTimeout(() => {
|
||||
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
|
||||
const getTypingMessages = (data) => {
|
||||
return $('.typing.message').filter(function (i) {
|
||||
return $(this).data('username') === data.username;
|
||||
});
|
||||
}
|
||||
|
||||
// Gets the color of a username through our hash function
|
||||
const 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(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', () => {
|
||||
updateTyping();
|
||||
});
|
||||
|
||||
// Click events
|
||||
|
||||
// Focus input when clicking anywhere on login page
|
||||
$loginPage.click(() => {
|
||||
$currentInput.focus();
|
||||
});
|
||||
|
||||
// Focus input when clicking on the message input's border
|
||||
$inputMessage.click(() => {
|
||||
$inputMessage.focus();
|
||||
});
|
||||
|
||||
// Socket events
|
||||
|
||||
// Whenever the server emits 'login', log the login message
|
||||
socket.on('login', (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', (data) => {
|
||||
addChatMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user joined', log it in the chat body
|
||||
socket.on('user joined', (data) => {
|
||||
log(data.username + ' joined');
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user left', log it in the chat body
|
||||
socket.on('user left', (data) => {
|
||||
log(data.username + ' left');
|
||||
addParticipantsMessage(data);
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'typing', show the typing message
|
||||
socket.on('typing', (data) => {
|
||||
addChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'stop typing', kill the typing message
|
||||
socket.on('stop typing', (data) => {
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
log('you have been disconnected');
|
||||
});
|
||||
|
||||
socket.on('reconnect', () => {
|
||||
log('you have been reconnected');
|
||||
if (username) {
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('reconnect_error', () => {
|
||||
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": "MIT",
|
||||
"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: `Buffer.allocUnsafe(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 = Buffer.allocUnsafe(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,81 +0,0 @@
|
||||
|
||||
/**
|
||||
* Bootstrap app.
|
||||
*/
|
||||
|
||||
require.paths.unshift(__dirname + '/../../lib/');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('express')
|
||||
, stylus = require('stylus')
|
||||
, nib = require('nib')
|
||||
, sio = require('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('sys');
|
||||
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.3.11"
|
||||
, "jade": "0.12.1"
|
||||
, "stylus": "0.13.3"
|
||||
, "nib": "0.0.8"
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
16
examples/webpack-build-server/lib/index.js
Normal file
16
examples/webpack-build-server/lib/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
const server = require('http').createServer();
|
||||
const io = require('socket.io')(server, {
|
||||
serveClient: false,
|
||||
wsEngine: 'ws' // uws is not supported since it is a native module
|
||||
});
|
||||
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));
|
||||
}
|
||||
15
examples/webpack-build-server/package.json
Normal file
15
examples/webpack-build-server/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"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",
|
||||
"devDependencies": {
|
||||
"socket.io": "^2.0.3",
|
||||
"webpack": "^2.6.1"
|
||||
}
|
||||
}
|
||||
9
examples/webpack-build-server/support/webpack.config.js
Normal file
9
examples/webpack-build-server/support/webpack.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
target: 'node',
|
||||
output: {
|
||||
path: require('path').join(__dirname, '../dist'),
|
||||
filename: 'server.js'
|
||||
}
|
||||
};
|
||||
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>
|
||||
12
examples/webpack-build/lib/index.js
Normal file
12
examples/webpack-build/lib/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
import io from 'socket.io-client';
|
||||
|
||||
const socket = io('http://localhost:3000');
|
||||
|
||||
console.log('init');
|
||||
|
||||
socket.on('connect', onConnect);
|
||||
|
||||
function onConnect(){
|
||||
console.log('connect ' + socket.id);
|
||||
}
|
||||
21
examples/webpack-build/package.json
Normal file
21
examples/webpack-build/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"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-json-parser": "webpack --config ./support/webpack.config.json-parser.js",
|
||||
"build-all": "npm run build && npm run build-slim && npm run build-json-parser"
|
||||
},
|
||||
"author": "Damien Arrachequesne",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"socket.io-client": "^2.0.2",
|
||||
"socket.io-json-parser": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"strip-loader": "^0.1.2",
|
||||
"webpack": "^2.6.1"
|
||||
}
|
||||
}
|
||||
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: require('path').join(__dirname, '../dist'),
|
||||
filename: 'app.js'
|
||||
}
|
||||
};
|
||||
33
examples/webpack-build/support/webpack.config.json-parser.js
Normal file
33
examples/webpack-build/support/webpack.config.json-parser.js
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
var webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
path: require('path').join(__dirname, '../dist'),
|
||||
filename: 'app.json-parser.js'
|
||||
},
|
||||
// generate sourcemap
|
||||
devtool: 'source-map',
|
||||
plugins: [
|
||||
// replace require('debug')() with an noop function
|
||||
new webpack.NormalModuleReplacementPlugin(/debug/, process.cwd() + '/support/noop.js'),
|
||||
// replace socket.io-parser with socket.io-json-parser
|
||||
new webpack.NormalModuleReplacementPlugin(/socket\.io-parser/, 'socket.io-json-parser'),
|
||||
// use uglifyJS (IE9+ support)
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
})
|
||||
],
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
// strip `debug()` calls
|
||||
test: /\.js$/,
|
||||
loader: 'strip-loader?strip[]=debug'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
31
examples/webpack-build/support/webpack.config.slim.js
Normal file
31
examples/webpack-build/support/webpack.config.slim.js
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
var webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
path: require('path').join(__dirname, '../dist'),
|
||||
filename: 'app.slim.js'
|
||||
},
|
||||
// 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>
|
||||
106
examples/whiteboard/public/main.js
Normal file
106
examples/whiteboard/public/main.js
Normal file
@@ -0,0 +1,106 @@
|
||||
'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);
|
||||
|
||||
//Touch support for mobile devices
|
||||
canvas.addEventListener('touchstart', onMouseDown, false);
|
||||
canvas.addEventListener('touchend', onMouseUp, false);
|
||||
canvas.addEventListener('touchcancel', onMouseUp, false);
|
||||
canvas.addEventListener('touchmove', 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||e.touches[0].clientX;
|
||||
current.y = e.clientY||e.touches[0].clientY;
|
||||
}
|
||||
|
||||
function onMouseUp(e){
|
||||
if (!drawing) { return; }
|
||||
drawing = false;
|
||||
drawLine(current.x, current.y, e.clientX||e.touches[0].clientX, e.clientY||e.touches[0].clientY, current.color, true);
|
||||
}
|
||||
|
||||
function onMouseMove(e){
|
||||
if (!drawing) { return; }
|
||||
drawLine(current.x, current.y, e.clientX||e.touches[0].clientX, e.clientY||e.touches[0].clientY, current.color, true);
|
||||
current.x = e.clientX||e.touches[0].clientX;
|
||||
current.y = e.clientY||e.touches[0].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; }
|
||||
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');
|
||||
272
lib/client.js
Normal file
272
lib/client.js
Normal file
@@ -0,0 +1,272 @@
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {Object} query the query parameters
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.connect = function(name, query){
|
||||
if (this.server.nsps[name]) {
|
||||
debug('connecting to namespace %s', name);
|
||||
return this.doConnect(name, query);
|
||||
}
|
||||
|
||||
this.server.checkNamespace(name, query, (dynamicNsp) => {
|
||||
if (dynamicNsp) {
|
||||
this.doConnect(name, query);
|
||||
} else {
|
||||
debug('creation of namespace %s was denied', name);
|
||||
this.packet({ type: parser.ERROR, nsp: name, data: 'Invalid namespace' });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Connects a client to a namespace.
|
||||
*
|
||||
* @param {String} name namespace
|
||||
* @param {String} query the query parameters
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.doConnect = function(name, query){
|
||||
var nsp = this.server.of(name);
|
||||
|
||||
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);
|
||||
};
|
||||
529
lib/index.js
Normal file
529
lib/index.js
Normal file
@@ -0,0 +1,529 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 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 clientVersion = require('socket.io-client/package.json').version;
|
||||
var Client = require('./client');
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var Namespace = require('./namespace');
|
||||
var ParentNamespace = require('./parent-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.parentNsps = new Map();
|
||||
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.protocol + '//' + parts.hostname + ':' + parts.port) ||
|
||||
~this._origins.indexOf(parts.hostname + ':' + parts.port) ||
|
||||
~this._origins.indexOf(parts.hostname + ':*') ||
|
||||
~this._origins.indexOf('*:' + parts.port);
|
||||
debug('origin %s is %svalid', origin, !!ok ? '' : 'not ');
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the middleware for an incoming namespace not already created on the server.
|
||||
*
|
||||
* @param {String} name name of incoming namespace
|
||||
* @param {Object} query the query parameters
|
||||
* @param {Function} fn callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.checkNamespace = function(name, query, fn){
|
||||
if (this.parentNsps.size === 0) return fn(false);
|
||||
|
||||
const keysIterator = this.parentNsps.keys();
|
||||
|
||||
const run = () => {
|
||||
let nextFn = keysIterator.next();
|
||||
if (nextFn.done) {
|
||||
return fn(false);
|
||||
}
|
||||
nextFn.value(name, query, (err, allow) => {
|
||||
if (err || !allow) {
|
||||
return run();
|
||||
}
|
||||
if (this.nsps[name]) {
|
||||
// the namespace was created in the meantime
|
||||
debug("dynamic namespace %s already exists", name);
|
||||
return fn(this.nsps[name]);
|
||||
}
|
||||
const namespace = this.parentNsps.get(nextFn.value).createChild(name);
|
||||
debug("dynamic namespace %s was created", name);
|
||||
fn(namespace);
|
||||
});
|
||||
};
|
||||
|
||||
run();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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|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);
|
||||
|
||||
if (this.sockets.fns.length > 0) {
|
||||
this.initEngine(srv, opts);
|
||||
return 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;
|
||||
|
||||
self.initEngine(srv, opts);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize engine
|
||||
*
|
||||
* @param {Object} options passed to engine.io
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.initEngine = function(srv, opts){
|
||||
// initialize engine
|
||||
debug('creating engine.io instance with opts %j', opts);
|
||||
this.eio = engine.attach(srv, opts);
|
||||
|
||||
// attach static file serving
|
||||
if (this._serveClient) this.attachServe(srv);
|
||||
|
||||
// Export http server
|
||||
this.httpServer = srv;
|
||||
|
||||
// bind to engine events
|
||||
this.bind(this.eio);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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("Cache-Control", "public, max-age=0");
|
||||
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|RegExp|Function} name nsp name
|
||||
* @param {Function} [fn] optional, nsp `connection` ev handler
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.of = function(name, fn){
|
||||
if (typeof name === 'function' || name instanceof RegExp) {
|
||||
const parentNsp = new ParentNamespace(this);
|
||||
debug('initializing parent namespace %s', parentNsp.name);
|
||||
if (typeof name === 'function') {
|
||||
this.parentNsps.set(name, parentNsp);
|
||||
} else {
|
||||
this.parentNsps.set((nsp, conn, next) => next(null, name.test(nsp)), parentNsp);
|
||||
}
|
||||
if (fn) parentNsp.on('connect', fn);
|
||||
return parentNsp;
|
||||
}
|
||||
|
||||
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', 'binary']).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,96 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var util = require('./util')
|
||||
, toArray = util.toArray;
|
||||
|
||||
/**
|
||||
* Log levels.
|
||||
*/
|
||||
|
||||
var levels = [
|
||||
'error'
|
||||
, 'warn'
|
||||
, 'info'
|
||||
, 'debug'
|
||||
];
|
||||
|
||||
/**
|
||||
* Colors for log levels.
|
||||
*/
|
||||
|
||||
var colors = [
|
||||
31
|
||||
, 33
|
||||
, 36
|
||||
, 90
|
||||
];
|
||||
|
||||
/**
|
||||
* Pads the nice output to the longest log level.
|
||||
*/
|
||||
|
||||
function pad (str) {
|
||||
var max = 0;
|
||||
|
||||
for (var i = 0, l = levels.length; i < l; i++)
|
||||
max = Math.max(max, levels[i].length);
|
||||
|
||||
if (str.length < max)
|
||||
return str + new Array(max - str.length + 1).join(' ');
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
/**
|
||||
* Logger (console).
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
var Logger = module.exports = function (opts) {
|
||||
opts = opts || {}
|
||||
this.colors = false !== opts.colors;
|
||||
this.level = 3;
|
||||
};
|
||||
|
||||
/**
|
||||
* Log method.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Logger.prototype.log = function (type) {
|
||||
var index = levels.indexOf(type);
|
||||
|
||||
if (index > this.level)
|
||||
return this;
|
||||
|
||||
console.log.apply(
|
||||
console
|
||||
, [this.colors
|
||||
? ' \033[' + colors[index] + 'm' + pad(type) + ' -\033[39m'
|
||||
: type + ':'
|
||||
].concat(toArray(arguments).slice(1))
|
||||
);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate methods.
|
||||
*/
|
||||
|
||||
levels.forEach(function (name) {
|
||||
Logger.prototype[name] = function () {
|
||||
this.log.apply(this, [name].concat(toArray(arguments)));
|
||||
};
|
||||
});
|
||||
957
lib/manager.js
957
lib/manager.js
@@ -1,957 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http')
|
||||
, https = require('https')
|
||||
, fs = require('fs')
|
||||
, url = require('url')
|
||||
, 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')
|
||||
, 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) {
|
||||
this.server = server;
|
||||
this.namespaces = {};
|
||||
this.sockets = this.of('');
|
||||
this.settings = {
|
||||
origins: '*:*'
|
||||
, log: true
|
||||
, store: new MemoryStore
|
||||
, logger: new Logger
|
||||
, heartbeats: true
|
||||
, resource: '/socket.io'
|
||||
, transports: defaultTransports
|
||||
, authorization: false
|
||||
, 'log level': 3
|
||||
, 'close timeout': 25
|
||||
, 'heartbeat timeout': 15
|
||||
, 'heartbeat interval': 20
|
||||
, 'polling duration': 20
|
||||
, 'flash policy server': true
|
||||
, 'flash policy port': 843
|
||||
, 'destroy upgrade': true
|
||||
, 'browser client': true
|
||||
, 'browser client minification': false
|
||||
, 'browser client etag': false
|
||||
, 'browser client handler': false
|
||||
, 'client store expiration': 15
|
||||
};
|
||||
|
||||
this.initStore();
|
||||
|
||||
// reset listeners
|
||||
this.oldListeners = server.listeners('request');
|
||||
server.removeAllListeners('request');
|
||||
|
||||
var self = this;
|
||||
|
||||
server.on('request', function (req, res) {
|
||||
self.handleRequest(req, res);
|
||||
});
|
||||
|
||||
server.on('upgrade', function (req, socket, head) {
|
||||
self.handleUpgrade(req, socket, head);
|
||||
});
|
||||
|
||||
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 () {
|
||||
if (this.disabled('log')) return;
|
||||
|
||||
var logger = this.get('logger');
|
||||
logger.level = this.get('log level');
|
||||
|
||||
return logger;
|
||||
});
|
||||
|
||||
/**
|
||||
* 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.closedA = [];
|
||||
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.closedA.splice(this.closedA.indexOf(id), 1);
|
||||
|
||||
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) {
|
||||
// go through the users who have pending buffers
|
||||
for (var i = 0, l = this.closedA.length; i < l; i++) {
|
||||
if (this.roomClients[this.closedA[i]][room]) {
|
||||
if (!~exceptions.indexOf(this.closedA[i])) {
|
||||
this.closed[this.closedA[i]].push(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// go through room clients
|
||||
if (this.rooms[room]) {
|
||||
for (var i = 0, l = this.rooms[room].length; i < l; i++) {
|
||||
var id = this.rooms[room][i];
|
||||
|
||||
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] = [];
|
||||
}
|
||||
|
||||
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]) {
|
||||
this.rooms[room].splice(this.rooms[room].indexOf(id), 1);
|
||||
delete this.roomClients[id][room];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a client closes a request in different node.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.onClose = function (id) {
|
||||
this.closed[id] = [];
|
||||
this.closedA.push(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) {
|
||||
this.closed[id].push(packet);
|
||||
};
|
||||
|
||||
/**
|
||||
* Receives a message for a client.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.onClientMessage = function (id, packet) {
|
||||
if (this.namespaces[packet.endpoint]) {
|
||||
this.namespaces[packet.endpoint].handlePacket(id, packet);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fired when a client disconnects (not triggered).
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.onClientDisconnect = function (id, reason) {
|
||||
for (var name in this.namespaces) {
|
||||
if (this.roomClients[id][name]) {
|
||||
this.namespaces[name].handleDisconnect(id, reason);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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];
|
||||
this.closedA.splice(this.closedA.indexOf(id), 1);
|
||||
}
|
||||
|
||||
if (this.roomClients[id]) {
|
||||
for (var room in this.roomClients[id]) {
|
||||
this.rooms[room].splice(this.rooms.indexOf(id), 1);
|
||||
}
|
||||
}
|
||||
|
||||
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.handleClientRequest(req, res, data);
|
||||
} 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);
|
||||
|
||||
if (this.handshaken[data.id]) {
|
||||
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);
|
||||
|
||||
// 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();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dictionary for static file serving
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Manager.static = {
|
||||
cache: {}
|
||||
, paths: {
|
||||
'/static/flashsocket/WebSocketMain.swf': client.dist + '/WebSocketMain.swf'
|
||||
, '/static/flashsocket/WebSocketMainInsecure.swf':
|
||||
client.dist + '/WebSocketMainInsecure.swf'
|
||||
, '/socket.io.js': client.dist + '/socket.io.js'
|
||||
, '/socket.io.js.min': client.dist + '/socket.io.min.js'
|
||||
}
|
||||
, mime: {
|
||||
'js': {
|
||||
contentType: 'application/javascript'
|
||||
, encoding: 'utf8'
|
||||
}
|
||||
, 'swf': {
|
||||
contentType: 'application/x-shockwave-flash'
|
||||
, encoding: 'binary'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Serves the client.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.handleClientRequest = function (req, res, data) {
|
||||
var static = Manager.static
|
||||
, extension = data.path.split('.').pop()
|
||||
, file = data.path + (this.enabled('browser client minification')
|
||||
&& extension == 'js' ? '.min' : '')
|
||||
, location = static.paths[file]
|
||||
, cache = static.cache[file];
|
||||
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* Writes a response, safely
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function write (status, headers, content, encoding) {
|
||||
try {
|
||||
res.writeHead(status, headers || null);
|
||||
res.end(content || '', encoding || null);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function serve () {
|
||||
if (req.headers['if-none-match'] === cache.Etag) {
|
||||
return write(304);
|
||||
}
|
||||
|
||||
var mime = static.mime[extension]
|
||||
, headers = {
|
||||
'Content-Type': mime.contentType
|
||||
, 'Content-Length': cache.length
|
||||
};
|
||||
|
||||
if (self.enabled('browser client etag') && cache.Etag) {
|
||||
headers.Etag = cache.Etag;
|
||||
}
|
||||
|
||||
write(200, headers, cache.content, mime.encoding);
|
||||
self.log.debug('served static ' + data.path);
|
||||
}
|
||||
|
||||
if (this.get('browser client handler')) {
|
||||
this.get('browser client handler').call(this, req, res);
|
||||
} else if (!cache) {
|
||||
fs.readFile(location, function (err, data) {
|
||||
if (err) {
|
||||
write(500, null, 'Error serving static ' + data.path);
|
||||
self.log.warn('Can\'t cache '+ data.path +', ' + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
cache = Manager.static.cache[file] = {
|
||||
content: data
|
||||
, length: data.length
|
||||
, Etag: client.version
|
||||
};
|
||||
|
||||
serve();
|
||||
});
|
||||
} else {
|
||||
serve();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
this.authorize(handshakeData, function (err, authorized, newData) {
|
||||
if (err) return error(err);
|
||||
|
||||
if (authorized) {
|
||||
var id = self.generateId()
|
||||
, hs = [
|
||||
id
|
||||
, 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);
|
||||
}
|
||||
|
||||
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 connectionAddress = null;
|
||||
if (data.request.connection.address) {
|
||||
connectionAddress = data.request.connection.address();
|
||||
}
|
||||
return {
|
||||
headers: data.headers
|
||||
, address: connectionAddress
|
||||
, time: (new Date).toString()
|
||||
, xdomain: !!data.request.headers.origin
|
||||
, secure: data.request.connection.secure
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies the origin of a request.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Manager.prototype.verifyOrigin = function (request) {
|
||||
var origin = request.headers.origin
|
||||
, origins = this.get('origins');
|
||||
|
||||
if (origin === 'null') origin = '*';
|
||||
|
||||
if (origins.indexOf('*:*') !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (origin) {
|
||||
try {
|
||||
var parts = url.parse(origin);
|
||||
|
||||
return
|
||||
~origins.indexOf(parts.host + ':' + parts.port) ||
|
||||
~origins.indexOf(parts.host + ':*') ||
|
||||
~origins.indexOf('*:' + parts.port);
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
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 = !!Manager.static.paths[path];
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Declares a socket namespace
|
||||
*/
|
||||
|
||||
Manager.prototype.of = function (nsp) {
|
||||
if (this.namespaces[nsp]) {
|
||||
return this.namespaces[nsp];
|
||||
}
|
||||
|
||||
return this.namespaces[nsp] = new SocketNamespace(this, nsp);
|
||||
};
|
||||
535
lib/namespace.js
535
lib/namespace.js
@@ -1,348 +1,305 @@
|
||||
|
||||
/**
|
||||
* 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 hasBin = require('has-binary2');
|
||||
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 ?
|
||||
(this.name !== '' ? '/' : '') + 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 = function (room) {
|
||||
this.flags.endpoint = (this.name === '' ? '' : (this.name + '/')) + 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;
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Emits to everyone (override)
|
||||
* Initializes the `Adapter` for this nsp.
|
||||
* Run upon changing adapter by `Server#adapter`
|
||||
* in addition to the constructor.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
SocketNamespace.prototype.emit = function (name) {
|
||||
if (name == 'connection' || name == 'newListener') {
|
||||
return this.$emit.apply(this, arguments);
|
||||
}
|
||||
|
||||
return this.packet({
|
||||
type: 'event'
|
||||
, name: name
|
||||
, args: util.toArray(arguments).slice(1)
|
||||
});
|
||||
Namespace.prototype.initAdapter = function(){
|
||||
this.adapter = new (this.server.adapter())(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves or creates a write-only socket for a client, unless specified.
|
||||
*
|
||||
* @param {Boolean} whether the socket will be readable when initialized
|
||||
* @api private
|
||||
*/
|
||||
|
||||
SocketNamespace.prototype.socket = function (sid, readable) {
|
||||
if (!this.sockets[sid]) {
|
||||
this.sockets[sid] = new Socket(this.manager, sid, this, readable);
|
||||
}
|
||||
|
||||
return this.sockets[sid];
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets authorization for this namespace
|
||||
* Sets up namespace middleware.
|
||||
*
|
||||
* @return {Namespace} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
SocketNamespace.prototype.authorization = function (fn) {
|
||||
this.auth = fn;
|
||||
Namespace.prototype.use = function(fn){
|
||||
if (this.server.eio && this.name === '/') {
|
||||
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) {
|
||||
if (this.sockets[sid] && this.sockets[sid].readable) {
|
||||
this.sockets[sid].onDisconnect(reason);
|
||||
}
|
||||
};
|
||||
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) {
|
||||
debug("next called after client was closed - ignoring socket");
|
||||
socket._cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
debug("middleware error, sending CONNECT_ERROR packet to the client");
|
||||
socket._cleanup();
|
||||
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);
|
||||
});
|
||||
});
|
||||
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);
|
||||
return this;
|
||||
}
|
||||
// set up packet object
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var packet = {
|
||||
type: (this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT : parser.EVENT,
|
||||
data: args
|
||||
};
|
||||
|
||||
if ('function' == typeof args[args.length - 1]) {
|
||||
throw new Error('Callbacks are not supported when broadcasting');
|
||||
}
|
||||
|
||||
var rooms = this.rooms.slice(0);
|
||||
var flags = Object.assign({}, this.flags);
|
||||
|
||||
// reset flags
|
||||
this.rooms = [];
|
||||
this.flags = {};
|
||||
|
||||
this.adapter.broadcast(packet, {
|
||||
rooms: rooms,
|
||||
flags: 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'
|
||||
, self = this;
|
||||
|
||||
function ack () {
|
||||
self.log.debug('sending data ack packet');
|
||||
socket.packet({
|
||||
type: 'ack'
|
||||
, args: util.toArray(arguments)
|
||||
, ackId: packet.id
|
||||
});
|
||||
};
|
||||
|
||||
function error (err) {
|
||||
self.log.warn('handshake error ' + err + ' for ' + self.name);
|
||||
socket.packet({ type: 'error', reason: err });
|
||||
};
|
||||
|
||||
function connect () {
|
||||
self.manager.onJoin(sessid, self.name);
|
||||
self.store.publish('join', sessid, self.name);
|
||||
|
||||
// packet echo
|
||||
socket.packet({ type: 'connect' });
|
||||
|
||||
// emit connection event
|
||||
self.emit('connection', socket);
|
||||
};
|
||||
|
||||
switch (packet.type) {
|
||||
case 'connect':
|
||||
if (packet.endpoint == '') {
|
||||
connect();
|
||||
} else {
|
||||
var manager = this.manager
|
||||
, handshakeData = manager.handshaken[sessid];
|
||||
|
||||
this.authorize(handshakeData, function (err, authorized, newData) {
|
||||
if (err) return error(err);
|
||||
|
||||
if (authorized) {
|
||||
manager.onHandshake(sessid, newData || handshakeData);
|
||||
self.store.publish('handshake', sessid, newData || handshakeData);
|
||||
connect();
|
||||
} else {
|
||||
error('unauthorized');
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ack':
|
||||
if (socket.acks[packet.ackId]) {
|
||||
socket.acks[packet.ackId].apply(socket, packet.args);
|
||||
} else {
|
||||
this.log.info('unknown ack packet');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'event':
|
||||
var params = [packet.name].concat(packet.args);
|
||||
|
||||
if (dataAck)
|
||||
params.push(ack);
|
||||
|
||||
socket.$emit.apply(socket, params);
|
||||
break;
|
||||
|
||||
case 'disconnect':
|
||||
this.manager.onLeave(sessid, this.name);
|
||||
this.store.publish('leave', sessid, this.name);
|
||||
|
||||
socket.emit('disconnect', packet.reason || 'packet');
|
||||
break;
|
||||
|
||||
case 'json':
|
||||
case 'message':
|
||||
var params = ['message', packet.data];
|
||||
|
||||
if (dataAck)
|
||||
params.push(ack);
|
||||
|
||||
socket.emit.apply(socket, params);
|
||||
};
|
||||
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){
|
||||
if(!this.adapter){
|
||||
throw new Error('No adapter for this namespace, are you trying to get the list of clients of a dynamic namespace?')
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the binary flag
|
||||
*
|
||||
* @param {Boolean} Encode as if it has binary data if `true`, Encode as if it doesnt have binary data if `false`
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Namespace.prototype.binary = function (binary) {
|
||||
this.flags.binary = binary;
|
||||
return this;
|
||||
};
|
||||
|
||||
39
lib/parent-namespace.js
Normal file
39
lib/parent-namespace.js
Normal file
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
const Namespace = require('./namespace');
|
||||
|
||||
let count = 0;
|
||||
|
||||
class ParentNamespace extends Namespace {
|
||||
|
||||
constructor(server) {
|
||||
super(server, '/_' + (count++));
|
||||
this.children = new Set();
|
||||
}
|
||||
|
||||
initAdapter() {}
|
||||
|
||||
emit() {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
|
||||
this.children.forEach(nsp => {
|
||||
nsp.rooms = this.rooms;
|
||||
nsp.flags = this.flags;
|
||||
nsp.emit.apply(nsp, args);
|
||||
});
|
||||
this.rooms = [];
|
||||
this.flags = {};
|
||||
}
|
||||
|
||||
createChild(name) {
|
||||
const namespace = new Namespace(this.server, name);
|
||||
namespace.fns = this.fns.slice(0);
|
||||
this.listeners('connect').forEach(listener => namespace.on('connect', listener));
|
||||
this.listeners('connection').forEach(listener => namespace.on('connection', listener));
|
||||
this.children.add(namespace);
|
||||
this.server.nsps[name] = namespace;
|
||||
return namespace;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ParentNamespace;
|
||||
243
lib/parser.js
243
lib/parser.js
@@ -1,243 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Packet types.
|
||||
*/
|
||||
|
||||
var packets = exports.packets = [
|
||||
'disconnect'
|
||||
, 'connect'
|
||||
, 'heartbeat'
|
||||
, 'message'
|
||||
, 'json'
|
||||
, 'event'
|
||||
, 'ack'
|
||||
, 'error'
|
||||
, 'noop'
|
||||
];
|
||||
|
||||
/**
|
||||
* Errors reasons.
|
||||
*/
|
||||
|
||||
var reasons = exports.reasons = [
|
||||
'transport not supported'
|
||||
, 'client not handshaken'
|
||||
, 'unauthorized'
|
||||
];
|
||||
|
||||
/**
|
||||
* Errors advice.
|
||||
*/
|
||||
|
||||
var advice = exports.advice = [
|
||||
'reconnect'
|
||||
];
|
||||
|
||||
/**
|
||||
* Encodes a packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.encodePacket = function (packet) {
|
||||
var type = packets.indexOf(packet.type)
|
||||
, id = packet.id || ''
|
||||
, endpoint = packet.endpoint || ''
|
||||
, ack = packet.ack
|
||||
, data = null;
|
||||
|
||||
switch (packet.type) {
|
||||
case 'error':
|
||||
var reason = packet.reason ? reasons.indexOf(packet.reason) : ''
|
||||
, adv = packet.advice ? advice.indexOf(packet.advice) : ''
|
||||
|
||||
if (reason !== '' || adv !== '')
|
||||
data = reason + (adv !== '' ? ('+' + adv) : '')
|
||||
|
||||
break;
|
||||
|
||||
case 'message':
|
||||
if (packet.data !== '')
|
||||
data = packet.data;
|
||||
break;
|
||||
|
||||
case 'event':
|
||||
var ev = { name: packet.name };
|
||||
|
||||
if (packet.args && packet.args.length) {
|
||||
ev.args = packet.args;
|
||||
}
|
||||
|
||||
data = JSON.stringify(ev);
|
||||
break;
|
||||
|
||||
case 'json':
|
||||
data = JSON.stringify(packet.data);
|
||||
break;
|
||||
|
||||
case 'connect':
|
||||
if (packet.qs)
|
||||
data = packet.qs;
|
||||
break;
|
||||
|
||||
case 'ack':
|
||||
data = packet.ackId
|
||||
+ (packet.args && packet.args.length
|
||||
? '+' + JSON.stringify(packet.args) : '');
|
||||
break;
|
||||
}
|
||||
|
||||
// construct packet with required fragments
|
||||
var encoded = [
|
||||
type
|
||||
, id + (ack == 'data' ? '+' : '')
|
||||
, endpoint
|
||||
];
|
||||
|
||||
// data fragment is optional
|
||||
if (data !== null && data !== undefined)
|
||||
encoded.push(data);
|
||||
|
||||
return encoded.join(':');
|
||||
};
|
||||
|
||||
/**
|
||||
* Encodes multiple messages (payload).
|
||||
*
|
||||
* @param {Array} messages
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.encodePayload = function (packets) {
|
||||
var decoded = '';
|
||||
|
||||
if (packets.length == 1)
|
||||
return packets[0];
|
||||
|
||||
for (var i = 0, l = packets.length; i < l; i++) {
|
||||
var packet = packets[i];
|
||||
decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]
|
||||
}
|
||||
|
||||
return decoded;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes a packet
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var regexp = /^([^:]+):([0-9]+)?(\+)?:([^:]+)?:?(.*)?$/;
|
||||
|
||||
exports.decodePacket = function (data) {
|
||||
var pieces = data.match(regexp);
|
||||
|
||||
if (!pieces) return {};
|
||||
|
||||
var id = pieces[2] || ''
|
||||
, data = pieces[5] || ''
|
||||
, packet = {
|
||||
type: packets[pieces[1]]
|
||||
, endpoint: pieces[4] || ''
|
||||
};
|
||||
|
||||
// whether we need to acknowledge the packet
|
||||
if (id) {
|
||||
packet.id = id;
|
||||
if (pieces[3])
|
||||
packet.ack = 'data';
|
||||
else
|
||||
packet.ack = true;
|
||||
}
|
||||
|
||||
// handle different packet types
|
||||
switch (packet.type) {
|
||||
case 'error':
|
||||
var pieces = data.split('+');
|
||||
packet.reason = reasons[pieces[0]] || '';
|
||||
packet.advice = advice[pieces[1]] || '';
|
||||
break;
|
||||
|
||||
case 'message':
|
||||
packet.data = data || '';
|
||||
break;
|
||||
|
||||
case 'event':
|
||||
try {
|
||||
var opts = JSON.parse(data);
|
||||
packet.name = opts.name;
|
||||
packet.args = opts.args;
|
||||
} catch (e) { }
|
||||
|
||||
packet.args = packet.args || [];
|
||||
break;
|
||||
|
||||
case 'json':
|
||||
try {
|
||||
packet.data = JSON.parse(data);
|
||||
} catch (e) { }
|
||||
break;
|
||||
|
||||
case 'connect':
|
||||
packet.qs = data || '';
|
||||
break;
|
||||
|
||||
case 'ack':
|
||||
var pieces = data.match(/^([0-9]+)(\+)?(.*)/);
|
||||
if (pieces) {
|
||||
packet.ackId = pieces[1];
|
||||
packet.args = [];
|
||||
|
||||
if (pieces[3]) {
|
||||
try {
|
||||
packet.args = pieces[3] ? JSON.parse(pieces[3]) : [];
|
||||
} catch (e) { }
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'disconnect':
|
||||
case 'heartbeat':
|
||||
break;
|
||||
};
|
||||
|
||||
return packet;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes data payload. Detects multiple messages
|
||||
*
|
||||
* @return {Array} messages
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.decodePayload = function (data) {
|
||||
if (data[0] == '\ufffd') {
|
||||
var ret = [];
|
||||
|
||||
for (var i = 1, length = ''; i < data.length; i++) {
|
||||
if (data[i] == '\ufffd') {
|
||||
ret.push(exports.decodePacket(data.substr(i + 1).substr(0, length)));
|
||||
i += Number(length) + 1;
|
||||
length = '';
|
||||
} else {
|
||||
length += data[i];
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
} else {
|
||||
return [exports.decodePacket(data)];
|
||||
}
|
||||
};
|
||||
125
lib/socket.io.js
125
lib/socket.io.js
@@ -1,125 +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.7.3';
|
||||
|
||||
/**
|
||||
* Supported protocol version.
|
||||
*/
|
||||
|
||||
exports.protocol = 1;
|
||||
|
||||
/**
|
||||
* Client that we serve.
|
||||
*/
|
||||
|
||||
exports.clientVersion = client.version;
|
||||
|
||||
/**
|
||||
* Attaches a manager
|
||||
*
|
||||
* @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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Manager constructor.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.Manager = require('./manager');
|
||||
|
||||
/**
|
||||
* Transport constructor.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.Transport = require('./transport');
|
||||
|
||||
/**
|
||||
* Socket constructor.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.Socket = require('./socket');
|
||||
|
||||
/**
|
||||
* 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');
|
||||
837
lib/socket.js
837
lib/socket.js
@@ -1,366 +1,583 @@
|
||||
|
||||
/*!
|
||||
* 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 hasBin = require('has-binary2');
|
||||
var url = require('url');
|
||||
var debug = require('debug')('socket.io:socket');
|
||||
|
||||
/**
|
||||
* Export the constructor.
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
exports = module.exports = Socket;
|
||||
module.exports = exports = Socket;
|
||||
|
||||
/**
|
||||
* Reserved event names.
|
||||
*/
|
||||
|
||||
var events = {
|
||||
message: 1
|
||||
, connect: 1
|
||||
, disconnect: 1
|
||||
, open: 1
|
||||
, close: 1
|
||||
, error: 1
|
||||
, retry: 1
|
||||
, reconnect: 1
|
||||
, newListener: 1
|
||||
};
|
||||
|
||||
/**
|
||||
* 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',
|
||||
'local'
|
||||
];
|
||||
|
||||
/**
|
||||
* `EventEmitter#emit` reference.
|
||||
*/
|
||||
|
||||
var emit = Emitter.prototype.emit;
|
||||
|
||||
function noop() {}
|
||||
|
||||
/**
|
||||
* 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.connected = false;
|
||||
this.disconnected = true;
|
||||
this.handshake = this.buildHandshake(query);
|
||||
this.fns = [];
|
||||
this.flags = {};
|
||||
this._rooms = [];
|
||||
|
||||
// prevents crash when the socket receives an "error" event without listener
|
||||
this.on('error', noop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherits from EventEmitter.
|
||||
* Inherits from `EventEmitter`.
|
||||
*/
|
||||
|
||||
Socket.prototype.__proto__ = EventEmitter.prototype;
|
||||
Socket.prototype.__proto__ = Emitter.prototype;
|
||||
|
||||
/**
|
||||
* Accessor shortcut for the handshake data
|
||||
* Apply flags from `Socket`.
|
||||
*/
|
||||
|
||||
flags.forEach(function(flag){
|
||||
Object.defineProperty(Socket.prototype, flag, {
|
||||
get: function() {
|
||||
this.flags[flag] = true;
|
||||
return this;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* `request` engine.io shortcut.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Object.defineProperty(Socket.prototype, 'request', {
|
||||
get: function() {
|
||||
return this.conn.request;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Builds the `handshake` BC object
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.__defineGetter__('handshake', function () {
|
||||
return this.manager.handshaken[this.id];
|
||||
});
|
||||
|
||||
/**
|
||||
* Accessor shortcut for the logger.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.__defineGetter__('log', function () {
|
||||
return this.manager.log;
|
||||
});
|
||||
|
||||
/**
|
||||
* JSON message flag.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.__defineGetter__('json', function () {
|
||||
this.flags.json = true;
|
||||
return this;
|
||||
});
|
||||
|
||||
/**
|
||||
* Volatile message flag.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.__defineGetter__('volatile', function () {
|
||||
this.flags.volatile = true;
|
||||
return this;
|
||||
});
|
||||
|
||||
/**
|
||||
* Broadcast message flag.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.__defineGetter__('broadcast', function () {
|
||||
this.flags.broadcast = true;
|
||||
return this;
|
||||
});
|
||||
|
||||
/**
|
||||
* Overrides the room to broadcast messages to (flag)
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.to = function (room) {
|
||||
this.flags.room = room;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resets flags
|
||||
*
|
||||
* @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 Object.assign({}, requestQuery, query);
|
||||
}
|
||||
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 === '' ? '' : (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();
|
||||
Socket.prototype.emit = function(ev){
|
||||
if (~exports.events.indexOf(ev)) {
|
||||
emit.apply(this, arguments);
|
||||
return this;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Joins a user to a room.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.leave = function (name, fn) {
|
||||
var nsp = this.namespace.name
|
||||
, name = (nsp === '' ? '' : (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);
|
||||
} else {
|
||||
packet.endpoint = this.flags.endpoint;
|
||||
packet = parser.encodePacket(packet);
|
||||
|
||||
this.dispatch(packet, this.flags.volatile);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
} else {
|
||||
if (this.manager.open[this.id]) {
|
||||
// if the connection is open in a different node
|
||||
this.manager.store.publish('disconnect-force:' + this.id);
|
||||
} else {
|
||||
this.manager.onClientDisconnect(this.id);
|
||||
this.manager.store.publish('disconnect:' + this.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a message.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.send = function (data, fn) {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var packet = {
|
||||
type: this.flags.json ? 'json' : 'message'
|
||||
, data: data
|
||||
type: (this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT : parser.EVENT,
|
||||
data: args
|
||||
};
|
||||
|
||||
if (fn) {
|
||||
packet.id = ++this.ackPackets;
|
||||
packet.ack = true;
|
||||
this.acks[packet.id] = fn;
|
||||
// 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');
|
||||
}
|
||||
|
||||
debug('emitting packet with ack id %d', this.nsp.ids);
|
||||
this.acks[this.nsp.ids] = args.pop();
|
||||
packet.id = this.nsp.ids++;
|
||||
}
|
||||
|
||||
return this.packet(packet);
|
||||
var rooms = this._rooms.slice(0);
|
||||
var flags = Object.assign({}, this.flags);
|
||||
|
||||
// reset flags
|
||||
this._rooms = [];
|
||||
this.flags = {};
|
||||
|
||||
if (rooms.length || flags.broadcast) {
|
||||
this.adapter.broadcast(packet, {
|
||||
except: [this.id],
|
||||
rooms: rooms,
|
||||
flags: flags
|
||||
});
|
||||
} else {
|
||||
// dispatch packet
|
||||
this.packet(packet, flags);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Original emit function.
|
||||
* Targets a room when broadcasting.
|
||||
*
|
||||
* @param {String} name
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.to =
|
||||
Socket.prototype.in = function(name){
|
||||
if (!~this._rooms.indexOf(name)) this._rooms.push(name);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.connected = true;
|
||||
this.disconnected = false;
|
||||
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.onerror(new 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: hasBin(args) ? parser.BINARY_ACK : 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){
|
||||
this.emit('error', err);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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._cleanup();
|
||||
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 (events[ev]) {
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the binary flag
|
||||
*
|
||||
* @param {Boolean} Encode as if it has binary data if `true`, Encode as if it doesnt have binary data if `false`
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.binary = function (binary) {
|
||||
this.flags.binary = binary;
|
||||
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);
|
||||
}
|
||||
if (self.connected) {
|
||||
emit.apply(self, event);
|
||||
} else {
|
||||
debug("ignore packet received after disconnection");
|
||||
}
|
||||
});
|
||||
}
|
||||
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);
|
||||
};
|
||||
|
||||
Socket.prototype._cleanup = function () {
|
||||
this.leaveAll();
|
||||
this.join = function noop() {};
|
||||
}
|
||||
|
||||
98
lib/store.js
98
lib/store.js
@@ -1,98 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Expose the constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = Store;
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var EventEmitter = process.EventEmitter;
|
||||
|
||||
/**
|
||||
* Store interface
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Store (options) {
|
||||
this.options = options;
|
||||
this.clients = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherit from EventEmitter.
|
||||
*/
|
||||
|
||||
Store.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Initializes a client store
|
||||
*
|
||||
* @param {String} id
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Store.prototype.client = function (id) {
|
||||
if (!this.clients[id]) {
|
||||
this.clients[id] = new (this.constructor.Client)(this, id);
|
||||
}
|
||||
|
||||
return this.clients[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys a client
|
||||
*
|
||||
* @api {String} sid
|
||||
* @param {Number} number of seconds to expire client data
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Store.prototype.destroyClient = function (id, expiration) {
|
||||
if (this.clients[id]) {
|
||||
this.clients[id].destroy(expiration);
|
||||
delete this.clients[id];
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys the store
|
||||
*
|
||||
* @param {Number} number of seconds to expire client data
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Store.prototype.destroy = function (clientExpiration) {
|
||||
var keys = Object.keys(this.clients)
|
||||
, count = keys.length;
|
||||
|
||||
for (var i = 0, l = count; i < l; i++) {
|
||||
this.destroyClient(keys[i], clientExpiration);
|
||||
}
|
||||
|
||||
this.clients = {};
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Client.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Store.Client = function (store, id) {
|
||||
this.store = store;
|
||||
this.id = id;
|
||||
};
|
||||
@@ -1,143 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var crypto = require('crypto')
|
||||
, Store = require('../store');
|
||||
|
||||
/**
|
||||
* Exports the constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = Memory;
|
||||
Memory.Client = Client;
|
||||
|
||||
/**
|
||||
* Memory store
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Memory (opts) {
|
||||
Store.call(this, opts);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Store.
|
||||
*/
|
||||
|
||||
Memory.prototype.__proto__ = Store.prototype;
|
||||
|
||||
/**
|
||||
* Publishes a message.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Memory.prototype.publish = function () { };
|
||||
|
||||
/**
|
||||
* Subscribes to a channel
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Memory.prototype.subscribe = function () { };
|
||||
|
||||
/**
|
||||
* Unsubscribes
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Memory.prototype.unsubscribe = function () { };
|
||||
|
||||
/**
|
||||
* Client constructor
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Client () {
|
||||
Store.Client.apply(this, arguments);
|
||||
this.data = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Store.Client
|
||||
*/
|
||||
|
||||
Client.prototype.__proto__ = Store.Client;
|
||||
|
||||
/**
|
||||
* Gets a key
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Client.prototype.get = function (key, fn) {
|
||||
fn(null, this.data[key] === undefined ? null : this.data[key]);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets a key
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Client.prototype.set = function (key, value, fn) {
|
||||
this.data[key] = value;
|
||||
fn && fn(null);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Has a key
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Client.prototype.has = function (key, fn) {
|
||||
fn(null, key in this.data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes a key
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Client.prototype.del = function (key, fn) {
|
||||
delete this.data[key];
|
||||
fn && fn(null);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys the client.
|
||||
*
|
||||
* @param {Number} number of seconds to expire data
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.destroy = function (expiration) {
|
||||
if ('number' != typeof expiration) {
|
||||
this.data = {};
|
||||
} else {
|
||||
var self = this;
|
||||
|
||||
setTimeout(function () {
|
||||
self.data = {};
|
||||
}, expiration * 1000);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -1,248 +0,0 @@
|
||||
|
||||
/*!
|
||||
* socket.io-node
|
||||
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var crypto = require('crypto')
|
||||
, Store = require('../store')
|
||||
, assert = require('assert')
|
||||
, redis = require('redis');
|
||||
|
||||
/**
|
||||
* Exports the constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = Redis;
|
||||
Redis.Client = Client;
|
||||
|
||||
/**
|
||||
* Redis store.
|
||||
* Options:
|
||||
* - nodeId (fn) gets an id that uniquely identifies this node
|
||||
* - redisPub (object) options to pass to the pub redis client
|
||||
* - redisSub (object) options to pass to the sub redis client
|
||||
* - redisClient (object) options to pass to the general redis client
|
||||
* - pack (fn) custom packing, defaults to JSON or msgpack if installed
|
||||
* - unpack (fn) custom packing, defaults to JSON or msgpack if installed
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Redis (opts) {
|
||||
opts = opts || {};
|
||||
|
||||
// node id to uniquely identify this node
|
||||
var nodeId = opts.nodeId || function () {
|
||||
// by default, we generate a random id
|
||||
return Math.abs(Math.random() * Math.random() * Date.now() | 0);
|
||||
};
|
||||
|
||||
this.nodeId = nodeId();
|
||||
|
||||
// packing / unpacking mechanism
|
||||
if (opts.pack) {
|
||||
this.pack = opts.pack;
|
||||
this.unpack = opts.unpack;
|
||||
} else {
|
||||
try {
|
||||
var msgpack = require('msgpack');
|
||||
this.pack = msgpack.pack;
|
||||
this.unpack = msgpack.unpack;
|
||||
} catch (e) {
|
||||
this.pack = JSON.stringify;
|
||||
this.unpack = JSON.parse;
|
||||
}
|
||||
}
|
||||
|
||||
// initialize a pubsub client and a regular client
|
||||
this.pub = redis.createClient(opts.redisPub);
|
||||
this.sub = redis.createClient(opts.redisSub);
|
||||
this.cmd = redis.createClient(opts.redisClient);
|
||||
|
||||
Store.call(this, opts);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Store.
|
||||
*/
|
||||
|
||||
Redis.prototype.__proto__ = Store.prototype;
|
||||
|
||||
/**
|
||||
* Publishes a message.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Redis.prototype.publish = function (name) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
this.pub.publish(name, this.pack({ nodeId: this.nodeId, args: args }));
|
||||
this.emit.apply(this, ['publish', name].concat(args));
|
||||
};
|
||||
|
||||
/**
|
||||
* Subscribes to a channel
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Redis.prototype.subscribe = function (name, consumer, fn) {
|
||||
this.sub.subscribe(name);
|
||||
|
||||
if (consumer || fn) {
|
||||
var self = this;
|
||||
|
||||
self.sub.on('subscribe', function subscribe (ch) {
|
||||
if (name == ch) {
|
||||
function message (ch, msg) {
|
||||
if (name == ch) {
|
||||
msg = self.unpack(msg);
|
||||
|
||||
// we check that the message consumed wasnt emitted by this node
|
||||
if (self.nodeId != msg.nodeId) {
|
||||
consumer.apply(null, msg.args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.sub.on('message', message);
|
||||
|
||||
self.on('unsubscribe', function unsubscribe (ch) {
|
||||
if (name == ch) {
|
||||
self.sub.removeListener('message', message);
|
||||
self.removeEvent('unsubscribe', unsubscribe);
|
||||
}
|
||||
});
|
||||
|
||||
self.sub.removeListener('subscribe', subscribe);
|
||||
|
||||
fn && fn();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.emit('subscribe', name, consumer, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unsubscribes
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Redis.prototype.unsubscribe = function (name, fn) {
|
||||
this.sub.unsubscribe(name);
|
||||
|
||||
if (fn) {
|
||||
var client = this.sub;
|
||||
|
||||
client.on('unsubscribe', function unsubscribe (ch) {
|
||||
if (name == ch) {
|
||||
fn();
|
||||
client.removeListener('unsubscribe', unsubscribe);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.emit('unsubscribe', name, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys the store
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Redis.prototype.destroy = function () {
|
||||
Store.prototype.destroy.call(this);
|
||||
|
||||
this.pub.end();
|
||||
this.sub.end();
|
||||
this.cmd.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* Client constructor
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Client (store, id) {
|
||||
Store.Client.call(this, store, id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Store.Client
|
||||
*/
|
||||
|
||||
Client.prototype.__proto__ = Store.Client;
|
||||
|
||||
/**
|
||||
* Redis hash get
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.get = function (key, fn) {
|
||||
this.store.cmd.hget(this.id, key, fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Redis hash set
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.set = function (key, value, fn) {
|
||||
this.store.cmd.hset(this.id, key, value, fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Redis hash del
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.del = function (key, fn) {
|
||||
this.store.cmd.hdel(this.id, key, fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Redis hash has
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.has = function (key, fn) {
|
||||
this.store.cmd.hexists(this.id, key, function (err, has) {
|
||||
if (err) return fn(err);
|
||||
fn(null, !!has);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys client
|
||||
*
|
||||
* @param {Number} number of seconds to expire data
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.destroy = function (expiration) {
|
||||
if ('number' != typeof expiration) {
|
||||
this.store.cmd.del(this.id);
|
||||
} else {
|
||||
this.store.cmd.expire(this.id, expiration);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user