mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-11 16:08:24 -05:00
Compare commits
174 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
899de7f873 | ||
|
|
580100d491 | ||
|
|
acb9cb421d | ||
|
|
abe2394a24 | ||
|
|
7012ba6c64 |
26
.github/ISSUE_TEMPLATE.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
*Note*: for support questions, please use one of these channels: [stackoverflow](http://stackoverflow.com/questions/tagged/socket.io) or [slack](https://socketio.slack.com)
|
||||
|
||||
### You want to:
|
||||
|
||||
* [x] report a *bug*
|
||||
* [ ] request a *feature*
|
||||
|
||||
### Current behaviour
|
||||
|
||||
|
||||
### Steps to reproduce (if the current behaviour is a bug)
|
||||
|
||||
**Note**: the best way to get a quick answer is to provide a failing test case, by forking the following [fiddle](https://github.com/darrachequesne/socket.io-fiddle) for example.
|
||||
|
||||
### Expected behaviour
|
||||
|
||||
|
||||
### Setup
|
||||
- OS:
|
||||
- browser:
|
||||
- socket.io version:
|
||||
|
||||
### Other information (e.g. stacktraces, related issues, suggestions how to fix)
|
||||
|
||||
|
||||
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
### The kind of change this PR does introduce
|
||||
|
||||
* [x] a bug fix
|
||||
* [ ] a new feature
|
||||
* [ ] an update to the documentation
|
||||
* [ ] a code change that improves performance
|
||||
* [ ] other
|
||||
|
||||
### Current behaviour
|
||||
|
||||
|
||||
### New behaviour
|
||||
|
||||
|
||||
### Other information (e.g. related issues)
|
||||
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,3 +9,5 @@ lib-cov
|
||||
benchmarks/*.png
|
||||
node_modules
|
||||
coverage
|
||||
.idea
|
||||
dist
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
support
|
||||
test
|
||||
examples
|
||||
.gitignore
|
||||
12
.travis.yml
12
.travis.yml
@@ -1,20 +1,12 @@
|
||||
sudo: false
|
||||
before_install:
|
||||
- npm install -g npm@'>=1.4.3'
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.8"
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
- "4"
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
#matrix:
|
||||
#fast_finish: true
|
||||
#allow_failures:
|
||||
#- node_js: "0.11"
|
||||
|
||||
notifications:
|
||||
irc: "irc.freenode.org#socket.io"
|
||||
|
||||
152
History.md
152
History.md
@@ -1,4 +1,155 @@
|
||||
|
||||
2.0.0 / 2017-05-09
|
||||
===================
|
||||
|
||||
* [feat] Move binary detection to the parser (#2923)
|
||||
* [feat] Allow to join several rooms at once (#2879)
|
||||
* [feat] Merge Engine.IO and Socket.IO handshake packets (#2833)
|
||||
* [feat] Allow the use of custom parsers (#2829)
|
||||
|
||||
* [fix] Use path.resolve by default and require.resolve as a fallback (#2797)
|
||||
* [fix] Properly close the connection on error (#2681)
|
||||
* [fix] Prevent null from being accepted as argument (#2606)
|
||||
|
||||
* [perf] Use shared instance of the encoder (#2825)
|
||||
* [perf] Reset properties instead of deleting them (#2826)
|
||||
* [perf] micro-optimisations (#2793)
|
||||
|
||||
* [chore] Merge history of 1.7.x and 0.9.x branches (#2930)
|
||||
* [chore] Added backers and sponsors on the README (#2933)
|
||||
* [chore] Bump dependencies (#2926)
|
||||
* [chore] Bump socket.io-adapter to version 1.0.0 (#2867)
|
||||
* [chore] Bump engine.io to version 2.0.2 (#2864)
|
||||
* [chore] Bump engine.io to version 2.0.0 (#2832)
|
||||
* [chore] Update issue template with fiddle (#2811)
|
||||
* [chore] Update copyright year LICENSE to 2017 (#2803)
|
||||
|
||||
* [docs] Add an example of custom parser (#2929)
|
||||
* [docs] Replace non-breaking space with proper whitespace (#2913)
|
||||
* [docs] Update emit cheatsheet (#2906)
|
||||
* [docs] Explicitly document that Server extends EventEmitter (#2874)
|
||||
* [docs] Add server.engine.generateId attribute (#2880)
|
||||
* [docs] Fix wrong space character in README (#2900)
|
||||
* [docs] Fix documentation for 'connect' event (#2898)
|
||||
* [docs] Add webpack build example (#2828)
|
||||
* [docs] Update the wording to match the code example (#2853)
|
||||
* [docs] Small addition to the Express Readme Part (#2846)
|
||||
* [docs] Add a 'Features' section in the README (#2824)
|
||||
* [docs] Add httpd cluster example (#2819)
|
||||
* [docs] Add haproxy cluster example (#2818)
|
||||
* [docs] Add nginx cluster example (#2817)
|
||||
* [docs] Implement whiteboard example (#2810)
|
||||
* [docs] Fix documentation for `local` flag (#2816)
|
||||
* [docs] Add emit cheatsheet (#2815)
|
||||
* [docs] Add pingInterval/pingTimeout/transports options in the API documentation (#2814)
|
||||
* [docs] Add an example for socket.join() method (#2813)
|
||||
* [docs] Fix a typo on `clients` method in the API documentation (#2812)
|
||||
* [docs] Fix wrong argument name in API.md (#2802)
|
||||
* [docs] Add install script on Readme.md (#2780)
|
||||
* [docs] API documentation (#2784)
|
||||
|
||||
1.7.4 / 2017-05-07
|
||||
===================
|
||||
|
||||
* [chore] Bump engine.io to version 1.8.4
|
||||
|
||||
0.9.18 / 2017-05-07
|
||||
===================
|
||||
|
||||
* Remove process.EventEmitter usage for Node 7.x
|
||||
|
||||
1.7.3 / 2017-02-17
|
||||
===================
|
||||
|
||||
* [chore] Bump engine.io to version 1.8.3
|
||||
|
||||
1.7.2 / 2016-12-11
|
||||
===================
|
||||
|
||||
* [chore] Bump engine.io to version 1.8.2 (#2782)
|
||||
* [fix] Fixes socket.use error packet (#2772)
|
||||
|
||||
1.7.1 / 2016-11-28
|
||||
===================
|
||||
|
||||
1.7.0 / 2016-11-27
|
||||
===================
|
||||
|
||||
* [docs] Comment connected socket availability for adapters (#2081)
|
||||
* [docs] Fixed grammar issues in the README.md (#2159)
|
||||
* [feature] serve sourcemap for socket.io-client (#2482)
|
||||
* [feature] Add a `local` flag (#2628)
|
||||
* [chore] Bump engine.io to version 1.8.1 (#2765)
|
||||
* [chore] Update client location and serve minified file (#2766)
|
||||
|
||||
1.6.0 / 2016-11-20
|
||||
==================
|
||||
|
||||
* [fix] Make ETag header comply with standard. (#2603)
|
||||
* [feature] Loading client script on demand. (#2567)
|
||||
* [test] Fix leaking clientSocket (#2721)
|
||||
* [feature] Add support for all event emitter methods (#2601)
|
||||
* [chore] Update year to 2016 (#2456)
|
||||
* [feature] Add support for socket middleware (#2306)
|
||||
* [feature] add support for Server#close(callback) (#2748)
|
||||
* [fix] Don't drop query variables on handshake (#2745)
|
||||
* [example] Add disconnection/reconnection logs to the chat example (#2675)
|
||||
* [perf] Minor code optimizations (#2219)
|
||||
* [chore] Bump debug to version 2.3.3 (#2754)
|
||||
* [chore] Bump engine.io to version 1.8.0 (#2755)
|
||||
* [chore] Bump socket.io-adapter to version 0.5.0 (#2756)
|
||||
|
||||
1.5.1 / 2016-10-24
|
||||
==================
|
||||
|
||||
* [fix] Avoid swallowing exceptions thrown by user event handlers (#2682)
|
||||
* [test] Use client function to unify `client` in test script (#2731)
|
||||
* [docs] Add link to LICENSE (#2221)
|
||||
* [docs] Fix JSDoc of optional parameters (#2465)
|
||||
* [docs] Fix typo (#2724)
|
||||
* [docs] Link readme npm package badge to npm registry page (#2612)
|
||||
* [docs] Minor fixes (#2526)
|
||||
* [chore] Bump socket.io-parser to 2.3.0 (#2730)
|
||||
* [chore] Add Github issue and PR templates (#2733)
|
||||
* [chore] Bump engine.io to 1.7.2 (#2729)
|
||||
* [chore] Bump socket.io-parser to 2.3.1 (#2734)
|
||||
|
||||
1.5.0 / 2016-10-06
|
||||
==================
|
||||
|
||||
* [feature] stop append /# before id when no namespace (#2508)
|
||||
* [feature] Add a 'disconnecting' event to access to socket.rooms upon disconnection (#2332)
|
||||
* [fix] Fix query string management (#2422)
|
||||
* [fix] add quote to exec paths, prevent error when spaces in path (#2508)
|
||||
* [docs] Prevent mixup for new programmers (#2599)
|
||||
* [example] Fix chat display in Firefox (#2477)
|
||||
* [chore] Add gulp & babel in the build process (#2471)
|
||||
* [chore] Bump engine.io to 1.7.0 (#2707)
|
||||
* [chore] Remove unused zuul-ngrok dependency (#2708)
|
||||
* [chore] Point towards current master of socket.io-client (#2710)
|
||||
* [chore] Restrict files included in npm package (#2709)
|
||||
* [chore] Link build badge to master branch (#2549)
|
||||
|
||||
1.4.8 / 2016-06-23
|
||||
==================
|
||||
|
||||
* package: bump `engine.io`
|
||||
|
||||
1.4.7 / 2016-06-23
|
||||
==================
|
||||
|
||||
* package: bump `engine.io`
|
||||
|
||||
1.4.6 / 2016-05-02
|
||||
==================
|
||||
|
||||
* package: bump engine.io
|
||||
|
||||
1.4.5 / 2016-01-26
|
||||
==================
|
||||
|
||||
* fix closing the underlying `http.Server`
|
||||
|
||||
1.4.4 / 2016-01-10
|
||||
==================
|
||||
|
||||
@@ -46,6 +197,7 @@
|
||||
* package: bump `has-binary` to work with all objects (fixes #1955)
|
||||
* fix origin verification default https port [evanlucas]
|
||||
* support compression [nkzawa]
|
||||
* changed type of `Client#sockets`, `Namespace#sockets` and `Socket#rooms` to maps (instead of arrays)
|
||||
|
||||
1.3.7 / 2015-09-21
|
||||
==================
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014-2015 Automattic <dev@cloudup.com>
|
||||
Copyright (c) 2014-2017 Automattic <dev@cloudup.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
11
Makefile
11
Makefile
@@ -1,15 +1,8 @@
|
||||
|
||||
REPORTER = dot
|
||||
|
||||
test:
|
||||
@./node_modules/.bin/mocha \
|
||||
--reporter $(REPORTER) \
|
||||
--slow 200ms \
|
||||
--bail
|
||||
@./node_modules/.bin/gulp test
|
||||
|
||||
test-cov:
|
||||
@./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- \
|
||||
--reporter $(REPORTER) \
|
||||
test/
|
||||
@./node_modules/.bin/gulp test-cov
|
||||
|
||||
.PHONY: test
|
||||
|
||||
530
Readme.md
530
Readme.md
@@ -1,11 +1,91 @@
|
||||
|
||||
# socket.io
|
||||
|
||||
[](https://travis-ci.org/socketio/socket.io)
|
||||

|
||||
[](#backers) [](#sponsors)
|
||||
[](https://travis-ci.org/socketio/socket.io)
|
||||
[](https://david-dm.org/socketio/socket.io)
|
||||
[](https://david-dm.org/socketio/socket.io#info=devDependencies)
|
||||
[](https://www.npmjs.com/package/socket.io)
|
||||

|
||||
[](http://slack.socket.io)
|
||||
|
||||
## Features
|
||||
|
||||
Socket.IO enables real-time bidirectional event-based communication. It consists in:
|
||||
|
||||
- a Node.js server (this repository)
|
||||
- a [Javascript client library](https://github.com/socketio/socket.io-client) for the browser (or a Node.js client)
|
||||
|
||||
Some implementations in other languages are also available:
|
||||
|
||||
- [Java](https://github.com/socketio/socket.io-client-java)
|
||||
- [C++](https://github.com/socketio/socket.io-client-cpp)
|
||||
- [Swift](https://github.com/socketio/socket.io-client-swift)
|
||||
|
||||
Its main features are:
|
||||
|
||||
#### Reliability
|
||||
|
||||
Connections are established even in the presence of:
|
||||
- proxies and load balancers.
|
||||
- personal firewall and antivirus software.
|
||||
|
||||
For this purpose, it relies on [Engine.IO](https://github.com/socketio/engine.io), which first establishes a long-polling connection, then tries to upgrade to better transports that are "tested" on the side, like WebSocket. Please see the [Goals](https://github.com/socketio/engine.io#goals) section for more information.
|
||||
|
||||
#### Auto-reconnection support
|
||||
|
||||
Unless instructed otherwise a disconnected client will try to reconnect forever, until the server is available again. Please see the available reconnection options [here](https://github.com/socketio/socket.io-client/blob/master/docs/API.md#new-managerurl-options).
|
||||
|
||||
#### Disconnection detection
|
||||
|
||||
An heartbeat mechanism is implemented at the Engine.IO level, allowing both the server and the client to know when the other one is not responding anymore.
|
||||
|
||||
That functionality is achieved with timers set on both the server and the client, with timeout values (the `pingInterval` and `pingTimeout` parameters) shared during the connection handshake. Those timers require any subsequent client calls to be directed to the same server, hence the `sticky-session` requirement when using multiples nodes.
|
||||
|
||||
#### Binary support
|
||||
|
||||
Any serializable data structures can be emitted, including:
|
||||
|
||||
- [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) and [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) in the browser
|
||||
- [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) and [Buffer](https://nodejs.org/api/buffer.html) in Node.js
|
||||
|
||||
#### Simple and convenient API
|
||||
|
||||
Sample code:
|
||||
|
||||
```js
|
||||
io.on('connection', function(socket){
|
||||
socket.emit('request', /* */); // emit an event to the socket
|
||||
io.emit('broadcast', /* */); // emit an event to all connected sockets
|
||||
socket.on('reply', function(){ /* */ }); // listen to the event
|
||||
});
|
||||
```
|
||||
|
||||
#### Cross-browser
|
||||
|
||||
Browser support is tested in Saucelabs:
|
||||
|
||||
[](https://saucelabs.com/u/socket)
|
||||
|
||||
#### Multiplexing support
|
||||
|
||||
In order to create separation of concerns within your application (for example per module, or based on permissions), Socket.IO allows you to create several `Namespaces`, which will act as separate communication channels but will share the same underlying connection.
|
||||
|
||||
#### Room support
|
||||
|
||||
Within each `Namespace`, you can define arbitrary channels, called `Rooms`, that sockets can join and leave. You can then broadcast to any given room, reaching every socket that has joined it.
|
||||
|
||||
This is a useful feature to send notifications to a group of users, or to a given user connected on several devices for example.
|
||||
|
||||
|
||||
**Note:** Socket.IO is not a WebSocket implementation. Although Socket.IO indeed uses WebSocket as a transport when possible, it adds some metadata to each packet: the packet type, the namespace and the ack id when a message acknowledgement is needed. That is why a WebSocket client will not be able to successfully connect to a Socket.IO server, and a Socket.IO client will not be able to connect to a WebSocket server (like `ws://echo.websocket.org`) either. Please see the protocol specification [here](https://github.com/socketio/socket.io-protocol).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install socket.io --save
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
The following example attaches socket.io to a plain Node.JS
|
||||
@@ -14,9 +94,9 @@ HTTP server listening on port `3000`.
|
||||
```js
|
||||
var server = require('http').createServer();
|
||||
var io = require('socket.io')(server);
|
||||
io.on('connection', function(socket){
|
||||
socket.on('event', function(data){});
|
||||
socket.on('disconnect', function(){});
|
||||
io.on('connection', function(client){
|
||||
client.on('event', function(data){});
|
||||
client.on('disconnect', function(){});
|
||||
});
|
||||
server.listen(3000);
|
||||
```
|
||||
@@ -25,7 +105,7 @@ server.listen(3000);
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){});
|
||||
io.on('connection', function(client){});
|
||||
io.listen(3000);
|
||||
```
|
||||
|
||||
@@ -34,7 +114,7 @@ io.listen(3000);
|
||||
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.
|
||||
function. Also make sure to call `.listen` on the `server`, not the `app`.
|
||||
|
||||
```js
|
||||
var app = require('express')();
|
||||
@@ -57,355 +137,9 @@ io.on('connection', function(){ /* … */ });
|
||||
server.listen(3000);
|
||||
```
|
||||
|
||||
## API
|
||||
## Documentation
|
||||
|
||||
### Server
|
||||
|
||||
Exposed by `require('socket.io')`.
|
||||
|
||||
### Server()
|
||||
|
||||
Creates a new `Server`. Works with and without `new`:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
// or
|
||||
var Server = require('socket.io');
|
||||
var io = new Server();
|
||||
```
|
||||
|
||||
### Server(opts:Object)
|
||||
|
||||
Optionally, the first or second argument (see below) of the `Server`
|
||||
constructor can be an options object.
|
||||
|
||||
The following options are supported:
|
||||
|
||||
- `serveClient` sets the value for Server#serveClient()
|
||||
- `path` sets the value for Server#path()
|
||||
|
||||
The same options passed to socket.io are always passed to
|
||||
the `engine.io` `Server` that gets created. See engine.io
|
||||
[options](https://github.com/socketio/engine.io#methods-1)
|
||||
as reference.
|
||||
|
||||
### Server(srv:http#Server, opts:Object)
|
||||
|
||||
Creates a new `Server` and attaches it to the given `srv`. Optionally
|
||||
`opts` can be passed.
|
||||
|
||||
### Server(port:Number, opts:Object)
|
||||
|
||||
Binds socket.io to a new `http.Server` that listens on `port`.
|
||||
|
||||
### Server#serveClient(v:Boolean):Server
|
||||
|
||||
If `v` is `true` the attached server (see `Server#attach`) will serve
|
||||
the client files. Defaults to `true`.
|
||||
|
||||
This method has no effect after `attach` is called.
|
||||
|
||||
```js
|
||||
// pass a server and the `serveClient` option
|
||||
var io = require('socket.io')(http, { serveClient: false });
|
||||
|
||||
// or pass no server and then you can call the method
|
||||
var io = require('socket.io')();
|
||||
io.serveClient(false);
|
||||
io.attach(http);
|
||||
```
|
||||
|
||||
If no arguments are supplied this method returns the current value.
|
||||
|
||||
### Server#path(v:String):Server
|
||||
|
||||
Sets the path `v` under which `engine.io` and the static files will be
|
||||
served. Defaults to `/socket.io`.
|
||||
|
||||
If no arguments are supplied this method returns the current value.
|
||||
|
||||
### Server#adapter(v:Adapter):Server
|
||||
|
||||
Sets the adapter `v`. Defaults to an instance of the `Adapter` that
|
||||
ships with socket.io which is memory based. See
|
||||
[socket.io-adapter](https://github.com/socketio/socket.io-adapter).
|
||||
|
||||
If no arguments are supplied this method returns the current value.
|
||||
|
||||
### Server#origins(v:String):Server
|
||||
|
||||
Sets the allowed origins `v`. Defaults to any origins being allowed.
|
||||
|
||||
If no arguments are supplied this method returns the current value.
|
||||
|
||||
### Server#origins(v:Function):Server
|
||||
|
||||
Sets the allowed origins as dynamic function. Function takes two arguments `origin:String` and `callback(error, success)`, where `success` is a boolean value indicating whether origin is allowed or not.
|
||||
|
||||
__Potential drawbacks__:
|
||||
* in some situations, when it is not possible to determine `origin` it may have value of `*`
|
||||
* As this function will be executed for every request, it is advised to make this function work as fast as possible
|
||||
* If `socket.io` is used together with `Express`, the CORS headers will be affected only for `socket.io` requests. For Express can use [cors](https://github.com/expressjs/cors)
|
||||
|
||||
|
||||
### Server#sockets:Namespace
|
||||
|
||||
The default (`/`) namespace.
|
||||
|
||||
### Server#attach(srv:http#Server, opts:Object):Server
|
||||
|
||||
Attaches the `Server` to an engine.io instance on `srv` with the
|
||||
supplied `opts` (optionally).
|
||||
|
||||
### Server#attach(port:Number, opts:Object):Server
|
||||
|
||||
Attaches the `Server` to an engine.io instance that is bound to `port`
|
||||
with the given `opts` (optionally).
|
||||
|
||||
### Server#listen
|
||||
|
||||
Synonym of `Server#attach`.
|
||||
|
||||
### Server#bind(srv:engine#Server):Server
|
||||
|
||||
Advanced use only. Binds the server to a specific engine.io `Server`
|
||||
(or compatible API) instance.
|
||||
|
||||
### Server#onconnection(socket:engine#Socket):Server
|
||||
|
||||
Advanced use only. Creates a new `socket.io` client from the incoming
|
||||
engine.io (or compatible API) `socket`.
|
||||
|
||||
### Server#of(nsp:String):Namespace
|
||||
|
||||
Initializes and retrieves the given `Namespace` by its pathname
|
||||
identifier `nsp`.
|
||||
|
||||
If the namespace was already initialized it returns it right away.
|
||||
|
||||
### Server#emit
|
||||
|
||||
Emits an event to all connected clients. The following two are
|
||||
equivalent:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.sockets.emit('an event sent to all connected clients');
|
||||
io.emit('an event sent to all connected clients');
|
||||
```
|
||||
|
||||
For other available methods, see `Namespace` below.
|
||||
|
||||
### Server#close
|
||||
|
||||
Closes socket server
|
||||
|
||||
```js
|
||||
var Server = require('socket.io');
|
||||
var PORT = 3030;
|
||||
var server = require('http').Server();
|
||||
|
||||
var io = Server(PORT);
|
||||
|
||||
io.close(); // Close current server
|
||||
|
||||
server.listen(PORT); // PORT is free to use
|
||||
|
||||
io = Server(server);
|
||||
```
|
||||
|
||||
### Server#use
|
||||
|
||||
See `Namespace#use` below.
|
||||
|
||||
### Namespace
|
||||
|
||||
Represents a pool of sockets connected under a given scope identified
|
||||
by a pathname (eg: `/chat`).
|
||||
|
||||
By default the client always connects to `/`.
|
||||
|
||||
#### Events
|
||||
|
||||
- `connection` / `connect`. Fired upon a connection.
|
||||
|
||||
Parameters:
|
||||
- `Socket` the incoming socket.
|
||||
|
||||
### Namespace#name:String
|
||||
|
||||
The namespace identifier property.
|
||||
|
||||
### Namespace#connected:Object<Socket>
|
||||
|
||||
Hash of `Socket` objects that are connected to this namespace indexed
|
||||
by `id`.
|
||||
|
||||
### Namespace#clients(fn:Function)
|
||||
|
||||
Gets a list of client IDs connected to this namespace (across all nodes if applicable).
|
||||
|
||||
An example to get all clients in a namespace:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.of('/chat').clients(function(error, clients){
|
||||
if (error) throw error;
|
||||
console.log(clients); // => [PZDoMHjiu8PYfRiKAAAF, Anw2LatarvGVVXEIAAAD]
|
||||
});
|
||||
```
|
||||
|
||||
An example to get all clients in namespace's room:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.of('/chat').in('general').clients(function(error, clients){
|
||||
if (error) throw error;
|
||||
console.log(clients); // => [Anw2LatarvGVVXEIAAAD]
|
||||
});
|
||||
```
|
||||
|
||||
As with broadcasting, the default is all clients from the default namespace ('/'):
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.clients(function(error, clients){
|
||||
if (error) throw error;
|
||||
console.log(clients); // => [6em3d4TJP8Et9EMNAAAA, G5p55dHhGgUnLUctAAAB]
|
||||
});
|
||||
```
|
||||
|
||||
### Namespace#use(fn:Function):Namespace
|
||||
|
||||
Registers a middleware, which is a function that gets executed for
|
||||
every incoming `Socket` and receives as parameter the socket and a
|
||||
function to optionally defer execution to the next registered
|
||||
middleware.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.use(function(socket, next){
|
||||
if (socket.request.headers.cookie) return next();
|
||||
next(new Error('Authentication error'));
|
||||
});
|
||||
```
|
||||
|
||||
Errors passed to middleware callbacks are sent as special `error`
|
||||
packets to clients.
|
||||
|
||||
### 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.
|
||||
|
||||
### Socket#rooms:Array
|
||||
|
||||
A list of strings identifying the rooms this socket is in.
|
||||
|
||||
### Socket#client:Client
|
||||
|
||||
A reference to the underlying `Client` object.
|
||||
|
||||
### Socket#conn:Socket
|
||||
|
||||
A reference to the underlying `Client` transport connection (engine.io
|
||||
`Socket` object).
|
||||
|
||||
### Socket#request:Request
|
||||
|
||||
A getter proxy that returns the reference to the `request` that
|
||||
originated the underlying engine.io `Client`. Useful for accessing
|
||||
request headers such as `Cookie` or `User-Agent`.
|
||||
|
||||
### Socket#id:String
|
||||
|
||||
A unique identifier for the socket session, that comes from the
|
||||
underlying `Client`.
|
||||
|
||||
### Socket#emit(name:String[, …]):Socket
|
||||
|
||||
Emits an event to the socket identified by the string `name`. Any
|
||||
other parameters can be included.
|
||||
|
||||
All datastructures are supported, including `Buffer`. JavaScript
|
||||
functions can't be serialized/deserialized.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.emit('an event', { some: 'data' });
|
||||
});
|
||||
```
|
||||
|
||||
### Socket#join(name:String[, fn:Function]):Socket
|
||||
|
||||
Adds the socket to the `room`, and fires optionally a callback `fn`
|
||||
with `err` signature (if any).
|
||||
|
||||
The socket is automatically a member of a room identified with its
|
||||
session id (see `Socket#id`).
|
||||
|
||||
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).
|
||||
|
||||
### Socket#leave(name:String[, fn:Function]):Socket
|
||||
|
||||
Removes the socket from `room`, and fires optionally a callback `fn`
|
||||
with `err` signature (if any).
|
||||
|
||||
**Rooms are left automatically upon disconnection**.
|
||||
|
||||
The mechanics of leaving rooms are handled by the `Adapter`
|
||||
that has been configured (see `Server#adapter` above), defaulting to
|
||||
[socket.io-adapter](https://github.com/socketio/socket.io-adapter).
|
||||
|
||||
### Socket#to(room:String):Socket
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event will
|
||||
only be _broadcasted_ to sockets that have joined the given `room`.
|
||||
|
||||
To emit to multiple rooms, you can call `to` several times.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.to('others').emit('an event', { some: 'data' });
|
||||
});
|
||||
```
|
||||
|
||||
### Socket#in(room:String):Socket
|
||||
|
||||
Same as `Socket#to`
|
||||
|
||||
### Socket#compress(v:Boolean):Socket
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event data will
|
||||
only be _compressed_ if the value is `true`. Defaults to `true` when you don't call the method.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.compress(false).emit('an event', { some: 'data' });
|
||||
});
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
The `Client` class represents an incoming transport (engine.io)
|
||||
connection. A `Client` can be associated with many multiplexed `Socket`
|
||||
that belong to different `Namespace`s.
|
||||
|
||||
### Client#conn
|
||||
|
||||
A reference to the underlying `engine.io` `Socket` connection.
|
||||
|
||||
### Client#request
|
||||
|
||||
A getter proxy that returns the reference to the `request` that
|
||||
originated the engine.io connection. Useful for accessing
|
||||
request headers such as `Cookie` or `User-Agent`.
|
||||
Please see the documentation [here](/docs/README.md). Contributions are welcome!
|
||||
|
||||
## Debug / logging
|
||||
|
||||
@@ -419,6 +153,90 @@ To see the output from all of Socket.IO's debugging scopes you can use:
|
||||
DEBUG=socket.io* node myapp
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
This runs the `gulp` task `test`. By default the test will be run with the source code in `lib` directory.
|
||||
|
||||
Set the environmental variable `TEST_VERSION` to `compat` to test the transpiled es5-compat version of the code.
|
||||
|
||||
The `gulp` task `test` will always transpile the source code into es5 and export to `dist` first before running the test.
|
||||
|
||||
|
||||
## Backers
|
||||
|
||||
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/socketio#backer)]
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
## Sponsors
|
||||
|
||||
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)]
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
[MIT](LICENSE)
|
||||
|
||||
619
docs/API.md
Normal file
619
docs/API.md
Normal file
@@ -0,0 +1,619 @@
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Class: Server](#server)
|
||||
- [new Server(httpServer[, options])](#new-serverhttpserver-options)
|
||||
- [new Server(port[, options])](#new-serverport-options)
|
||||
- [new Server(options)](#new-serveroptions)
|
||||
- [server.sockets](#serversockets)
|
||||
- [server.engine.generateId](#serverenginegenerateid)
|
||||
- [server.serveClient([value])](#serverserveclientvalue)
|
||||
- [server.path([value])](#serverpathvalue)
|
||||
- [server.adapter([value])](#serveradaptervalue)
|
||||
- [server.origins([value])](#serveroriginsvalue)
|
||||
- [server.origins(fn)](#serveroriginsfn)
|
||||
- [server.attach(httpServer[, options])](#serverattachhttpserver-options)
|
||||
- [server.attach(port[, options])](#serverattachport-options)
|
||||
- [server.listen(httpServer[, options])](#serverlistenhttpserver-options)
|
||||
- [server.listen(port[, options])](#serverlistenport-options)
|
||||
- [server.bind(engine)](#serverbindengine)
|
||||
- [server.onconnection(socket)](#serveronconnectionsocket)
|
||||
- [server.of(nsp)](#serverofnsp)
|
||||
- [server.close([callback])](#serverclosecallback)
|
||||
- [Class: Namespace](#namespace)
|
||||
- [namespace.name](#namespacename)
|
||||
- [namespace.connected](#namespaceconnected)
|
||||
- [namespace.emit(eventName[, ...args])](#namespaceemiteventname-args)
|
||||
- [namespace.clients(callback)](#namespaceclientscallback)
|
||||
- [namespace.use(fn)](#namespaceusefn)
|
||||
- [Event: 'connect'](#event-connect)
|
||||
- [Event: 'connection'](#event-connect)
|
||||
- [Flag: 'volatile'](#flag-volatile)
|
||||
- [Flag: 'local'](#flag-local)
|
||||
- [Class: Socket](#socket)
|
||||
- [socket.id](#socketid)
|
||||
- [socket.rooms](#socketrooms)
|
||||
- [socket.client](#socketclient)
|
||||
- [socket.conn](#socketconn)
|
||||
- [socket.request](#socketrequest)
|
||||
- [socket.use(fn)](#socketusefn)
|
||||
- [socket.send([...args][, ack])](#socketsendargs-ack)
|
||||
- [socket.emit(eventName[, ...args][, ack])](#socketemiteventname-args-ack)
|
||||
- [socket.on(eventName, callback)](#socketoneventname-callback)
|
||||
- [socket.once(eventName, listener)](#socketonceeventname-listener)
|
||||
- [socket.removeListener(eventName, listener)](#socketremovelistenereventname-listener)
|
||||
- [socket.removeAllListeners([eventName])](#socketremovealllistenerseventname)
|
||||
- [socket.eventNames()](#socketeventnames)
|
||||
- [socket.join(room[, callback])](#socketjoinroom-callback)
|
||||
- [socket.join(rooms[, callback])](#socketjoinrooms-callback)
|
||||
- [socket.leave(room[, callback])](#socketleaveroom-callback)
|
||||
- [socket.to(room)](#sockettoroom)
|
||||
- [socket.in(room)](#socketinroom)
|
||||
- [socket.compress(value)](#socketcompressvalue)
|
||||
- [socket.disconnect(close)](#socketdisconnectclose)
|
||||
- [Flag: 'broadcast'](#flag-broadcast)
|
||||
- [Flag: 'volatile'](#flag-volatile-1)
|
||||
- [Event: 'disconnect'](#event-disconnect)
|
||||
- [Event: 'error'](#event-error)
|
||||
- [Event: 'disconnecting'](#event-disconnecting)
|
||||
- [Class: Client](#client)
|
||||
- [client.conn](#clientconn)
|
||||
- [client.request](#clientrequest)
|
||||
|
||||
|
||||
### Server
|
||||
|
||||
Exposed by `require('socket.io')`.
|
||||
|
||||
#### new Server(httpServer[, options])
|
||||
|
||||
- `httpServer` _(http.Server)_ the server to bind to.
|
||||
- `options` _(Object)_
|
||||
- `path` _(String)_: name of the path to capture (`/socket.io`)
|
||||
- `serveClient` _(Boolean)_: whether to serve the client files (`true`)
|
||||
- `adapter` _(Adapter)_: the adapter to use. Defaults to an instance of the `Adapter` that ships with socket.io which is memory based. See [socket.io-adapter](https://github.com/socketio/socket.io-adapter)
|
||||
- `origins` _(String)_: the allowed origins (`*`)
|
||||
- `allowRequest` _(Function)_: A function that receives a given handshake or upgrade request as its first parameter, and can decide whether to continue or not. The second argument is a function that needs to be called with the decided information: `fn(err, success)`, where `success` is a boolean value where false means that the request is rejected, and err is an error code.
|
||||
- `parser` _(Parser)_: the parser to use. Defaults to an instance of the `Parser` that ships with socket.io. See [socket.io-parser](https://github.com/socketio/socket.io-parser).
|
||||
|
||||
Works with and without `new`:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
// or
|
||||
var Server = require('socket.io');
|
||||
var io = new Server();
|
||||
```
|
||||
|
||||
The same options passed to socket.io are always passed to the `engine.io` `Server` that gets created. See engine.io [options](https://github.com/socketio/engine.io#methods-1) as reference.
|
||||
|
||||
Among those options:
|
||||
|
||||
- `pingTimeout` _(Number)_: how many ms without a pong packet to consider the connection closed (`60000`)
|
||||
- `pingInterval` _(Number)_: how many ms before sending a new ping packet (`25000`).
|
||||
|
||||
Those two parameters will impact the delay before a client knows the server is not available anymore. For example, if the underlying TCP connection is not closed properly due to a network issue, a client may have to wait up to `pingTimeout + pingInterval` ms before getting a `disconnect` event.
|
||||
|
||||
- `transports` _(Array<String>)_: transports to allow connections to (`['polling', 'websocket']`).
|
||||
|
||||
**Note:** The order is important. By default, a long-polling connection is established first, and then upgraded to WebSocket if possible. Using `['websocket']` means there will be no fallback if a WebSocket connection cannot be opened.
|
||||
|
||||
#### new Server(port[, options])
|
||||
|
||||
- `port` _(Number)_ a port to listen to (a new `http.Server` will be created)
|
||||
- `options` _(Object)_
|
||||
|
||||
See [above](#new-serverhttpserver-options) for available options.
|
||||
|
||||
#### new Server(options)
|
||||
|
||||
- `options` _(Object)_
|
||||
|
||||
See [above](#new-serverhttpserver-options) for available options.
|
||||
|
||||
#### server.sockets
|
||||
|
||||
* _(Namespace)_
|
||||
|
||||
The default (`/`) namespace.
|
||||
|
||||
#### server.serveClient([value])
|
||||
|
||||
- `value` _(Boolean)_
|
||||
- **Returns** `Server|Boolean`
|
||||
|
||||
If `value` is `true` the attached server (see `Server#attach`) will serve the client files. Defaults to `true`. This method has no effect after `attach` is called. If no arguments are supplied this method returns the current value.
|
||||
|
||||
```js
|
||||
// pass a server and the `serveClient` option
|
||||
var io = require('socket.io')(http, { serveClient: false });
|
||||
|
||||
// or pass no server and then you can call the method
|
||||
var io = require('socket.io')();
|
||||
io.serveClient(false);
|
||||
io.attach(http);
|
||||
```
|
||||
|
||||
#### server.path([value])
|
||||
|
||||
- `value` _(String)_
|
||||
- **Returns** `Server|String`
|
||||
|
||||
Sets the path `value` under which `engine.io` and the static files will be served. Defaults to `/socket.io`. If no arguments are supplied this method returns the current value.
|
||||
|
||||
#### server.adapter([value])
|
||||
|
||||
- `value` _(Adapter)_
|
||||
- **Returns** `Server|Adapter`
|
||||
|
||||
Sets the adapter `value`. Defaults to an instance of the `Adapter` that ships with socket.io which is memory based. See [socket.io-adapter](https://github.com/socketio/socket.io-adapter). If no arguments are supplied this method returns the current value.
|
||||
|
||||
#### server.origins([value])
|
||||
|
||||
- `value` _(String)_
|
||||
- **Returns** `Server|String`
|
||||
|
||||
Sets the allowed origins `value`. Defaults to any origins being allowed. If no arguments are supplied this method returns the current value.
|
||||
|
||||
#### server.origins(fn)
|
||||
|
||||
- `fn` _(Function)_
|
||||
- **Returns** `Server`
|
||||
|
||||
Provides a function taking two arguments `origin:String` and `callback(error, success)`, where `success` is a boolean value indicating whether origin is allowed or not.
|
||||
|
||||
__Potential drawbacks__:
|
||||
* in some situations, when it is not possible to determine `origin` it may have value of `*`
|
||||
* As this function will be executed for every request, it is advised to make this function work as fast as possible
|
||||
* If `socket.io` is used together with `Express`, the CORS headers will be affected only for `socket.io` requests. For Express can use [cors](https://github.com/expressjs/cors).
|
||||
|
||||
#### server.attach(httpServer[, options])
|
||||
|
||||
- `httpServer` _(http.Server)_ the server to attach to
|
||||
- `options` _(Object)_
|
||||
|
||||
Attaches the `Server` to an engine.io instance on `httpServer` with the supplied `options` (optionally).
|
||||
|
||||
### server.attach(port[, options])
|
||||
|
||||
- `port` _(Number)_ the port to listen on
|
||||
- `options` _(Object)_
|
||||
|
||||
Attaches the `Server` to an engine.io instance on a new http.Server with the supplied `options` (optionally).
|
||||
|
||||
#### server.listen(httpServer[, options])
|
||||
|
||||
Synonym of [server.attach(httpServer[, options])](#serverattachhttpserver-options).
|
||||
|
||||
#### server.listen(port[, options])
|
||||
|
||||
Synonym of [server.attach(port[, options])](#serverattachport-options).
|
||||
|
||||
#### server.bind(engine)
|
||||
|
||||
- `engine` _(engine.Server)_
|
||||
- **Returns** `Server`
|
||||
|
||||
Advanced use only. Binds the server to a specific engine.io `Server` (or compatible API) instance.
|
||||
|
||||
#### server.onconnection(socket)
|
||||
|
||||
- `socket` _(engine.Socket)_
|
||||
- **Returns** `Server`
|
||||
|
||||
Advanced use only. Creates a new `socket.io` client from the incoming engine.io (or compatible API) `Socket`.
|
||||
|
||||
#### server.of(nsp)
|
||||
|
||||
- `nsp` _(String)_
|
||||
- **Returns** `Namespace`
|
||||
|
||||
Initializes and retrieves the given `Namespace` by its pathname identifier `nsp`. If the namespace was already initialized it returns it immediately.
|
||||
|
||||
#### server.close([callback])
|
||||
|
||||
- `callback` _(Function)_
|
||||
|
||||
Closes the socket.io server. The `callback` argument is optional and will be called when all connections are closed.
|
||||
|
||||
```js
|
||||
var Server = require('socket.io');
|
||||
var PORT = 3030;
|
||||
var server = require('http').Server();
|
||||
|
||||
var io = Server(PORT);
|
||||
|
||||
io.close(); // Close current server
|
||||
|
||||
server.listen(PORT); // PORT is free to use
|
||||
|
||||
io = Server(server);
|
||||
```
|
||||
|
||||
#### server.engine.generateId
|
||||
|
||||
Overwrites the default method to generate your custom socket id.
|
||||
|
||||
The function is called with a node request object (`http.IncomingMessage`) as first parameter.
|
||||
|
||||
```js
|
||||
io.engine.generateId = function (req) {
|
||||
return "custom:id:" + custom_id++; // custom id must be unique
|
||||
}
|
||||
```
|
||||
|
||||
### Namespace
|
||||
|
||||
Represents a pool of sockets connected under a given scope identified
|
||||
by a pathname (eg: `/chat`).
|
||||
|
||||
By default the client always connects to `/`.
|
||||
|
||||
#### namespace.name
|
||||
|
||||
* _(String)_
|
||||
|
||||
The namespace identifier property.
|
||||
|
||||
#### namespace.connected
|
||||
|
||||
* _(Object<Socket>)_
|
||||
|
||||
The hash of `Socket` objects that are connected to this namespace, indexed by `id`.
|
||||
|
||||
#### namespace.emit(eventName[, ...args])
|
||||
|
||||
- `eventName` _(String)_
|
||||
- `args`
|
||||
|
||||
Emits an event to all connected clients. The following two are equivalent:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
|
||||
io.emit('an event sent to all connected clients'); // main namespace
|
||||
|
||||
var chat = io.of('/chat');
|
||||
chat.emit('an event sent to all connected clients in chat namespace');
|
||||
```
|
||||
|
||||
#### namespace.clients(callback)
|
||||
|
||||
- `callback` _(Function)_
|
||||
|
||||
Gets a list of client IDs connected to this namespace (across all nodes if applicable).
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.of('/chat').clients(function(error, clients){
|
||||
if (error) throw error;
|
||||
console.log(clients); // => [PZDoMHjiu8PYfRiKAAAF, Anw2LatarvGVVXEIAAAD]
|
||||
});
|
||||
```
|
||||
|
||||
An example to get all clients in namespace's room:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.of('/chat').in('general').clients(function(error, clients){
|
||||
if (error) throw error;
|
||||
console.log(clients); // => [Anw2LatarvGVVXEIAAAD]
|
||||
});
|
||||
```
|
||||
|
||||
As with broadcasting, the default is all clients from the default namespace ('/'):
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.clients(function(error, clients){
|
||||
if (error) throw error;
|
||||
console.log(clients); // => [6em3d4TJP8Et9EMNAAAA, G5p55dHhGgUnLUctAAAB]
|
||||
});
|
||||
```
|
||||
|
||||
#### namespace.use(fn)
|
||||
|
||||
- `fn` _(Function)_
|
||||
|
||||
Registers a middleware, which is a function that gets executed for every incoming `Socket`, and receives as parameters the socket and a function to optionally defer execution to the next registered middleware.
|
||||
|
||||
Errors passed to middleware callbacks are sent as special `error` packets to clients.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.use(function(socket, next){
|
||||
if (socket.request.headers.cookie) return next();
|
||||
next(new Error('Authentication error'));
|
||||
});
|
||||
```
|
||||
|
||||
#### Event: 'connect'
|
||||
|
||||
- `socket` _(Socket)_ socket connection with client
|
||||
|
||||
Fired upon a connection from client.
|
||||
|
||||
#### Event: 'connection'
|
||||
|
||||
Synonym of [Event: 'connect'](#event-connect).
|
||||
|
||||
#### Flag: 'volatile'
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event data may be lost if the clients are not ready to receive messages (because of network slowness or other issues, or because they’re connected through long polling and is in the middle of a request-response cycle).
|
||||
|
||||
```js
|
||||
io.volatile.emit('an event', { some: 'data' }); // the clients may or may not receive it
|
||||
```
|
||||
|
||||
#### Flag: 'local'
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event data will only be _broadcast_ to the current node (when the [Redis adapter](https://github.com/socketio/socket.io-redis) is used).
|
||||
|
||||
```js
|
||||
io.local.emit('an event', { some: 'data' });
|
||||
```
|
||||
|
||||
### Socket
|
||||
|
||||
A `Socket` is the fundamental class for interacting with browser clients. A `Socket` belongs to a certain `Namespace` (by default `/`) and uses an underlying `Client` to communicate.
|
||||
|
||||
It should be noted the `Socket` doesn't relate directly to the actual underlying TCP/IP `socket` and it is only the name of the class.
|
||||
|
||||
Within each `Namespace`, you can also define arbitrary channels (called `room`) that the `Socket` can join and leave. That provides a convenient way to broadcast to a group of `Socket`s (see `Socket#to` below).
|
||||
|
||||
The `Socket` class inherits from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). The `Socket` class overrides the `emit` method, and does not modify any other `EventEmitter` method. All methods documented here which also appear as `EventEmitter` methods (apart from `emit`) are implemented by `EventEmitter`, and documentation for `EventEmitter` applies.
|
||||
|
||||
#### socket.id
|
||||
|
||||
* _(String)_
|
||||
|
||||
A unique identifier for the session, that comes from the underlying `Client`.
|
||||
|
||||
#### socket.rooms
|
||||
|
||||
* _(Object)_
|
||||
|
||||
A hash of strings identifying the rooms this client is in, indexed by room name.
|
||||
|
||||
#### socket.client
|
||||
|
||||
* _(Client)_
|
||||
|
||||
A reference to the underlying `Client` object.
|
||||
|
||||
#### socket.conn
|
||||
|
||||
* _(engine.Socket)_
|
||||
|
||||
A reference to the underlying `Client` transport connection (engine.io `Socket` object). This allows access to the IO transport layer, which still (mostly) abstracts the actual TCP/IP socket.
|
||||
|
||||
#### socket.request
|
||||
|
||||
* _(Request)_
|
||||
|
||||
A getter proxy that returns the reference to the `request` that originated the underlying engine.io `Client`. Useful for accessing request headers such as `Cookie` or `User-Agent`.
|
||||
|
||||
#### socket.use(fn)
|
||||
|
||||
- `fn` _(Function)_
|
||||
|
||||
Registers a middleware, which is a function that gets executed for every incoming `Packet` and receives as parameter the packet and a function to optionally defer execution to the next registered middleware.
|
||||
|
||||
Errors passed to middleware callbacks are sent as special `error` packets to clients.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.use(function(packet, next){
|
||||
if (packet.doge === true) return next();
|
||||
next(new Error('Not a doge error'));
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### socket.send([...args][, ack])
|
||||
|
||||
- `args`
|
||||
- `ack` _(Function)_
|
||||
- **Returns** `Socket`
|
||||
|
||||
Sends a `message` event. See [socket.emit(eventName[, ...args][, ack])](#socketemiteventname-args-ack).
|
||||
|
||||
#### socket.emit(eventName[, ...args][, ack])
|
||||
|
||||
*(overrides `EventEmitter.emit`)*
|
||||
- `eventName` _(String)_
|
||||
- `args`
|
||||
- `ack` _(Function)_
|
||||
- **Returns** `Socket`
|
||||
|
||||
Emits an event to the socket identified by the string name. Any other parameters can be included. All serializable datastructures are supported, including `Buffer`.
|
||||
|
||||
```js
|
||||
socket.emit('hello', 'world');
|
||||
socket.emit('with-binary', 1, '2', { 3: '4', 5: new Buffer(6) });
|
||||
```
|
||||
|
||||
The `ack` argument is optional and will be called with the client's answer.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(client){
|
||||
client.emit('an event', { some: 'data' });
|
||||
|
||||
client.emit('ferret', 'tobi', function (data) {
|
||||
console.log(data); // data will be 'woot'
|
||||
});
|
||||
|
||||
// the client code
|
||||
// client.on('ferret', function (name, fn) {
|
||||
// fn('woot');
|
||||
// });
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
#### socket.on(eventName, callback)
|
||||
|
||||
*(inherited from `EventEmitter`)*
|
||||
- `eventName` _(String)_
|
||||
- `callback` _(Function)_
|
||||
- **Returns** `Socket`
|
||||
|
||||
Register a new handler for the given event.
|
||||
|
||||
```js
|
||||
socket.on('news', function (data) {
|
||||
console.log(data);
|
||||
});
|
||||
```
|
||||
|
||||
#### socket.once(eventName, listener)
|
||||
#### socket.removeListener(eventName, listener)
|
||||
#### socket.removeAllListeners([eventName])
|
||||
#### socket.eventNames()
|
||||
|
||||
Inherited from `EventEmitter` (along with other methods not mentioned here). See Node.js documentation for the `events` module.
|
||||
|
||||
#### socket.join(room[, callback])
|
||||
|
||||
- `room` _(String)_
|
||||
- `callback` _(Function)_
|
||||
- **Returns** `Socket` for chaining
|
||||
|
||||
Adds the client to the `room`, and fires optionally a callback with `err` signature (if any).
|
||||
|
||||
```js
|
||||
io.on('connection', function(socket){
|
||||
socket.join('room 237', function(){
|
||||
console.log(socket.rooms); // [ <socket.id>, 'room 237' ]
|
||||
io.to('room 237', 'a new user has joined the room'); // broadcast to everyone in the room
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
The mechanics of joining rooms are handled by the `Adapter` that has been configured (see `Server#adapter` above), defaulting to [socket.io-adapter](https://github.com/socketio/socket.io-adapter).
|
||||
|
||||
For your convenience, each socket automatically joins a room identified by this id (see `Socket#id`). This makes it easy to broadcast messages to other sockets:
|
||||
|
||||
```js
|
||||
io.on('connection', function(client){
|
||||
client.on('say to someone', function(id, msg){
|
||||
// send a private message to the socket with the given id
|
||||
client.broadcast.to(id).emit('my message', msg);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### socket.join(rooms[, callback])
|
||||
|
||||
- `rooms` _(Array)_
|
||||
- `callback` _(Function)_
|
||||
- **Returns** `Socket` for chaining
|
||||
|
||||
Adds the client to the list of room, and fires optionally a callback with `err` signature (if any).
|
||||
|
||||
#### socket.leave(room[, callback])
|
||||
|
||||
- `room` _(String)_
|
||||
- `callback` _(Function)_
|
||||
- **Returns** `Socket` for chaining
|
||||
|
||||
Removes the client from `room`, and fires optionally a callback with `err` signature (if any).
|
||||
|
||||
**Rooms are left automatically upon disconnection**.
|
||||
|
||||
#### socket.to(room)
|
||||
|
||||
- `room` _(String)_
|
||||
- **Returns** `Socket` for chaining
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event will only be _broadcasted_ to clients that have joined the given `room`.
|
||||
|
||||
To emit to multiple rooms, you can call `to` several times.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(client){
|
||||
// to one room
|
||||
client.to('others').emit('an event', { some: 'data' });
|
||||
// to multiple rooms
|
||||
client.to('room1').to('room2').emit('hello');
|
||||
});
|
||||
```
|
||||
|
||||
#### socket.in(room)
|
||||
|
||||
Synonym of [socket.to(room)](#sockettoroom).
|
||||
|
||||
#### socket.compress(value)
|
||||
|
||||
- `value` _(Boolean)_ whether to following packet will be compressed
|
||||
- **Returns** `Socket` for chaining
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event data will only be _compressed_ if the value is `true`. Defaults to `true` when you don't call the method.
|
||||
|
||||
#### socket.disconnect(close)
|
||||
|
||||
- `close` _(Boolean)_ whether to close the underlying connection
|
||||
- **Returns** `Socket`
|
||||
|
||||
Disconnects this client. If value of close is `true`, closes the underlying connection. Otherwise, it just disconnects the namespace.
|
||||
|
||||
#### Flag: 'broadcast'
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event data will only be _broadcast_ to every sockets but the sender.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.broadcast.emit('an event', { some: 'data' }); // everyone gets it but the sender
|
||||
});
|
||||
```
|
||||
|
||||
#### Flag: 'volatile'
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to receive messages (because of network slowness or other issues, or because they’re connected through long polling and is in the middle of a request-response cycle).
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.volatile.emit('an event', { some: 'data' }); // the client may or may not receive it
|
||||
});
|
||||
```
|
||||
|
||||
#### Event: 'disconnect'
|
||||
|
||||
- `reason` _(String)_ the reason of the disconnection (either client or server-side)
|
||||
|
||||
Fired upon disconnection.
|
||||
|
||||
#### Event: 'error'
|
||||
|
||||
- `error` _(Object)_ error object
|
||||
|
||||
Fired when an error occurs.
|
||||
|
||||
#### Event: 'disconnecting'
|
||||
|
||||
- `reason` _(String)_ the reason of the disconnection (either client or server-side)
|
||||
|
||||
Fired when the client is going to be disconnected (but hasn't left its `rooms` yet).
|
||||
|
||||
These are reserved events (along with `connect`, `newListener` and `removeListener`) which cannot be used as event names.
|
||||
|
||||
### Client
|
||||
|
||||
The `Client` class represents an incoming transport (engine.io) connection. A `Client` can be associated with many multiplexed `Socket`s that belong to different `Namespace`s.
|
||||
|
||||
#### client.conn
|
||||
|
||||
* _(engine.Socket)_
|
||||
|
||||
A reference to the underlying `engine.io` `Socket` connection.
|
||||
|
||||
#### client.request
|
||||
|
||||
* _(Request)_
|
||||
|
||||
A getter proxy that returns the reference to the `request` that originated the engine.io connection. Useful for accessing request headers such as `Cookie` or `User-Agent`.
|
||||
15
docs/README.md
Normal file
15
docs/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
## Table of Contents
|
||||
|
||||
#### Getting started
|
||||
|
||||
- [Write a chat application](http://socket.io/get-started/chat/)
|
||||
|
||||
#### API Reference
|
||||
|
||||
- [Server API](API.md)
|
||||
- [Client API](https://github.com/socketio/socket.io-client/blob/master/docs/API.md)
|
||||
|
||||
#### Advanced topics
|
||||
|
||||
- [Emit cheatsheet](emit.md)
|
||||
58
docs/emit.md
Normal file
58
docs/emit.md
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
## Emit cheatsheet
|
||||
|
||||
```js
|
||||
|
||||
io.on('connect', onConnect);
|
||||
|
||||
function onConnect(socket){
|
||||
|
||||
// sending to the client
|
||||
socket.emit('hello', 'can you hear me?', 1, 2, 'abc');
|
||||
|
||||
// sending to all clients except sender
|
||||
socket.broadcast.emit('broadcast', 'hello friends!');
|
||||
|
||||
// sending to all clients in 'game' room except sender
|
||||
socket.to('game').emit('nice game', "let's play a game");
|
||||
|
||||
// sending to all clients in 'game1' and/or in 'game2' room, except sender
|
||||
socket.to('game1').to('game2').emit('nice game', "let's play a game (too)");
|
||||
|
||||
// sending to all clients in 'game' room, including sender
|
||||
io.in('game').emit('big-announcement', 'the game will start soon');
|
||||
|
||||
// sending to all clients in namespace 'myNamespace', including sender
|
||||
io.of('myNamespace').emit('bigger-announcement', 'the tournament will start soon');
|
||||
|
||||
// sending to a specific room in a specific namespace, including sender
|
||||
io.of('myNamespace').to('room').emit('event', 'message');
|
||||
|
||||
// sending to individual socketid (private message)
|
||||
socket.to(<socketid>).emit('hey', 'I just met you');
|
||||
|
||||
// sending with acknowledgement
|
||||
socket.emit('question', 'do you think so?', function (answer) {});
|
||||
|
||||
// sending without compression
|
||||
socket.compress(false).emit('uncompressed', "that's rough");
|
||||
|
||||
// sending a message that might be dropped if the client is not ready to receive messages
|
||||
socket.volatile.emit('maybe', 'do you really need it?');
|
||||
|
||||
// sending to all clients on this node (when using multiple nodes)
|
||||
io.local.emit('hi', 'my lovely babies');
|
||||
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
**Note:** The following events are reserved and should not be used as event names by your application:
|
||||
- `error`
|
||||
- `connect`
|
||||
- `disconnect`
|
||||
- `disconnecting`
|
||||
- `newListener`
|
||||
- `removeListener`
|
||||
- `ping`
|
||||
- `pong`
|
||||
@@ -10,7 +10,7 @@ $ cd socket.io
|
||||
$ npm install
|
||||
$ cd examples/chat
|
||||
$ npm install
|
||||
$ node .
|
||||
$ npm start
|
||||
```
|
||||
|
||||
And point your browser to `http://localhost:3000`. Optionally, specify
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"private": true,
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "3.4.8"
|
||||
"express": "4.13.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,4 +263,20 @@ $(function() {
|
||||
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');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -128,7 +128,6 @@ ul {
|
||||
}
|
||||
|
||||
.username {
|
||||
float: left;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
padding-right: 15px;
|
||||
|
||||
31
examples/cluster-haproxy/README.md
Normal file
31
examples/cluster-haproxy/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
# Socket.IO Chat with haproxy & redis
|
||||
|
||||
A simple chat demo for socket.io
|
||||
|
||||
## How to use
|
||||
|
||||
Install [Docker Compose](https://docs.docker.com/compose/install/), then:
|
||||
|
||||
```
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
And then point your browser to `http://localhost:3000`.
|
||||
|
||||
This will start four Socket.IO nodes, behind a haproxy instance which will loadbalance the requests (using a cookie for sticky sessions, see [cookie](https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#4.2-cookie)).
|
||||
|
||||
Each node connects to the redis backend, which will enable to broadcast to every client, no matter which node it is currently connected to.
|
||||
|
||||
```
|
||||
# you can kill a given node, the client should reconnect to another node
|
||||
$ docker-compose stop server-george
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Multiple users can join a chat room by each entering a unique username
|
||||
on website load.
|
||||
- Users can type chat messages to the chat room.
|
||||
- A notification is sent to all users when a user joins or leaves
|
||||
the chatroom.
|
||||
51
examples/cluster-haproxy/docker-compose.yml
Normal file
51
examples/cluster-haproxy/docker-compose.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
haproxy:
|
||||
build: ./haproxy
|
||||
links:
|
||||
- server-john
|
||||
- server-paul
|
||||
- server-george
|
||||
- server-ringo
|
||||
ports:
|
||||
- "3000:80"
|
||||
|
||||
server-john:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=John
|
||||
|
||||
server-paul:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=Paul
|
||||
|
||||
server-george:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=George
|
||||
|
||||
server-ringo:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=Ringo
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
expose:
|
||||
- "6379"
|
||||
2
examples/cluster-haproxy/haproxy/Dockerfile
Normal file
2
examples/cluster-haproxy/haproxy/Dockerfile
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM haproxy:1.7-alpine
|
||||
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
|
||||
31
examples/cluster-haproxy/haproxy/haproxy.cfg
Normal file
31
examples/cluster-haproxy/haproxy/haproxy.cfg
Normal file
@@ -0,0 +1,31 @@
|
||||
# Reference: http://blog.haproxy.com/2012/11/07/websockets-load-balancing-with-haproxy/
|
||||
|
||||
global
|
||||
daemon
|
||||
maxconn 4096
|
||||
nbproc 2
|
||||
|
||||
defaults
|
||||
mode http
|
||||
balance roundrobin
|
||||
option http-server-close
|
||||
timeout connect 5s
|
||||
timeout client 30s
|
||||
timeout client-fin 30s
|
||||
timeout server 30s
|
||||
timeout tunnel 1h
|
||||
default-server inter 1s rise 2 fall 1 on-marked-down shutdown-sessions
|
||||
option forwardfor
|
||||
|
||||
listen chat
|
||||
bind *:80
|
||||
default_backend nodes
|
||||
|
||||
backend nodes
|
||||
option httpchk HEAD /health
|
||||
http-check expect status 200
|
||||
cookie serverid insert
|
||||
server john server-john:3000 cookie john check
|
||||
server paul server-paul:3000 cookie paul check
|
||||
server george server-george:3000 cookie george check
|
||||
server ringo server-ringo:3000 cookie ringo check
|
||||
15
examples/cluster-haproxy/server/Dockerfile
Normal file
15
examples/cluster-haproxy/server/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM mhart/alpine-node:6
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "npm", "start" ]
|
||||
87
examples/cluster-haproxy/server/index.js
Normal file
87
examples/cluster-haproxy/server/index.js
Normal file
@@ -0,0 +1,87 @@
|
||||
// Setup basic express server
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var server = require('http').createServer(app);
|
||||
var io = require('socket.io')(server);
|
||||
var redis = require('socket.io-redis');
|
||||
var port = process.env.PORT || 3000;
|
||||
var serverName = process.env.NAME || 'Unknown';
|
||||
|
||||
io.adapter(redis({ host: 'redis', port: 6379 }));
|
||||
|
||||
server.listen(port, function () {
|
||||
console.log('Server listening at port %d', port);
|
||||
console.log('Hello, I\'m %s, how can I help?', serverName);
|
||||
});
|
||||
|
||||
// Routing
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// Health check
|
||||
app.head('/health', function (req, res) {
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
// Chatroom
|
||||
|
||||
var numUsers = 0;
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.emit('my-name-is', serverName);
|
||||
|
||||
var addedUser = false;
|
||||
|
||||
// when the client emits 'new message', this listens and executes
|
||||
socket.on('new message', function (data) {
|
||||
// we tell the client to execute 'new message'
|
||||
socket.broadcast.emit('new message', {
|
||||
username: socket.username,
|
||||
message: data
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'add user', this listens and executes
|
||||
socket.on('add user', function (username) {
|
||||
if (addedUser) return;
|
||||
|
||||
// we store the username in the socket session for this client
|
||||
socket.username = username;
|
||||
++numUsers;
|
||||
addedUser = true;
|
||||
socket.emit('login', {
|
||||
numUsers: numUsers
|
||||
});
|
||||
// echo globally (all clients) that a person has connected
|
||||
socket.broadcast.emit('user joined', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'typing', we broadcast it to others
|
||||
socket.on('typing', function () {
|
||||
socket.broadcast.emit('typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'stop typing', we broadcast it to others
|
||||
socket.on('stop typing', function () {
|
||||
socket.broadcast.emit('stop typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the user disconnects.. perform this
|
||||
socket.on('disconnect', function () {
|
||||
if (addedUser) {
|
||||
--numUsers;
|
||||
|
||||
// echo globally that this client has left
|
||||
socket.broadcast.emit('user left', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
17
examples/cluster-haproxy/server/package.json
Normal file
17
examples/cluster-haproxy/server/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "socket.io-chat",
|
||||
"version": "0.0.0",
|
||||
"description": "A simple chat client using socket.io",
|
||||
"main": "index.js",
|
||||
"author": "Grant Timmerman",
|
||||
"private": true,
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
28
examples/cluster-haproxy/server/public/index.html
Normal file
28
examples/cluster-haproxy/server/public/index.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO Chat Example</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<ul class="pages">
|
||||
<li class="chat page">
|
||||
<div class="chatArea">
|
||||
<ul class="messages"></ul>
|
||||
</div>
|
||||
<input class="inputMessage" placeholder="Type here..."/>
|
||||
</li>
|
||||
<li class="login page">
|
||||
<div class="form">
|
||||
<h3 class="title">What's your nickname?</h3>
|
||||
<input class="usernameInput" type="text" maxlength="14" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
286
examples/cluster-haproxy/server/public/main.js
Normal file
286
examples/cluster-haproxy/server/public/main.js
Normal file
@@ -0,0 +1,286 @@
|
||||
$(function() {
|
||||
var FADE_TIME = 150; // ms
|
||||
var TYPING_TIMER_LENGTH = 400; // ms
|
||||
var COLORS = [
|
||||
'#e21400', '#91580f', '#f8a700', '#f78b00',
|
||||
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
|
||||
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
|
||||
];
|
||||
|
||||
// Initialize variables
|
||||
var $window = $(window);
|
||||
var $usernameInput = $('.usernameInput'); // Input for username
|
||||
var $messages = $('.messages'); // Messages area
|
||||
var $inputMessage = $('.inputMessage'); // Input message input box
|
||||
|
||||
var $loginPage = $('.login.page'); // The login page
|
||||
var $chatPage = $('.chat.page'); // The chatroom page
|
||||
|
||||
// Prompt for setting a username
|
||||
var username;
|
||||
var connected = false;
|
||||
var typing = false;
|
||||
var lastTypingTime;
|
||||
var $currentInput = $usernameInput.focus();
|
||||
|
||||
var socket = io();
|
||||
|
||||
function addParticipantsMessage (data) {
|
||||
var message = '';
|
||||
if (data.numUsers === 1) {
|
||||
message += "there's 1 participant";
|
||||
} else {
|
||||
message += "there are " + data.numUsers + " participants";
|
||||
}
|
||||
log(message);
|
||||
}
|
||||
|
||||
// Sets the client's username
|
||||
function setUsername () {
|
||||
username = cleanInput($usernameInput.val().trim());
|
||||
|
||||
// If the username is valid
|
||||
if (username) {
|
||||
$loginPage.fadeOut();
|
||||
$chatPage.show();
|
||||
$loginPage.off('click');
|
||||
$currentInput = $inputMessage.focus();
|
||||
|
||||
// Tell the server your username
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
}
|
||||
|
||||
// Sends a chat message
|
||||
function sendMessage () {
|
||||
var message = $inputMessage.val();
|
||||
// Prevent markup from being injected into the message
|
||||
message = cleanInput(message);
|
||||
// if there is a non-empty message and a socket connection
|
||||
if (message && connected) {
|
||||
$inputMessage.val('');
|
||||
addChatMessage({
|
||||
username: username,
|
||||
message: message
|
||||
});
|
||||
// tell server to execute 'new message' and send along one parameter
|
||||
socket.emit('new message', message);
|
||||
}
|
||||
}
|
||||
|
||||
// Log a message
|
||||
function log (message, options) {
|
||||
var $el = $('<li>').addClass('log').text(message);
|
||||
addMessageElement($el, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat message to the message list
|
||||
function addChatMessage (data, options) {
|
||||
// Don't fade the message in if there is an 'X was typing'
|
||||
var $typingMessages = getTypingMessages(data);
|
||||
options = options || {};
|
||||
if ($typingMessages.length !== 0) {
|
||||
options.fade = false;
|
||||
$typingMessages.remove();
|
||||
}
|
||||
|
||||
var $usernameDiv = $('<span class="username"/>')
|
||||
.text(data.username)
|
||||
.css('color', getUsernameColor(data.username));
|
||||
var $messageBodyDiv = $('<span class="messageBody">')
|
||||
.text(data.message);
|
||||
|
||||
var typingClass = data.typing ? 'typing' : '';
|
||||
var $messageDiv = $('<li class="message"/>')
|
||||
.data('username', data.username)
|
||||
.addClass(typingClass)
|
||||
.append($usernameDiv, $messageBodyDiv);
|
||||
|
||||
addMessageElement($messageDiv, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat typing message
|
||||
function addChatTyping (data) {
|
||||
data.typing = true;
|
||||
data.message = 'is typing';
|
||||
addChatMessage(data);
|
||||
}
|
||||
|
||||
// Removes the visual chat typing message
|
||||
function removeChatTyping (data) {
|
||||
getTypingMessages(data).fadeOut(function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Adds a message element to the messages and scrolls to the bottom
|
||||
// el - The element to add as a message
|
||||
// options.fade - If the element should fade-in (default = true)
|
||||
// options.prepend - If the element should prepend
|
||||
// all other messages (default = false)
|
||||
function addMessageElement (el, options) {
|
||||
var $el = $(el);
|
||||
|
||||
// Setup default options
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (typeof options.fade === 'undefined') {
|
||||
options.fade = true;
|
||||
}
|
||||
if (typeof options.prepend === 'undefined') {
|
||||
options.prepend = false;
|
||||
}
|
||||
|
||||
// Apply options
|
||||
if (options.fade) {
|
||||
$el.hide().fadeIn(FADE_TIME);
|
||||
}
|
||||
if (options.prepend) {
|
||||
$messages.prepend($el);
|
||||
} else {
|
||||
$messages.append($el);
|
||||
}
|
||||
$messages[0].scrollTop = $messages[0].scrollHeight;
|
||||
}
|
||||
|
||||
// Prevents input from having injected markup
|
||||
function cleanInput (input) {
|
||||
return $('<div/>').text(input).text();
|
||||
}
|
||||
|
||||
// Updates the typing event
|
||||
function updateTyping () {
|
||||
if (connected) {
|
||||
if (!typing) {
|
||||
typing = true;
|
||||
socket.emit('typing');
|
||||
}
|
||||
lastTypingTime = (new Date()).getTime();
|
||||
|
||||
setTimeout(function () {
|
||||
var typingTimer = (new Date()).getTime();
|
||||
var timeDiff = typingTimer - lastTypingTime;
|
||||
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
}
|
||||
}, TYPING_TIMER_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the 'X is typing' messages of a user
|
||||
function getTypingMessages (data) {
|
||||
return $('.typing.message').filter(function (i) {
|
||||
return $(this).data('username') === data.username;
|
||||
});
|
||||
}
|
||||
|
||||
// Gets the color of a username through our hash function
|
||||
function getUsernameColor (username) {
|
||||
// Compute hash code
|
||||
var hash = 7;
|
||||
for (var i = 0; i < username.length; i++) {
|
||||
hash = username.charCodeAt(i) + (hash << 5) - hash;
|
||||
}
|
||||
// Calculate color
|
||||
var index = Math.abs(hash % COLORS.length);
|
||||
return COLORS[index];
|
||||
}
|
||||
|
||||
// Keyboard events
|
||||
|
||||
$window.keydown(function (event) {
|
||||
// Auto-focus the current input when a key is typed
|
||||
if (!(event.ctrlKey || event.metaKey || event.altKey)) {
|
||||
$currentInput.focus();
|
||||
}
|
||||
// When the client hits ENTER on their keyboard
|
||||
if (event.which === 13) {
|
||||
if (username) {
|
||||
sendMessage();
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
} else {
|
||||
setUsername();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$inputMessage.on('input', function() {
|
||||
updateTyping();
|
||||
});
|
||||
|
||||
// Click events
|
||||
|
||||
// Focus input when clicking anywhere on login page
|
||||
$loginPage.click(function () {
|
||||
$currentInput.focus();
|
||||
});
|
||||
|
||||
// Focus input when clicking on the message input's border
|
||||
$inputMessage.click(function () {
|
||||
$inputMessage.focus();
|
||||
});
|
||||
|
||||
// Socket events
|
||||
|
||||
// Whenever the server emits 'login', log the login message
|
||||
socket.on('login', function (data) {
|
||||
connected = true;
|
||||
// Display the welcome message
|
||||
var message = "Welcome to Socket.IO Chat – ";
|
||||
log(message, {
|
||||
prepend: true
|
||||
});
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'new message', update the chat body
|
||||
socket.on('new message', function (data) {
|
||||
addChatMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user joined', log it in the chat body
|
||||
socket.on('user joined', function (data) {
|
||||
log(data.username + ' joined');
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user left', log it in the chat body
|
||||
socket.on('user left', function (data) {
|
||||
log(data.username + ' left');
|
||||
addParticipantsMessage(data);
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'typing', show the typing message
|
||||
socket.on('typing', function (data) {
|
||||
addChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'stop typing', kill the typing message
|
||||
socket.on('stop typing', function (data) {
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
log('you have been disconnected');
|
||||
});
|
||||
|
||||
socket.on('reconnect', function () {
|
||||
log('you have been reconnected');
|
||||
if (username) {
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('reconnect_error', function () {
|
||||
log('attempt to reconnect has failed');
|
||||
});
|
||||
|
||||
socket.on('my-name-is', function (serverName) {
|
||||
log('host is now ' + serverName);
|
||||
})
|
||||
|
||||
});
|
||||
149
examples/cluster-haproxy/server/public/style.css
Normal file
149
examples/cluster-haproxy/server/public/style.css
Normal file
@@ -0,0 +1,149 @@
|
||||
/* Fix user-agent */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-weight: 300;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
html, input {
|
||||
font-family:
|
||||
"HelveticaNeue-Light",
|
||||
"Helvetica Neue Light",
|
||||
"Helvetica Neue",
|
||||
Helvetica,
|
||||
Arial,
|
||||
"Lucida Grande",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Pages */
|
||||
|
||||
.pages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Login Page */
|
||||
|
||||
.login.page {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.login.page .form {
|
||||
height: 100px;
|
||||
margin-top: -100px;
|
||||
position: absolute;
|
||||
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login.page .form .usernameInput {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #fff;
|
||||
outline: none;
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.login.page .title {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
.login.page .usernameInput {
|
||||
font-size: 200%;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.login.page .title, .login.page .usernameInput {
|
||||
color: #fff;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* Chat page */
|
||||
|
||||
.chat.page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
.messages {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.inputMessage {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: gray;
|
||||
font-size: 70%;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
|
||||
.chatArea {
|
||||
height: 100%;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-y: scroll;
|
||||
padding: 10px 20px 10px 20px;
|
||||
}
|
||||
|
||||
.message.typing .messageBody {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
padding-right: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
|
||||
.inputMessage {
|
||||
border: 10px solid #000;
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
left: 0;
|
||||
outline: none;
|
||||
padding-left: 10px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
31
examples/cluster-httpd/README.md
Normal file
31
examples/cluster-httpd/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
# Socket.IO Chat with httpd & redis
|
||||
|
||||
A simple chat demo for socket.io
|
||||
|
||||
## How to use
|
||||
|
||||
Install [Docker Compose](https://docs.docker.com/compose/install/), then:
|
||||
|
||||
```
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
And then point your browser to `http://localhost:3000`.
|
||||
|
||||
This will start four Socket.IO nodes, behind a httpd proxy which will loadbalance the requests (using a cookie for sticky sessions, see [cookie](http://httpd.apache.org/docs/2.4/fr/mod/mod_proxy_balancer.html)).
|
||||
|
||||
Each node connects to the redis backend, which will enable to broadcast to every client, no matter which node it is currently connected to.
|
||||
|
||||
```
|
||||
# you can kill a given node, the client should reconnect to another node
|
||||
$ docker-compose stop server-george
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Multiple users can join a chat room by each entering a unique username
|
||||
on website load.
|
||||
- Users can type chat messages to the chat room.
|
||||
- A notification is sent to all users when a user joins or leaves
|
||||
the chatroom.
|
||||
51
examples/cluster-httpd/docker-compose.yml
Normal file
51
examples/cluster-httpd/docker-compose.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
httpd:
|
||||
build: ./httpd
|
||||
links:
|
||||
- server-john
|
||||
- server-paul
|
||||
- server-george
|
||||
- server-ringo
|
||||
ports:
|
||||
- "3000:80"
|
||||
|
||||
server-john:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=John
|
||||
|
||||
server-paul:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=Paul
|
||||
|
||||
server-george:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=George
|
||||
|
||||
server-ringo:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=Ringo
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
expose:
|
||||
- "6379"
|
||||
2
examples/cluster-httpd/httpd/Dockerfile
Normal file
2
examples/cluster-httpd/httpd/Dockerfile
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM httpd:2.4-alpine
|
||||
COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf
|
||||
52
examples/cluster-httpd/httpd/httpd.conf
Normal file
52
examples/cluster-httpd/httpd/httpd.conf
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
Listen 80
|
||||
|
||||
ServerName localhost
|
||||
|
||||
LoadModule authn_file_module modules/mod_authn_file.so
|
||||
LoadModule authn_core_module modules/mod_authn_core.so
|
||||
LoadModule authz_host_module modules/mod_authz_host.so
|
||||
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
|
||||
LoadModule authz_user_module modules/mod_authz_user.so
|
||||
LoadModule authz_core_module modules/mod_authz_core.so
|
||||
|
||||
LoadModule headers_module modules/mod_headers.so
|
||||
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
|
||||
LoadModule proxy_module modules/mod_proxy.so
|
||||
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
|
||||
LoadModule proxy_http_module modules/mod_proxy_http.so
|
||||
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
|
||||
LoadModule unixd_module modules/mod_unixd.so
|
||||
|
||||
User daemon
|
||||
Group daemon
|
||||
|
||||
ErrorLog /proc/self/fd/2
|
||||
|
||||
Header add Set-Cookie "SERVERID=sticky.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
|
||||
|
||||
<Proxy "balancer://nodes_polling">
|
||||
BalancerMember "http://server-john:3000" route=john
|
||||
BalancerMember "http://server-paul:3000" route=paul
|
||||
BalancerMember "http://server-george:3000" route=george
|
||||
BalancerMember "http://server-ringo:3000" route=ringo
|
||||
ProxySet stickysession=SERVERID
|
||||
</Proxy>
|
||||
|
||||
<Proxy "balancer://nodes_ws">
|
||||
BalancerMember "ws://server-john:3000" route=john
|
||||
BalancerMember "ws://server-paul:3000" route=paul
|
||||
BalancerMember "ws://server-george:3000" route=george
|
||||
BalancerMember "ws://server-ringo:3000" route=ringo
|
||||
ProxySet stickysession=SERVERID
|
||||
</Proxy>
|
||||
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:Upgrade} =websocket [NC]
|
||||
RewriteRule /(.*) balancer://nodes_ws/$1 [P,L]
|
||||
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
|
||||
RewriteRule /(.*) balancer://nodes_polling/$1 [P,L]
|
||||
|
||||
ProxyTimeout 3
|
||||
15
examples/cluster-httpd/server/Dockerfile
Normal file
15
examples/cluster-httpd/server/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM mhart/alpine-node:6
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "npm", "start" ]
|
||||
82
examples/cluster-httpd/server/index.js
Normal file
82
examples/cluster-httpd/server/index.js
Normal file
@@ -0,0 +1,82 @@
|
||||
// Setup basic express server
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var server = require('http').createServer(app);
|
||||
var io = require('socket.io')(server);
|
||||
var redis = require('socket.io-redis');
|
||||
var port = process.env.PORT || 3000;
|
||||
var serverName = process.env.NAME || 'Unknown';
|
||||
|
||||
io.adapter(redis({ host: 'redis', port: 6379 }));
|
||||
|
||||
server.listen(port, function () {
|
||||
console.log('Server listening at port %d', port);
|
||||
console.log('Hello, I\'m %s, how can I help?', serverName);
|
||||
});
|
||||
|
||||
// Routing
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// Chatroom
|
||||
|
||||
var numUsers = 0;
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.emit('my-name-is', serverName);
|
||||
|
||||
var addedUser = false;
|
||||
|
||||
// when the client emits 'new message', this listens and executes
|
||||
socket.on('new message', function (data) {
|
||||
// we tell the client to execute 'new message'
|
||||
socket.broadcast.emit('new message', {
|
||||
username: socket.username,
|
||||
message: data
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'add user', this listens and executes
|
||||
socket.on('add user', function (username) {
|
||||
if (addedUser) return;
|
||||
|
||||
// we store the username in the socket session for this client
|
||||
socket.username = username;
|
||||
++numUsers;
|
||||
addedUser = true;
|
||||
socket.emit('login', {
|
||||
numUsers: numUsers
|
||||
});
|
||||
// echo globally (all clients) that a person has connected
|
||||
socket.broadcast.emit('user joined', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'typing', we broadcast it to others
|
||||
socket.on('typing', function () {
|
||||
socket.broadcast.emit('typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'stop typing', we broadcast it to others
|
||||
socket.on('stop typing', function () {
|
||||
socket.broadcast.emit('stop typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the user disconnects.. perform this
|
||||
socket.on('disconnect', function () {
|
||||
if (addedUser) {
|
||||
--numUsers;
|
||||
|
||||
// echo globally that this client has left
|
||||
socket.broadcast.emit('user left', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
17
examples/cluster-httpd/server/package.json
Normal file
17
examples/cluster-httpd/server/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "socket.io-chat",
|
||||
"version": "0.0.0",
|
||||
"description": "A simple chat client using socket.io",
|
||||
"main": "index.js",
|
||||
"author": "Grant Timmerman",
|
||||
"private": true,
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
28
examples/cluster-httpd/server/public/index.html
Normal file
28
examples/cluster-httpd/server/public/index.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO Chat Example</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<ul class="pages">
|
||||
<li class="chat page">
|
||||
<div class="chatArea">
|
||||
<ul class="messages"></ul>
|
||||
</div>
|
||||
<input class="inputMessage" placeholder="Type here..."/>
|
||||
</li>
|
||||
<li class="login page">
|
||||
<div class="form">
|
||||
<h3 class="title">What's your nickname?</h3>
|
||||
<input class="usernameInput" type="text" maxlength="14" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
286
examples/cluster-httpd/server/public/main.js
Normal file
286
examples/cluster-httpd/server/public/main.js
Normal file
@@ -0,0 +1,286 @@
|
||||
$(function() {
|
||||
var FADE_TIME = 150; // ms
|
||||
var TYPING_TIMER_LENGTH = 400; // ms
|
||||
var COLORS = [
|
||||
'#e21400', '#91580f', '#f8a700', '#f78b00',
|
||||
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
|
||||
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
|
||||
];
|
||||
|
||||
// Initialize variables
|
||||
var $window = $(window);
|
||||
var $usernameInput = $('.usernameInput'); // Input for username
|
||||
var $messages = $('.messages'); // Messages area
|
||||
var $inputMessage = $('.inputMessage'); // Input message input box
|
||||
|
||||
var $loginPage = $('.login.page'); // The login page
|
||||
var $chatPage = $('.chat.page'); // The chatroom page
|
||||
|
||||
// Prompt for setting a username
|
||||
var username;
|
||||
var connected = false;
|
||||
var typing = false;
|
||||
var lastTypingTime;
|
||||
var $currentInput = $usernameInput.focus();
|
||||
|
||||
var socket = io();
|
||||
|
||||
function addParticipantsMessage (data) {
|
||||
var message = '';
|
||||
if (data.numUsers === 1) {
|
||||
message += "there's 1 participant";
|
||||
} else {
|
||||
message += "there are " + data.numUsers + " participants";
|
||||
}
|
||||
log(message);
|
||||
}
|
||||
|
||||
// Sets the client's username
|
||||
function setUsername () {
|
||||
username = cleanInput($usernameInput.val().trim());
|
||||
|
||||
// If the username is valid
|
||||
if (username) {
|
||||
$loginPage.fadeOut();
|
||||
$chatPage.show();
|
||||
$loginPage.off('click');
|
||||
$currentInput = $inputMessage.focus();
|
||||
|
||||
// Tell the server your username
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
}
|
||||
|
||||
// Sends a chat message
|
||||
function sendMessage () {
|
||||
var message = $inputMessage.val();
|
||||
// Prevent markup from being injected into the message
|
||||
message = cleanInput(message);
|
||||
// if there is a non-empty message and a socket connection
|
||||
if (message && connected) {
|
||||
$inputMessage.val('');
|
||||
addChatMessage({
|
||||
username: username,
|
||||
message: message
|
||||
});
|
||||
// tell server to execute 'new message' and send along one parameter
|
||||
socket.emit('new message', message);
|
||||
}
|
||||
}
|
||||
|
||||
// Log a message
|
||||
function log (message, options) {
|
||||
var $el = $('<li>').addClass('log').text(message);
|
||||
addMessageElement($el, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat message to the message list
|
||||
function addChatMessage (data, options) {
|
||||
// Don't fade the message in if there is an 'X was typing'
|
||||
var $typingMessages = getTypingMessages(data);
|
||||
options = options || {};
|
||||
if ($typingMessages.length !== 0) {
|
||||
options.fade = false;
|
||||
$typingMessages.remove();
|
||||
}
|
||||
|
||||
var $usernameDiv = $('<span class="username"/>')
|
||||
.text(data.username)
|
||||
.css('color', getUsernameColor(data.username));
|
||||
var $messageBodyDiv = $('<span class="messageBody">')
|
||||
.text(data.message);
|
||||
|
||||
var typingClass = data.typing ? 'typing' : '';
|
||||
var $messageDiv = $('<li class="message"/>')
|
||||
.data('username', data.username)
|
||||
.addClass(typingClass)
|
||||
.append($usernameDiv, $messageBodyDiv);
|
||||
|
||||
addMessageElement($messageDiv, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat typing message
|
||||
function addChatTyping (data) {
|
||||
data.typing = true;
|
||||
data.message = 'is typing';
|
||||
addChatMessage(data);
|
||||
}
|
||||
|
||||
// Removes the visual chat typing message
|
||||
function removeChatTyping (data) {
|
||||
getTypingMessages(data).fadeOut(function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Adds a message element to the messages and scrolls to the bottom
|
||||
// el - The element to add as a message
|
||||
// options.fade - If the element should fade-in (default = true)
|
||||
// options.prepend - If the element should prepend
|
||||
// all other messages (default = false)
|
||||
function addMessageElement (el, options) {
|
||||
var $el = $(el);
|
||||
|
||||
// Setup default options
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (typeof options.fade === 'undefined') {
|
||||
options.fade = true;
|
||||
}
|
||||
if (typeof options.prepend === 'undefined') {
|
||||
options.prepend = false;
|
||||
}
|
||||
|
||||
// Apply options
|
||||
if (options.fade) {
|
||||
$el.hide().fadeIn(FADE_TIME);
|
||||
}
|
||||
if (options.prepend) {
|
||||
$messages.prepend($el);
|
||||
} else {
|
||||
$messages.append($el);
|
||||
}
|
||||
$messages[0].scrollTop = $messages[0].scrollHeight;
|
||||
}
|
||||
|
||||
// Prevents input from having injected markup
|
||||
function cleanInput (input) {
|
||||
return $('<div/>').text(input).text();
|
||||
}
|
||||
|
||||
// Updates the typing event
|
||||
function updateTyping () {
|
||||
if (connected) {
|
||||
if (!typing) {
|
||||
typing = true;
|
||||
socket.emit('typing');
|
||||
}
|
||||
lastTypingTime = (new Date()).getTime();
|
||||
|
||||
setTimeout(function () {
|
||||
var typingTimer = (new Date()).getTime();
|
||||
var timeDiff = typingTimer - lastTypingTime;
|
||||
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
}
|
||||
}, TYPING_TIMER_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the 'X is typing' messages of a user
|
||||
function getTypingMessages (data) {
|
||||
return $('.typing.message').filter(function (i) {
|
||||
return $(this).data('username') === data.username;
|
||||
});
|
||||
}
|
||||
|
||||
// Gets the color of a username through our hash function
|
||||
function getUsernameColor (username) {
|
||||
// Compute hash code
|
||||
var hash = 7;
|
||||
for (var i = 0; i < username.length; i++) {
|
||||
hash = username.charCodeAt(i) + (hash << 5) - hash;
|
||||
}
|
||||
// Calculate color
|
||||
var index = Math.abs(hash % COLORS.length);
|
||||
return COLORS[index];
|
||||
}
|
||||
|
||||
// Keyboard events
|
||||
|
||||
$window.keydown(function (event) {
|
||||
// Auto-focus the current input when a key is typed
|
||||
if (!(event.ctrlKey || event.metaKey || event.altKey)) {
|
||||
$currentInput.focus();
|
||||
}
|
||||
// When the client hits ENTER on their keyboard
|
||||
if (event.which === 13) {
|
||||
if (username) {
|
||||
sendMessage();
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
} else {
|
||||
setUsername();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$inputMessage.on('input', function() {
|
||||
updateTyping();
|
||||
});
|
||||
|
||||
// Click events
|
||||
|
||||
// Focus input when clicking anywhere on login page
|
||||
$loginPage.click(function () {
|
||||
$currentInput.focus();
|
||||
});
|
||||
|
||||
// Focus input when clicking on the message input's border
|
||||
$inputMessage.click(function () {
|
||||
$inputMessage.focus();
|
||||
});
|
||||
|
||||
// Socket events
|
||||
|
||||
// Whenever the server emits 'login', log the login message
|
||||
socket.on('login', function (data) {
|
||||
connected = true;
|
||||
// Display the welcome message
|
||||
var message = "Welcome to Socket.IO Chat – ";
|
||||
log(message, {
|
||||
prepend: true
|
||||
});
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'new message', update the chat body
|
||||
socket.on('new message', function (data) {
|
||||
addChatMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user joined', log it in the chat body
|
||||
socket.on('user joined', function (data) {
|
||||
log(data.username + ' joined');
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user left', log it in the chat body
|
||||
socket.on('user left', function (data) {
|
||||
log(data.username + ' left');
|
||||
addParticipantsMessage(data);
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'typing', show the typing message
|
||||
socket.on('typing', function (data) {
|
||||
addChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'stop typing', kill the typing message
|
||||
socket.on('stop typing', function (data) {
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
log('you have been disconnected');
|
||||
});
|
||||
|
||||
socket.on('reconnect', function () {
|
||||
log('you have been reconnected');
|
||||
if (username) {
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('reconnect_error', function () {
|
||||
log('attempt to reconnect has failed');
|
||||
});
|
||||
|
||||
socket.on('my-name-is', function (serverName) {
|
||||
log('host is now ' + serverName);
|
||||
})
|
||||
|
||||
});
|
||||
149
examples/cluster-httpd/server/public/style.css
Normal file
149
examples/cluster-httpd/server/public/style.css
Normal file
@@ -0,0 +1,149 @@
|
||||
/* Fix user-agent */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-weight: 300;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
html, input {
|
||||
font-family:
|
||||
"HelveticaNeue-Light",
|
||||
"Helvetica Neue Light",
|
||||
"Helvetica Neue",
|
||||
Helvetica,
|
||||
Arial,
|
||||
"Lucida Grande",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Pages */
|
||||
|
||||
.pages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Login Page */
|
||||
|
||||
.login.page {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.login.page .form {
|
||||
height: 100px;
|
||||
margin-top: -100px;
|
||||
position: absolute;
|
||||
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login.page .form .usernameInput {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #fff;
|
||||
outline: none;
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.login.page .title {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
.login.page .usernameInput {
|
||||
font-size: 200%;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.login.page .title, .login.page .usernameInput {
|
||||
color: #fff;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* Chat page */
|
||||
|
||||
.chat.page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
.messages {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.inputMessage {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: gray;
|
||||
font-size: 70%;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
|
||||
.chatArea {
|
||||
height: 100%;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-y: scroll;
|
||||
padding: 10px 20px 10px 20px;
|
||||
}
|
||||
|
||||
.message.typing .messageBody {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
padding-right: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
|
||||
.inputMessage {
|
||||
border: 10px solid #000;
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
left: 0;
|
||||
outline: none;
|
||||
padding-left: 10px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
31
examples/cluster-nginx/README.md
Normal file
31
examples/cluster-nginx/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
# Socket.IO Chat with nginx & redis
|
||||
|
||||
A simple chat demo for socket.io
|
||||
|
||||
## How to use
|
||||
|
||||
Install [Docker Compose](https://docs.docker.com/compose/install/), then:
|
||||
|
||||
```
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
And then point your browser to `http://localhost:3000`.
|
||||
|
||||
This will start four Socket.IO nodes, behind a nginx proxy which will loadbalance the requests (using the IP of the client, see [ip_hash](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#ip_hash)).
|
||||
|
||||
Each node connects to the redis backend, which will enable to broadcast to every client, no matter which node it is currently connected to.
|
||||
|
||||
```
|
||||
# you can kill a given node, the client should reconnect to another node
|
||||
$ docker-compose stop server-george
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Multiple users can join a chat room by each entering a unique username
|
||||
on website load.
|
||||
- Users can type chat messages to the chat room.
|
||||
- A notification is sent to all users when a user joins or leaves
|
||||
the chatroom.
|
||||
51
examples/cluster-nginx/docker-compose.yml
Normal file
51
examples/cluster-nginx/docker-compose.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
nginx:
|
||||
build: ./nginx
|
||||
links:
|
||||
- server-john
|
||||
- server-paul
|
||||
- server-george
|
||||
- server-ringo
|
||||
ports:
|
||||
- "3000:80"
|
||||
|
||||
server-john:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=John
|
||||
|
||||
server-paul:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=Paul
|
||||
|
||||
server-george:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=George
|
||||
|
||||
server-ringo:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NAME=Ringo
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
expose:
|
||||
- "6379"
|
||||
3
examples/cluster-nginx/nginx/Dockerfile
Normal file
3
examples/cluster-nginx/nginx/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
35
examples/cluster-nginx/nginx/nginx.conf
Normal file
35
examples/cluster-nginx/nginx/nginx.conf
Normal file
@@ -0,0 +1,35 @@
|
||||
# Reference: https://www.nginx.com/resources/wiki/start/topics/examples/full/
|
||||
|
||||
worker_processes 4;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
proxy_pass http://nodes;
|
||||
|
||||
# enable WebSockets
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
|
||||
upstream nodes {
|
||||
# enable sticky session
|
||||
ip_hash;
|
||||
|
||||
server server-john:3000;
|
||||
server server-paul:3000;
|
||||
server server-george:3000;
|
||||
server server-ringo:3000;
|
||||
}
|
||||
}
|
||||
15
examples/cluster-nginx/server/Dockerfile
Normal file
15
examples/cluster-nginx/server/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM mhart/alpine-node:6
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "npm", "start" ]
|
||||
82
examples/cluster-nginx/server/index.js
Normal file
82
examples/cluster-nginx/server/index.js
Normal file
@@ -0,0 +1,82 @@
|
||||
// Setup basic express server
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var server = require('http').createServer(app);
|
||||
var io = require('socket.io')(server);
|
||||
var redis = require('socket.io-redis');
|
||||
var port = process.env.PORT || 3000;
|
||||
var serverName = process.env.NAME || 'Unknown';
|
||||
|
||||
io.adapter(redis({ host: 'redis', port: 6379 }));
|
||||
|
||||
server.listen(port, function () {
|
||||
console.log('Server listening at port %d', port);
|
||||
console.log('Hello, I\'m %s, how can I help?', serverName);
|
||||
});
|
||||
|
||||
// Routing
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// Chatroom
|
||||
|
||||
var numUsers = 0;
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.emit('my-name-is', serverName);
|
||||
|
||||
var addedUser = false;
|
||||
|
||||
// when the client emits 'new message', this listens and executes
|
||||
socket.on('new message', function (data) {
|
||||
// we tell the client to execute 'new message'
|
||||
socket.broadcast.emit('new message', {
|
||||
username: socket.username,
|
||||
message: data
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'add user', this listens and executes
|
||||
socket.on('add user', function (username) {
|
||||
if (addedUser) return;
|
||||
|
||||
// we store the username in the socket session for this client
|
||||
socket.username = username;
|
||||
++numUsers;
|
||||
addedUser = true;
|
||||
socket.emit('login', {
|
||||
numUsers: numUsers
|
||||
});
|
||||
// echo globally (all clients) that a person has connected
|
||||
socket.broadcast.emit('user joined', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'typing', we broadcast it to others
|
||||
socket.on('typing', function () {
|
||||
socket.broadcast.emit('typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'stop typing', we broadcast it to others
|
||||
socket.on('stop typing', function () {
|
||||
socket.broadcast.emit('stop typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the user disconnects.. perform this
|
||||
socket.on('disconnect', function () {
|
||||
if (addedUser) {
|
||||
--numUsers;
|
||||
|
||||
// echo globally that this client has left
|
||||
socket.broadcast.emit('user left', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
17
examples/cluster-nginx/server/package.json
Normal file
17
examples/cluster-nginx/server/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "socket.io-chat",
|
||||
"version": "0.0.0",
|
||||
"description": "A simple chat client using socket.io",
|
||||
"main": "index.js",
|
||||
"author": "Grant Timmerman",
|
||||
"private": true,
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
28
examples/cluster-nginx/server/public/index.html
Normal file
28
examples/cluster-nginx/server/public/index.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO Chat Example</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<ul class="pages">
|
||||
<li class="chat page">
|
||||
<div class="chatArea">
|
||||
<ul class="messages"></ul>
|
||||
</div>
|
||||
<input class="inputMessage" placeholder="Type here..."/>
|
||||
</li>
|
||||
<li class="login page">
|
||||
<div class="form">
|
||||
<h3 class="title">What's your nickname?</h3>
|
||||
<input class="usernameInput" type="text" maxlength="14" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
286
examples/cluster-nginx/server/public/main.js
Normal file
286
examples/cluster-nginx/server/public/main.js
Normal file
@@ -0,0 +1,286 @@
|
||||
$(function() {
|
||||
var FADE_TIME = 150; // ms
|
||||
var TYPING_TIMER_LENGTH = 400; // ms
|
||||
var COLORS = [
|
||||
'#e21400', '#91580f', '#f8a700', '#f78b00',
|
||||
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
|
||||
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
|
||||
];
|
||||
|
||||
// Initialize variables
|
||||
var $window = $(window);
|
||||
var $usernameInput = $('.usernameInput'); // Input for username
|
||||
var $messages = $('.messages'); // Messages area
|
||||
var $inputMessage = $('.inputMessage'); // Input message input box
|
||||
|
||||
var $loginPage = $('.login.page'); // The login page
|
||||
var $chatPage = $('.chat.page'); // The chatroom page
|
||||
|
||||
// Prompt for setting a username
|
||||
var username;
|
||||
var connected = false;
|
||||
var typing = false;
|
||||
var lastTypingTime;
|
||||
var $currentInput = $usernameInput.focus();
|
||||
|
||||
var socket = io();
|
||||
|
||||
function addParticipantsMessage (data) {
|
||||
var message = '';
|
||||
if (data.numUsers === 1) {
|
||||
message += "there's 1 participant";
|
||||
} else {
|
||||
message += "there are " + data.numUsers + " participants";
|
||||
}
|
||||
log(message);
|
||||
}
|
||||
|
||||
// Sets the client's username
|
||||
function setUsername () {
|
||||
username = cleanInput($usernameInput.val().trim());
|
||||
|
||||
// If the username is valid
|
||||
if (username) {
|
||||
$loginPage.fadeOut();
|
||||
$chatPage.show();
|
||||
$loginPage.off('click');
|
||||
$currentInput = $inputMessage.focus();
|
||||
|
||||
// Tell the server your username
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
}
|
||||
|
||||
// Sends a chat message
|
||||
function sendMessage () {
|
||||
var message = $inputMessage.val();
|
||||
// Prevent markup from being injected into the message
|
||||
message = cleanInput(message);
|
||||
// if there is a non-empty message and a socket connection
|
||||
if (message && connected) {
|
||||
$inputMessage.val('');
|
||||
addChatMessage({
|
||||
username: username,
|
||||
message: message
|
||||
});
|
||||
// tell server to execute 'new message' and send along one parameter
|
||||
socket.emit('new message', message);
|
||||
}
|
||||
}
|
||||
|
||||
// Log a message
|
||||
function log (message, options) {
|
||||
var $el = $('<li>').addClass('log').text(message);
|
||||
addMessageElement($el, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat message to the message list
|
||||
function addChatMessage (data, options) {
|
||||
// Don't fade the message in if there is an 'X was typing'
|
||||
var $typingMessages = getTypingMessages(data);
|
||||
options = options || {};
|
||||
if ($typingMessages.length !== 0) {
|
||||
options.fade = false;
|
||||
$typingMessages.remove();
|
||||
}
|
||||
|
||||
var $usernameDiv = $('<span class="username"/>')
|
||||
.text(data.username)
|
||||
.css('color', getUsernameColor(data.username));
|
||||
var $messageBodyDiv = $('<span class="messageBody">')
|
||||
.text(data.message);
|
||||
|
||||
var typingClass = data.typing ? 'typing' : '';
|
||||
var $messageDiv = $('<li class="message"/>')
|
||||
.data('username', data.username)
|
||||
.addClass(typingClass)
|
||||
.append($usernameDiv, $messageBodyDiv);
|
||||
|
||||
addMessageElement($messageDiv, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat typing message
|
||||
function addChatTyping (data) {
|
||||
data.typing = true;
|
||||
data.message = 'is typing';
|
||||
addChatMessage(data);
|
||||
}
|
||||
|
||||
// Removes the visual chat typing message
|
||||
function removeChatTyping (data) {
|
||||
getTypingMessages(data).fadeOut(function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Adds a message element to the messages and scrolls to the bottom
|
||||
// el - The element to add as a message
|
||||
// options.fade - If the element should fade-in (default = true)
|
||||
// options.prepend - If the element should prepend
|
||||
// all other messages (default = false)
|
||||
function addMessageElement (el, options) {
|
||||
var $el = $(el);
|
||||
|
||||
// Setup default options
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (typeof options.fade === 'undefined') {
|
||||
options.fade = true;
|
||||
}
|
||||
if (typeof options.prepend === 'undefined') {
|
||||
options.prepend = false;
|
||||
}
|
||||
|
||||
// Apply options
|
||||
if (options.fade) {
|
||||
$el.hide().fadeIn(FADE_TIME);
|
||||
}
|
||||
if (options.prepend) {
|
||||
$messages.prepend($el);
|
||||
} else {
|
||||
$messages.append($el);
|
||||
}
|
||||
$messages[0].scrollTop = $messages[0].scrollHeight;
|
||||
}
|
||||
|
||||
// Prevents input from having injected markup
|
||||
function cleanInput (input) {
|
||||
return $('<div/>').text(input).text();
|
||||
}
|
||||
|
||||
// Updates the typing event
|
||||
function updateTyping () {
|
||||
if (connected) {
|
||||
if (!typing) {
|
||||
typing = true;
|
||||
socket.emit('typing');
|
||||
}
|
||||
lastTypingTime = (new Date()).getTime();
|
||||
|
||||
setTimeout(function () {
|
||||
var typingTimer = (new Date()).getTime();
|
||||
var timeDiff = typingTimer - lastTypingTime;
|
||||
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
}
|
||||
}, TYPING_TIMER_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the 'X is typing' messages of a user
|
||||
function getTypingMessages (data) {
|
||||
return $('.typing.message').filter(function (i) {
|
||||
return $(this).data('username') === data.username;
|
||||
});
|
||||
}
|
||||
|
||||
// Gets the color of a username through our hash function
|
||||
function getUsernameColor (username) {
|
||||
// Compute hash code
|
||||
var hash = 7;
|
||||
for (var i = 0; i < username.length; i++) {
|
||||
hash = username.charCodeAt(i) + (hash << 5) - hash;
|
||||
}
|
||||
// Calculate color
|
||||
var index = Math.abs(hash % COLORS.length);
|
||||
return COLORS[index];
|
||||
}
|
||||
|
||||
// Keyboard events
|
||||
|
||||
$window.keydown(function (event) {
|
||||
// Auto-focus the current input when a key is typed
|
||||
if (!(event.ctrlKey || event.metaKey || event.altKey)) {
|
||||
$currentInput.focus();
|
||||
}
|
||||
// When the client hits ENTER on their keyboard
|
||||
if (event.which === 13) {
|
||||
if (username) {
|
||||
sendMessage();
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
} else {
|
||||
setUsername();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$inputMessage.on('input', function() {
|
||||
updateTyping();
|
||||
});
|
||||
|
||||
// Click events
|
||||
|
||||
// Focus input when clicking anywhere on login page
|
||||
$loginPage.click(function () {
|
||||
$currentInput.focus();
|
||||
});
|
||||
|
||||
// Focus input when clicking on the message input's border
|
||||
$inputMessage.click(function () {
|
||||
$inputMessage.focus();
|
||||
});
|
||||
|
||||
// Socket events
|
||||
|
||||
// Whenever the server emits 'login', log the login message
|
||||
socket.on('login', function (data) {
|
||||
connected = true;
|
||||
// Display the welcome message
|
||||
var message = "Welcome to Socket.IO Chat – ";
|
||||
log(message, {
|
||||
prepend: true
|
||||
});
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'new message', update the chat body
|
||||
socket.on('new message', function (data) {
|
||||
addChatMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user joined', log it in the chat body
|
||||
socket.on('user joined', function (data) {
|
||||
log(data.username + ' joined');
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user left', log it in the chat body
|
||||
socket.on('user left', function (data) {
|
||||
log(data.username + ' left');
|
||||
addParticipantsMessage(data);
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'typing', show the typing message
|
||||
socket.on('typing', function (data) {
|
||||
addChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'stop typing', kill the typing message
|
||||
socket.on('stop typing', function (data) {
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
log('you have been disconnected');
|
||||
});
|
||||
|
||||
socket.on('reconnect', function () {
|
||||
log('you have been reconnected');
|
||||
if (username) {
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('reconnect_error', function () {
|
||||
log('attempt to reconnect has failed');
|
||||
});
|
||||
|
||||
socket.on('my-name-is', function (serverName) {
|
||||
log('host is now ' + serverName);
|
||||
})
|
||||
|
||||
});
|
||||
149
examples/cluster-nginx/server/public/style.css
Normal file
149
examples/cluster-nginx/server/public/style.css
Normal file
@@ -0,0 +1,149 @@
|
||||
/* Fix user-agent */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-weight: 300;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
html, input {
|
||||
font-family:
|
||||
"HelveticaNeue-Light",
|
||||
"Helvetica Neue Light",
|
||||
"Helvetica Neue",
|
||||
Helvetica,
|
||||
Arial,
|
||||
"Lucida Grande",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Pages */
|
||||
|
||||
.pages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Login Page */
|
||||
|
||||
.login.page {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.login.page .form {
|
||||
height: 100px;
|
||||
margin-top: -100px;
|
||||
position: absolute;
|
||||
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login.page .form .usernameInput {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #fff;
|
||||
outline: none;
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.login.page .title {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
.login.page .usernameInput {
|
||||
font-size: 200%;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.login.page .title, .login.page .usernameInput {
|
||||
color: #fff;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* Chat page */
|
||||
|
||||
.chat.page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
.messages {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.inputMessage {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: gray;
|
||||
font-size: 70%;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
|
||||
.chatArea {
|
||||
height: 100%;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-y: scroll;
|
||||
padding: 10px 20px 10px 20px;
|
||||
}
|
||||
|
||||
.message.typing .messageBody {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
padding-right: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
|
||||
.inputMessage {
|
||||
border: 10px solid #000;
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
left: 0;
|
||||
outline: none;
|
||||
padding-left: 10px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
50
examples/custom-parsers/README.md
Normal file
50
examples/custom-parsers/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
# Socket.IO custom parsers
|
||||
|
||||
Since Socket.IO version 2.0.0, you can provide your custom parser, according to the needs of your application.
|
||||
|
||||
Several parsers are showcased here:
|
||||
|
||||
- the default one: [socket.io-parser](https://github.com/socketio/socket.io-parser)
|
||||
- one based on msgpack: [socket.io-msgpack-parser](https://github.com/darrachequesne/socket.io-msgpack-parser)
|
||||
- one based on native JSON: [socket.io-json-parser](https://github.com/darrachequesne/socket.io-json-parser)
|
||||
- a custom one based on [schemapack](https://github.com/phretaddin/schemapack)
|
||||
|
||||
They are tested with various payloads:
|
||||
|
||||
- string: `['1', '2', ... '1000']`
|
||||
- numeric: `[1, 2, ... 1000]`
|
||||
- binary: `new Buffer(1000), where buf[i] = i`
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ npm i && npm start
|
||||
```
|
||||
|
||||
## Results
|
||||
|
||||
| bytes / packet | CONNECT packet | string | numeric | binary |
|
||||
|----------------|----------------|--------|---------|-----------|
|
||||
| default | 1 | 5903 | 3904 | 43 + 1000 |
|
||||
| msgpack | 20 | 3919 | 2646 | 1029 |
|
||||
| JSON | 20 | 5930 | 3931 | 3625 |
|
||||
| schemapack | 20 | 3895 | 2005 | 1005 |
|
||||
|
||||
## Comparison
|
||||
|
||||
`default parser`
|
||||
- supports any serializable datastructure, including Blob and File
|
||||
- **but** binary payload is encoded as 2 packets
|
||||
|
||||
`msgpack`
|
||||
- the size of payloads containing mostly numeric values will be greatly reduced
|
||||
- **but** rely on [ArrayBuffer](https://caniuse.com/#feat=typedarrays) in the browser (IE > 9)
|
||||
|
||||
`JSON`
|
||||
- optimized
|
||||
- **but** does not support binary payloads
|
||||
|
||||
`schemapack`
|
||||
- the most efficient in both speed and size
|
||||
- **but** you have to provide a schema for each packet
|
||||
21
examples/custom-parsers/package.json
Normal file
21
examples/custom-parsers/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "parsers",
|
||||
"version": "1.0.0",
|
||||
"description": "Various socket.io parsers",
|
||||
"scripts": {
|
||||
"build": "webpack --config ./support/webpack.config.js",
|
||||
"start": "npm run build && node ./src/server.js"
|
||||
},
|
||||
"author": "Damien Arrachequesne",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"component-emitter": "^1.2.1",
|
||||
"express": "^4.15.2",
|
||||
"schemapack": "^1.4.2",
|
||||
"socket.io": "socketio/socket.io",
|
||||
"socket.io-client": "socketio/socket.io-client",
|
||||
"socket.io-json-parser": "^1.0.0",
|
||||
"socket.io-msgpack-parser": "^1.0.0",
|
||||
"webpack": "^2.4.1"
|
||||
}
|
||||
}
|
||||
13
examples/custom-parsers/public/index.html
Normal file
13
examples/custom-parsers/public/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO custom parsers</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="client1.bundle.js"></script>
|
||||
<script src="client2.bundle.js"></script>
|
||||
<script src="client3.bundle.js"></script>
|
||||
<script src="client4.bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
examples/custom-parsers/src/client1.js
Normal file
8
examples/custom-parsers/src/client1.js
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
const socket = require('socket.io-client')('localhost:3001', {});
|
||||
|
||||
socket.io.engine.on('data', (data) => console.log('[default]' + ' size= ' + (typeof data === 'string' ? data.length : data.byteLength)));
|
||||
|
||||
socket.on('string', (data) => console.log('[default] [string]', data));
|
||||
socket.on('numeric', (data) => console.log('[default] [numeric]', data));
|
||||
socket.on('binary', (data) => console.log('[default] [binary]', data));
|
||||
11
examples/custom-parsers/src/client2.js
Normal file
11
examples/custom-parsers/src/client2.js
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
const customParser = require('socket.io-msgpack-parser');
|
||||
const socket = require('socket.io-client')('http://localhost:3002', {
|
||||
parser: customParser
|
||||
});
|
||||
|
||||
socket.io.engine.on('data', (data) => console.log('[msgpack]' + ' size= ' + (typeof data === 'string' ? data.length : data.byteLength)));
|
||||
|
||||
socket.on('string', (data) => console.log('[msgpack] [string]', data));
|
||||
socket.on('numeric', (data) => console.log('[msgpack] [numeric]', data));
|
||||
socket.on('binary', (data) => console.log('[msgpack] [binary]', data));
|
||||
11
examples/custom-parsers/src/client3.js
Normal file
11
examples/custom-parsers/src/client3.js
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
const customParser = require('socket.io-json-parser');
|
||||
const socket = require('socket.io-client')('localhost:3003', {
|
||||
parser: customParser
|
||||
});
|
||||
|
||||
socket.io.engine.on('data', (data) => console.log('[json]' + ' size= ' + (typeof data === 'string' ? data.length : data.byteLength)));
|
||||
|
||||
socket.on('string', (data) => console.log('[json] [string]', data));
|
||||
socket.on('numeric', (data) => console.log('[json] [numeric]', data));
|
||||
socket.on('binary', (data) => console.log('[json] [binary]', data));
|
||||
11
examples/custom-parsers/src/client4.js
Normal file
11
examples/custom-parsers/src/client4.js
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
const customParser = require('./custom-parser');
|
||||
const socket = require('socket.io-client')('localhost:3004', {
|
||||
parser: customParser
|
||||
});
|
||||
|
||||
socket.io.engine.on('data', (data) => console.log('[custom]' + ' size= ' + (typeof data === 'string' ? data.length : data.byteLength)));
|
||||
|
||||
socket.on('string', (data) => console.log('[custom] [string]', data));
|
||||
socket.on('numeric', (data) => console.log('[custom] [numeric]', data));
|
||||
socket.on('binary', (data) => console.log('[custom] [binary]', data));
|
||||
125
examples/custom-parsers/src/custom-parser.js
Normal file
125
examples/custom-parsers/src/custom-parser.js
Normal file
@@ -0,0 +1,125 @@
|
||||
|
||||
const Emitter = require('component-emitter');
|
||||
const schemapack = require('schemapack');
|
||||
|
||||
/**
|
||||
* Packet types (see https://github.com/socketio/socket.io-protocol)
|
||||
*/
|
||||
|
||||
const TYPES = {
|
||||
CONNECT: 0,
|
||||
DISCONNECT: 1,
|
||||
EVENT: 2,
|
||||
ACK: 3,
|
||||
ERROR: 4,
|
||||
BINARY_EVENT: 5,
|
||||
BINARY_ACK: 6
|
||||
};
|
||||
|
||||
const stringSchema = schemapack.build({
|
||||
_id: 'uint8',
|
||||
data: [ 'string' ],
|
||||
nsp: 'string'
|
||||
});
|
||||
|
||||
const numericSchema = schemapack.build({
|
||||
_id: 'uint8',
|
||||
data: [ 'uint16' ],
|
||||
nsp: 'string'
|
||||
});
|
||||
|
||||
const binarySchema = schemapack.build({
|
||||
_id: 'uint8',
|
||||
data: 'buffer',
|
||||
nsp: 'string'
|
||||
});
|
||||
|
||||
const errorPacket = {
|
||||
type: TYPES.ERROR,
|
||||
data: 'parser error'
|
||||
};
|
||||
|
||||
class Encoder {
|
||||
encode (packet, callback) {
|
||||
switch (packet.type) {
|
||||
case TYPES.EVENT:
|
||||
return callback([ this.pack(packet) ]);
|
||||
default:
|
||||
return callback([ JSON.stringify(packet) ]);
|
||||
}
|
||||
}
|
||||
pack (packet) {
|
||||
let eventName = packet.data[0];
|
||||
let flatPacket = {
|
||||
data: packet.data[1],
|
||||
nsp: packet.nsp
|
||||
};
|
||||
switch (eventName) {
|
||||
case 'string':
|
||||
flatPacket._id = 1;
|
||||
return stringSchema.encode(flatPacket);
|
||||
case 'numeric':
|
||||
flatPacket._id = 2;
|
||||
return numericSchema.encode(flatPacket);
|
||||
case 'binary':
|
||||
flatPacket._id = 3;
|
||||
return binarySchema.encode(flatPacket);
|
||||
default:
|
||||
throw new Error('unknown event name: ' + eventName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Decoder extends Emitter {
|
||||
add (obj) {
|
||||
if (typeof obj === 'string') {
|
||||
this.parseJSON(obj);
|
||||
} else {
|
||||
this.parseBinary(obj);
|
||||
}
|
||||
}
|
||||
parseJSON (obj) {
|
||||
try {
|
||||
let decoded = JSON.parse(obj);
|
||||
this.emit('decoded', decoded);
|
||||
} catch (e) {
|
||||
this.emit('decoded', errorPacket);
|
||||
}
|
||||
}
|
||||
parseBinary (obj) {
|
||||
let view = new Uint8Array(obj);
|
||||
let packetId = view[0];
|
||||
try {
|
||||
let packet = {
|
||||
type: TYPES.EVENT
|
||||
};
|
||||
let decoded;
|
||||
switch (packetId) {
|
||||
case 1:
|
||||
decoded = stringSchema.decode(obj);
|
||||
packet.data = [ 'string', decoded.data ];
|
||||
packet.nsp = decoded.nsp;
|
||||
break;
|
||||
case 2:
|
||||
decoded = numericSchema.decode(obj);
|
||||
packet.data = [ 'numeric', decoded.data ];
|
||||
packet.nsp = decoded.nsp;
|
||||
break;
|
||||
case 3:
|
||||
decoded = binarySchema.decode(obj);
|
||||
packet.data = [ 'binary', decoded.data.buffer ];
|
||||
packet.nsp = decoded.nsp;
|
||||
break;
|
||||
default:
|
||||
throw new Error('unknown type');
|
||||
}
|
||||
this.emit('decoded', packet);
|
||||
} catch (e) {
|
||||
this.emit('decoded', errorPacket);
|
||||
}
|
||||
}
|
||||
destroy () {}
|
||||
}
|
||||
|
||||
exports.Encoder = Encoder;
|
||||
exports.Decoder = Decoder;
|
||||
55
examples/custom-parsers/src/server.js
Normal file
55
examples/custom-parsers/src/server.js
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const server = require('http').createServer(app);
|
||||
const path = require('path');
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../public')));
|
||||
|
||||
server.listen(port, () => console.log('>>> http://localhost:' + port));
|
||||
|
||||
const io = require('socket.io');
|
||||
const msgpackParser = require('socket.io-msgpack-parser');
|
||||
const jsonParser = require('socket.io-json-parser');
|
||||
const customParser = require('./custom-parser');
|
||||
|
||||
let server1 = io(3001, {});
|
||||
let server2 = io(3002, {
|
||||
parser: msgpackParser
|
||||
});
|
||||
let server3 = io(3003, {
|
||||
parser: jsonParser
|
||||
});
|
||||
let server4 = io(3004, {
|
||||
parser: customParser
|
||||
});
|
||||
|
||||
let string = [];
|
||||
let numeric = [];
|
||||
let binary = new Buffer(1e3);
|
||||
for (var i = 0; i < 1e3; i++) {
|
||||
string.push('' + i);
|
||||
numeric.push(i);
|
||||
binary[i] = i;
|
||||
}
|
||||
|
||||
server1.on('connect', onConnect(1000));
|
||||
server2.on('connect', onConnect(2000));
|
||||
server3.on('connect', onConnect(3000));
|
||||
server4.on('connect', onConnect(4000));
|
||||
|
||||
function onConnect (delay) {
|
||||
return function (socket) {
|
||||
console.log('connect ' + socket.id);
|
||||
|
||||
setTimeout(() => {
|
||||
socket.emit('string', string);
|
||||
socket.emit('numeric', numeric);
|
||||
socket.emit('binary', binary);
|
||||
}, delay);
|
||||
|
||||
socket.on('disconnect', () => console.log('disconnect ' + socket.id));
|
||||
};
|
||||
}
|
||||
|
||||
15
examples/custom-parsers/support/webpack.config.js
Normal file
15
examples/custom-parsers/support/webpack.config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
client1: './src/client1.js',
|
||||
client2: './src/client2.js',
|
||||
client3: './src/client3.js',
|
||||
client4: './src/client4.js'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../public'),
|
||||
filename: '[name].bundle.js'
|
||||
}
|
||||
};
|
||||
18
examples/webpack-build-server/README.md
Normal file
18
examples/webpack-build-server/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
# Socket.IO WebPack build
|
||||
|
||||
A sample Webpack build for the server.
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ npm i
|
||||
$ npm run build
|
||||
$ npm start
|
||||
```
|
||||
|
||||
**Note:**
|
||||
|
||||
- the `bufferutil` and `utf-8-validate` are optional dependencies from `ws`, compiled from native code, which are meant to improve performance ([ref](https://github.com/websockets/ws#opt-in-for-performance)). You can also omit them, as they have their JS fallback, and ignore the WebPack warning.
|
||||
|
||||
- the server is initiated with `serveClient` set to `false`, so it will not serve the client file.
|
||||
15
examples/webpack-build-server/lib/index.js
Normal file
15
examples/webpack-build-server/lib/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
const server = require('http').createServer();
|
||||
const io = require('socket.io')(server, {
|
||||
// serveClient: false // do not serve the client file, in that case the brfs loader is not needed
|
||||
});
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
io.on('connect', onConnect);
|
||||
server.listen(port, () => console.log('server listening on port ' + port));
|
||||
|
||||
function onConnect(socket){
|
||||
console.log('connect ' + socket.id);
|
||||
|
||||
socket.on('disconnect', () => console.log('disconnect ' + socket.id));
|
||||
}
|
||||
23
examples/webpack-build-server/package.json
Normal file
23
examples/webpack-build-server/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "webpack-build-server",
|
||||
"version": "1.0.0",
|
||||
"description": "A sample Webpack build (for the server)",
|
||||
"scripts": {
|
||||
"start": "node dist/server.js",
|
||||
"build": "webpack --config ./support/webpack.config.js"
|
||||
},
|
||||
"author": "Damien Arrachequesne",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"brfs": "^1.4.3",
|
||||
"bufferutil": "^1.3.0",
|
||||
"socket.io": "^1.7.2",
|
||||
"transform-loader": "^0.2.3",
|
||||
"utf-8-validate": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"json-loader": "^0.5.4",
|
||||
"null-loader": "^0.1.1",
|
||||
"webpack": "^1.14.0"
|
||||
}
|
||||
}
|
||||
25
examples/webpack-build-server/support/webpack.config.js
Normal file
25
examples/webpack-build-server/support/webpack.config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
target: 'node',
|
||||
output: {
|
||||
path: './dist',
|
||||
filename: 'server.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /(\.md|\.map)$/,
|
||||
loader: 'null'
|
||||
},
|
||||
{
|
||||
test: /\.json$/,
|
||||
loader: 'json'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: "transform-loader?brfs"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
20
examples/webpack-build/README.md
Normal file
20
examples/webpack-build/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
# Socket.IO WebPack build
|
||||
|
||||
A sample Webpack build for the browser.
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ npm i
|
||||
$ npm run build-all
|
||||
```
|
||||
|
||||
There are two WebPack configuration:
|
||||
|
||||
- the minimal configuration, just bundling the application and its dependencies. The `app.js` file in the `dist` folder is the result of that build.
|
||||
|
||||
- a slimmer one, where:
|
||||
- the JSON polyfill needed for IE6/IE7 support has been removed.
|
||||
- the `debug` calls and import have been removed (the [debug](https://github.com/visionmedia/debug) library is included in the build by default).
|
||||
- the source has been uglified (dropping IE8 support), and an associated SourceMap has been generated.
|
||||
13
examples/webpack-build/index.html
Normal file
13
examples/webpack-build/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO WebPack Example</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- <script src="dist/app.js"></script> -->
|
||||
<script src="dist/app.slim.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
10
examples/webpack-build/lib/index.js
Normal file
10
examples/webpack-build/lib/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
var socket = require('socket.io-client')('http://localhost:3000');
|
||||
|
||||
console.log('init');
|
||||
|
||||
socket.on('connect', onConnect);
|
||||
|
||||
function onConnect(){
|
||||
console.log('connect ' + socket.id);
|
||||
}
|
||||
19
examples/webpack-build/package.json
Normal file
19
examples/webpack-build/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "webpack-build",
|
||||
"version": "1.0.0",
|
||||
"description": "A sample Webpack build",
|
||||
"scripts": {
|
||||
"build": "webpack --config ./support/webpack.config.js",
|
||||
"build-slim": "webpack --config ./support/webpack.config.slim.js",
|
||||
"build-all": "npm run build && npm run build-slim"
|
||||
},
|
||||
"author": "Damien Arrachequesne",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"socket.io-client": "^1.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"strip-loader": "^0.1.2",
|
||||
"webpack": "^1.14.0"
|
||||
}
|
||||
}
|
||||
2
examples/webpack-build/support/noop.js
Normal file
2
examples/webpack-build/support/noop.js
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
module.exports = function () { return function () {}; };
|
||||
8
examples/webpack-build/support/webpack.config.js
Normal file
8
examples/webpack-build/support/webpack.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
path: './dist',
|
||||
filename: 'app.js'
|
||||
},
|
||||
};
|
||||
35
examples/webpack-build/support/webpack.config.slim.js
Normal file
35
examples/webpack-build/support/webpack.config.slim.js
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
var webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
path: './dist',
|
||||
filename: 'app.slim.js'
|
||||
},
|
||||
externals: {
|
||||
// replace JSON polyfill (IE6/IE7) with global JSON object
|
||||
json3: 'JSON'
|
||||
},
|
||||
// generate sourcemap
|
||||
devtool: 'source-map',
|
||||
plugins: [
|
||||
// replace require('debug')() with an noop function
|
||||
new webpack.NormalModuleReplacementPlugin(/debug/, process.cwd() + '/support/noop.js'),
|
||||
// use uglifyJS (IE9+ support)
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
})
|
||||
],
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
// strip `debug()` calls
|
||||
test: /\.js$/,
|
||||
loader: 'strip-loader?strip[]=debug'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
17
examples/whiteboard/README.md
Normal file
17
examples/whiteboard/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
# Socket.IO Collaborative Whiteboard
|
||||
|
||||
A simple collaborative whiteboard for socket.io
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ npm i && npm start
|
||||
```
|
||||
|
||||
And point your browser to `http://localhost:3000`. Optionally, specify
|
||||
a port by supplying the `PORT` env variable.
|
||||
|
||||
## Features
|
||||
|
||||
- draw on the whiteboard and all other users will see you drawings live
|
||||
16
examples/whiteboard/index.js
Normal file
16
examples/whiteboard/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const http = require('http').Server(app);
|
||||
const io = require('socket.io')(http);
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
function onConnection(socket){
|
||||
socket.on('drawing', (data) => socket.broadcast.emit('drawing', data));
|
||||
}
|
||||
|
||||
io.on('connection', onConnection);
|
||||
|
||||
http.listen(port, () => console.log('listening on port ' + port));
|
||||
19
examples/whiteboard/package.json
Normal file
19
examples/whiteboard/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "whiteboard",
|
||||
"version": "1.0.0",
|
||||
"description": "A simple collaborative whiteboard using socket.io",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
"socket.io",
|
||||
"whiteboard"
|
||||
],
|
||||
"dependencies": {
|
||||
"express": "4.9.x",
|
||||
"socket.io": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index"
|
||||
},
|
||||
"author": "Damien Arrachequesne",
|
||||
"license": "MIT"
|
||||
}
|
||||
23
examples/whiteboard/public/index.html
Normal file
23
examples/whiteboard/public/index.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO whiteboard</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<canvas class="whiteboard" ></canvas>
|
||||
|
||||
<div class="colors">
|
||||
<div class="color black"></div>
|
||||
<div class="color red"></div>
|
||||
<div class="color green"></div>
|
||||
<div class="color blue"></div>
|
||||
<div class="color yellow"></div>
|
||||
</div>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
100
examples/whiteboard/public/main.js
Normal file
100
examples/whiteboard/public/main.js
Normal file
@@ -0,0 +1,100 @@
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
|
||||
var socket = io();
|
||||
var canvas = document.getElementsByClassName('whiteboard')[0];
|
||||
var colors = document.getElementsByClassName('color');
|
||||
var context = canvas.getContext('2d');
|
||||
|
||||
var current = {
|
||||
color: 'black'
|
||||
};
|
||||
var drawing = false;
|
||||
|
||||
canvas.addEventListener('mousedown', onMouseDown, false);
|
||||
canvas.addEventListener('mouseup', onMouseUp, false);
|
||||
canvas.addEventListener('mouseout', onMouseUp, false);
|
||||
canvas.addEventListener('mousemove', throttle(onMouseMove, 10), false);
|
||||
|
||||
for (var i = 0; i < colors.length; i++){
|
||||
colors[i].addEventListener('click', onColorUpdate, false);
|
||||
}
|
||||
|
||||
socket.on('drawing', onDrawingEvent);
|
||||
|
||||
window.addEventListener('resize', onResize, false);
|
||||
onResize();
|
||||
|
||||
|
||||
function drawLine(x0, y0, x1, y1, color, emit){
|
||||
context.beginPath();
|
||||
context.moveTo(x0, y0);
|
||||
context.lineTo(x1, y1);
|
||||
context.strokeStyle = color;
|
||||
context.lineWidth = 2;
|
||||
context.stroke();
|
||||
context.closePath();
|
||||
|
||||
if (!emit) { return; }
|
||||
var w = canvas.width;
|
||||
var h = canvas.height;
|
||||
|
||||
socket.emit('drawing', {
|
||||
x0: x0 / w,
|
||||
y0: y0 / h,
|
||||
x1: x1 / w,
|
||||
y1: y1 / h,
|
||||
color: color
|
||||
});
|
||||
}
|
||||
|
||||
function onMouseDown(e){
|
||||
drawing = true;
|
||||
current.x = e.clientX;
|
||||
current.y = e.clientY;
|
||||
}
|
||||
|
||||
function onMouseUp(e){
|
||||
if (!drawing) { return; }
|
||||
drawing = false;
|
||||
drawLine(current.x, current.y, e.clientX, e.clientY, current.color, true);
|
||||
}
|
||||
|
||||
function onMouseMove(e){
|
||||
if (!drawing) { return; }
|
||||
drawLine(current.x, current.y, e.clientX, e.clientY, current.color, true);
|
||||
current.x = e.clientX;
|
||||
current.y = e.clientY;
|
||||
}
|
||||
|
||||
function onColorUpdate(e){
|
||||
current.color = e.target.className.split(' ')[1];
|
||||
}
|
||||
|
||||
// limit the number of events per second
|
||||
function throttle(callback, delay) {
|
||||
var previousCall = new Date().getTime();
|
||||
return function() {
|
||||
var time = new Date().getTime();
|
||||
|
||||
if ((time - previousCall) >= delay) {
|
||||
previousCall = time;
|
||||
callback.apply(null, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function onDrawingEvent(data){
|
||||
var w = canvas.width;
|
||||
var h = canvas.height;
|
||||
drawLine(data.x0 * w, data.y0 * h, data.x1 * w, data.y1 * h, data.color);
|
||||
}
|
||||
|
||||
// make the canvas fill its parent
|
||||
function onResize() {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
}
|
||||
|
||||
})();
|
||||
44
examples/whiteboard/public/style.css
Normal file
44
examples/whiteboard/public/style.css
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
/**
|
||||
* Fix user-agent
|
||||
*/
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Canvas
|
||||
*/
|
||||
|
||||
.whiteboard {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.colors {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.color {
|
||||
display: inline-block;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.color.black { background-color: black; }
|
||||
.color.red { background-color: red; }
|
||||
.color.green { background-color: green; }
|
||||
.color.blue { background-color: blue; }
|
||||
.color.yellow { background-color: yellow; }
|
||||
69
gulpfile.js
Normal file
69
gulpfile.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const gulp = require('gulp');
|
||||
const mocha = require('gulp-mocha');
|
||||
const babel = require("gulp-babel");
|
||||
const istanbul = require('gulp-istanbul');
|
||||
const help = require('gulp-task-listing');
|
||||
const del = require('del');
|
||||
|
||||
gulp.task('help', help);
|
||||
|
||||
gulp.task('default', ['transpile']);
|
||||
|
||||
const TRANSPILE_DEST_DIR = './dist';
|
||||
|
||||
// By default, individual js files are transformed by babel and exported to /dist
|
||||
gulp.task('transpile', function () {
|
||||
return gulp.src("lib/*.js")
|
||||
.pipe(babel({ "presets": ["es2015"] }))
|
||||
.pipe(gulp.dest(TRANSPILE_DEST_DIR));
|
||||
});
|
||||
|
||||
gulp.task('clean', function () {
|
||||
return del([TRANSPILE_DEST_DIR]);
|
||||
})
|
||||
|
||||
gulp.task('test', ['transpile'], function(){
|
||||
return gulp.src('test/socket.io.js', {read: false})
|
||||
.pipe(mocha({
|
||||
slow: 200,
|
||||
reporter: 'spec',
|
||||
bail: true,
|
||||
timeout: 10000
|
||||
}))
|
||||
.once('error', function (err) {
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
})
|
||||
.once('end', function () {
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('set-compat-node-env', function() {
|
||||
process.env.TEST_VERSION = 'compat';
|
||||
});
|
||||
|
||||
gulp.task('test-compat', ['set-compat-node-env', 'test']);
|
||||
|
||||
gulp.task('istanbul-pre-test', function () {
|
||||
return gulp.src(['lib/**/*.js'])
|
||||
// Covering files
|
||||
.pipe(istanbul())
|
||||
// Force `require` to return covered files
|
||||
.pipe(istanbul.hookRequire());
|
||||
});
|
||||
|
||||
gulp.task('test-cov', ['istanbul-pre-test'], function(){
|
||||
return gulp.src('test/socket.io.js', {read: false})
|
||||
.pipe(mocha({
|
||||
reporter: 'dot'
|
||||
}))
|
||||
.pipe(istanbul.writeReports())
|
||||
.once('error', function (err){
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
})
|
||||
.once('end', function (){
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
var parser = require('socket.io-parser');
|
||||
var debug = require('debug')('socket.io:client');
|
||||
var url = require('url');
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
@@ -16,15 +17,15 @@ module.exports = Client;
|
||||
* Client constructor.
|
||||
*
|
||||
* @param {Server} server instance
|
||||
* @param {Socket} connection
|
||||
* @param {Socket} conn
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Client(server, conn){
|
||||
this.server = server;
|
||||
this.conn = conn;
|
||||
this.encoder = new parser.Encoder();
|
||||
this.decoder = new parser.Decoder();
|
||||
this.encoder = server.encoder;
|
||||
this.decoder = new server.parser.Decoder();
|
||||
this.id = conn.id;
|
||||
this.request = conn.request;
|
||||
this.setup();
|
||||
@@ -54,11 +55,11 @@ Client.prototype.setup = function(){
|
||||
/**
|
||||
* Connects a client to a namespace.
|
||||
*
|
||||
* @param {String} namespace name
|
||||
* @param {String} name namespace
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.connect = function(name){
|
||||
Client.prototype.connect = function(name, query){
|
||||
debug('connecting to namespace %s', name);
|
||||
var nsp = this.server.nsps[name];
|
||||
if (!nsp) {
|
||||
@@ -72,7 +73,7 @@ Client.prototype.connect = function(name){
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var socket = nsp.add(this, function(){
|
||||
var socket = nsp.add(this, query, function(){
|
||||
self.sockets[socket.id] = socket;
|
||||
self.nsps[nsp.name] = socket;
|
||||
|
||||
@@ -133,7 +134,7 @@ Client.prototype.close = function(){
|
||||
* Writes a packet to the transport.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @param {Object} options
|
||||
* @param {Object} opts
|
||||
* @api private
|
||||
*/
|
||||
|
||||
@@ -152,9 +153,7 @@ Client.prototype.packet = function(packet, opts){
|
||||
if ('open' == this.conn.readyState) {
|
||||
debug('writing packet %j', packet);
|
||||
if (!opts.preEncoded) { // not broadcasting, need to encode
|
||||
this.encoder.encode(packet, function (encodedPackets) { // encode, then write results to engine
|
||||
writeToEngine(encodedPackets);
|
||||
});
|
||||
this.encoder.encode(packet, writeToEngine); // encode, then write results to engine
|
||||
} else { // a broadcast pre-encodes a packet
|
||||
writeToEngine(packet);
|
||||
}
|
||||
@@ -186,11 +185,13 @@ Client.prototype.ondata = function(data){
|
||||
|
||||
Client.prototype.ondecoded = function(packet) {
|
||||
if (parser.CONNECT == packet.type) {
|
||||
this.connect(packet.nsp);
|
||||
this.connect(url.parse(packet.nsp).pathname, url.parse(packet.nsp, true).query);
|
||||
} else {
|
||||
var socket = this.nsps[packet.nsp];
|
||||
if (socket) {
|
||||
socket.onpacket(packet);
|
||||
process.nextTick(function() {
|
||||
socket.onpacket(packet);
|
||||
});
|
||||
} else {
|
||||
debug('no socket for namespace %s', packet.nsp);
|
||||
}
|
||||
@@ -200,7 +201,7 @@ Client.prototype.ondecoded = function(packet) {
|
||||
/**
|
||||
* Handles an error.
|
||||
*
|
||||
* @param {Objcet} error object
|
||||
* @param {Object} err object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
@@ -210,7 +211,7 @@ Client.prototype.onerror = function(err){
|
||||
this.sockets[id].onerror(err);
|
||||
}
|
||||
}
|
||||
this.onclose('client error');
|
||||
this.conn.close();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
156
lib/index.js
156
lib/index.js
@@ -5,13 +5,16 @@
|
||||
|
||||
var http = require('http');
|
||||
var read = require('fs').readFileSync;
|
||||
var parse = require('url').parse;
|
||||
var path = require('path');
|
||||
var exists = require('fs').existsSync;
|
||||
var engine = require('engine.io');
|
||||
var client = require('socket.io-client');
|
||||
var clientVersion = require('socket.io-client/package').version;
|
||||
var Client = require('./client');
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var Namespace = require('./namespace');
|
||||
var Adapter = require('socket.io-adapter');
|
||||
var parser = require('socket.io-parser');
|
||||
var debug = require('debug')('socket.io:server');
|
||||
var url = require('url');
|
||||
|
||||
@@ -25,19 +28,20 @@ module.exports = Server;
|
||||
* Socket.IO client source.
|
||||
*/
|
||||
|
||||
var clientSource = read(require.resolve('socket.io-client/socket.io.js'), 'utf-8');
|
||||
var clientSource = undefined;
|
||||
var clientSourceMap = undefined;
|
||||
|
||||
/**
|
||||
* Server constructor.
|
||||
*
|
||||
* @param {http.Server|Number|Object} http server, port or options
|
||||
* @param {Object} options
|
||||
* @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.listen) {
|
||||
if ('object' == typeof srv && srv instanceof Object && !srv.listen) {
|
||||
opts = srv;
|
||||
srv = null;
|
||||
}
|
||||
@@ -45,6 +49,8 @@ function Server(srv, opts){
|
||||
this.nsps = {};
|
||||
this.path(opts.path || '/socket.io');
|
||||
this.serveClient(false !== opts.serveClient);
|
||||
this.parser = opts.parser || parser;
|
||||
this.encoder = new this.parser.Encoder();
|
||||
this.adapter(opts.adapter || Adapter);
|
||||
this.origins(opts.origins || '*:*');
|
||||
this.sockets = this.of('/');
|
||||
@@ -54,8 +60,8 @@ function Server(srv, opts){
|
||||
/**
|
||||
* Server request verification function, that checks for allowed origins
|
||||
*
|
||||
* @param {http.IncomingMessage} request
|
||||
* @param {Function} callback to be called with the result: `fn(err, success)`
|
||||
* @param {http.IncomingMessage} req request
|
||||
* @param {Function} fn callback to be called with the result: `fn(err, success)`
|
||||
*/
|
||||
|
||||
Server.prototype.checkRequest = function(req, fn) {
|
||||
@@ -87,7 +93,7 @@ Server.prototype.checkRequest = function(req, fn) {
|
||||
/**
|
||||
* Sets/gets whether client code is being served.
|
||||
*
|
||||
* @param {Boolean} whether to serve client code
|
||||
* @param {Boolean} v whether to serve client code
|
||||
* @return {Server|Boolean} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
@@ -95,6 +101,21 @@ Server.prototype.checkRequest = function(req, fn) {
|
||||
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.min.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;
|
||||
};
|
||||
|
||||
@@ -110,7 +131,7 @@ var oldSettings = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Backwards compatiblity.
|
||||
* Backwards compatibility.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
@@ -140,7 +161,7 @@ Server.prototype.set = function(key, val){
|
||||
/**
|
||||
* Sets the client serving path.
|
||||
*
|
||||
* @param {String} pathname
|
||||
* @param {String} v pathname
|
||||
* @return {Server|String} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
@@ -154,7 +175,7 @@ Server.prototype.path = function(v){
|
||||
/**
|
||||
* Sets the adapter for rooms.
|
||||
*
|
||||
* @param {Adapter} pathname
|
||||
* @param {Adapter} v pathname
|
||||
* @return {Server|Adapter} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
@@ -173,7 +194,7 @@ Server.prototype.adapter = function(v){
|
||||
/**
|
||||
* Sets the allowed origins for requests.
|
||||
*
|
||||
* @param {String} origins
|
||||
* @param {String} v origins
|
||||
* @return {Server|Adapter} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
@@ -224,18 +245,27 @@ Server.prototype.attach = function(srv, opts){
|
||||
// set origins verification
|
||||
opts.allowRequest = opts.allowRequest || this.checkRequest.bind(this);
|
||||
|
||||
// initialize engine
|
||||
debug('creating engine.io instance with opts %j', opts);
|
||||
this.eio = engine.attach(srv, opts);
|
||||
var self = this;
|
||||
|
||||
// attach static file serving
|
||||
if (this._serveClient) this.attachServe(srv);
|
||||
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;
|
||||
|
||||
// Export http server
|
||||
this.httpServer = srv;
|
||||
// initialize engine
|
||||
debug('creating engine.io instance with opts %j', opts);
|
||||
self.eio = engine.attach(srv, opts);
|
||||
|
||||
// bind to engine events
|
||||
this.bind(this.eio);
|
||||
// attach static file serving
|
||||
if (self._serveClient) self.attachServe(srv);
|
||||
|
||||
// Export http server
|
||||
self.httpServer = srv;
|
||||
|
||||
// bind to engine events
|
||||
self.bind(self.eio);
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -243,18 +273,21 @@ Server.prototype.attach = function(srv, opts){
|
||||
/**
|
||||
* Attaches the static file serving.
|
||||
*
|
||||
* @param {Function|http.Server} http server
|
||||
* @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(url)) {
|
||||
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++) {
|
||||
@@ -273,9 +306,13 @@ Server.prototype.attachServe = function(srv){
|
||||
*/
|
||||
|
||||
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 (clientVersion == etag) {
|
||||
if (expectedEtag == etag) {
|
||||
debug('serve client 304');
|
||||
res.writeHead(304);
|
||||
res.end();
|
||||
@@ -285,15 +322,46 @@ Server.prototype.serve = function(req, res){
|
||||
|
||||
debug('serve client source');
|
||||
res.setHeader('Content-Type', 'application/javascript');
|
||||
res.setHeader('ETag', clientVersion);
|
||||
res.setHeader('ETag', expectedEtag);
|
||||
res.setHeader('X-SourceMap', 'socket.io.js.map');
|
||||
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.io (or compatible) server
|
||||
* @param {engine.Server} engine engine.io (or compatible) server
|
||||
* @return {Server} self
|
||||
* @api public
|
||||
*/
|
||||
@@ -307,7 +375,7 @@ Server.prototype.bind = function(engine){
|
||||
/**
|
||||
* Called with each incoming transport connection.
|
||||
*
|
||||
* @param {engine.Socket} socket
|
||||
* @param {engine.Socket} conn
|
||||
* @return {Server} self
|
||||
* @api public
|
||||
*/
|
||||
@@ -322,14 +390,14 @@ Server.prototype.onconnection = function(conn){
|
||||
/**
|
||||
* Looks up a namespace.
|
||||
*
|
||||
* @param {String} nsp name
|
||||
* @param {Function} optional, nsp `connection` ev handler
|
||||
* @param {String} name nsp name
|
||||
* @param {Function} [fn] optional, nsp `connection` ev handler
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.of = function(name, fn){
|
||||
if (String(name)[0] !== '/') name = '/' + name;
|
||||
|
||||
|
||||
var nsp = this.nsps[name];
|
||||
if (!nsp) {
|
||||
debug('initializing namespace %s', name);
|
||||
@@ -343,10 +411,11 @@ Server.prototype.of = function(name, fn){
|
||||
/**
|
||||
* Closes server connection
|
||||
*
|
||||
* @param {Function} [fn] optional, called as `fn([err])` on error OR all conns closed
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.close = function(){
|
||||
Server.prototype.close = function(fn){
|
||||
for (var id in this.nsps['/'].sockets) {
|
||||
if (this.nsps['/'].sockets.hasOwnProperty(id)) {
|
||||
this.nsps['/'].sockets[id].onclose();
|
||||
@@ -355,8 +424,10 @@ Server.prototype.close = function(){
|
||||
|
||||
this.engine.close();
|
||||
|
||||
if(this.httpServer){
|
||||
this.httpServer.close();
|
||||
if (this.httpServer) {
|
||||
this.httpServer.close(fn);
|
||||
} else {
|
||||
fn && fn();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -364,18 +435,23 @@ Server.prototype.close = function(){
|
||||
* Expose main namespace (/).
|
||||
*/
|
||||
|
||||
['on', 'to', 'in', 'use', 'emit', 'send', 'write', 'clients', 'compress'].forEach(function(fn){
|
||||
var emitterMethods = Object.keys(Emitter.prototype).filter(function(key){
|
||||
return typeof Emitter.prototype[key] === 'function';
|
||||
});
|
||||
|
||||
emitterMethods.concat(['to', 'in', 'use', 'send', 'write', 'clients', 'compress']).forEach(function(fn){
|
||||
Server.prototype[fn] = function(){
|
||||
var nsp = this.sockets[fn];
|
||||
return nsp.apply(this.sockets, arguments);
|
||||
return this.sockets[fn].apply(this.sockets, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
Namespace.flags.forEach(function(flag){
|
||||
Server.prototype.__defineGetter__(flag, function(){
|
||||
this.sockets.flags = this.sockets.flags || {};
|
||||
this.sockets.flags[flag] = true;
|
||||
return this;
|
||||
Object.defineProperty(Server.prototype, flag, {
|
||||
get: function() {
|
||||
this.sockets.flags = this.sockets.flags || {};
|
||||
this.sockets.flags[flag] = true;
|
||||
return this;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ var Socket = require('./socket');
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var parser = require('socket.io-parser');
|
||||
var debug = require('debug')('socket.io:namespace');
|
||||
var hasBin = require('has-binary');
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
@@ -31,7 +30,8 @@ exports.events = [
|
||||
|
||||
exports.flags = [
|
||||
'json',
|
||||
'volatile'
|
||||
'volatile',
|
||||
'local'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -55,6 +55,8 @@ function Namespace(server, name){
|
||||
this.connected = {};
|
||||
this.fns = [];
|
||||
this.ids = 0;
|
||||
this.rooms = [];
|
||||
this.flags = {};
|
||||
this.initAdapter();
|
||||
}
|
||||
|
||||
@@ -69,10 +71,11 @@ Namespace.prototype.__proto__ = Emitter.prototype;
|
||||
*/
|
||||
|
||||
exports.flags.forEach(function(flag){
|
||||
Namespace.prototype.__defineGetter__(flag, function(){
|
||||
this.flags = this.flags || {};
|
||||
this.flags[flag] = true;
|
||||
return this;
|
||||
Object.defineProperty(Namespace.prototype, flag, {
|
||||
get: function() {
|
||||
this.flags[flag] = true;
|
||||
return this;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,7 +107,7 @@ Namespace.prototype.use = function(fn){
|
||||
* Executes the middleware for an incoming client.
|
||||
*
|
||||
* @param {Socket} socket that will get added
|
||||
* @param {Function} last fn call in the middleware
|
||||
* @param {Function} fn last fn call in the middleware
|
||||
* @api private
|
||||
*/
|
||||
|
||||
@@ -137,8 +140,7 @@ Namespace.prototype.run = function(socket, fn){
|
||||
*/
|
||||
|
||||
Namespace.prototype.to =
|
||||
Namespace.prototype['in'] = function(name){
|
||||
this.rooms = this.rooms || [];
|
||||
Namespace.prototype.in = function(name){
|
||||
if (!~this.rooms.indexOf(name)) this.rooms.push(name);
|
||||
return this;
|
||||
};
|
||||
@@ -150,9 +152,9 @@ Namespace.prototype['in'] = function(name){
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Namespace.prototype.add = function(client, fn){
|
||||
Namespace.prototype.add = function(client, query, fn){
|
||||
debug('adding socket to nsp %s', this.name);
|
||||
var socket = new Socket(this, client);
|
||||
var socket = new Socket(this, client, query);
|
||||
var self = this;
|
||||
this.run(socket, function(err){
|
||||
process.nextTick(function(){
|
||||
@@ -207,10 +209,7 @@ Namespace.prototype.emit = function(ev){
|
||||
} else {
|
||||
// set up packet object
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var parserType = parser.EVENT; // default
|
||||
if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary
|
||||
|
||||
var packet = { type: parserType, data: args };
|
||||
var packet = { type: parser.EVENT, data: args };
|
||||
|
||||
if ('function' == typeof args[args.length - 1]) {
|
||||
throw new Error('Callbacks are not supported when broadcasting');
|
||||
@@ -221,8 +220,8 @@ Namespace.prototype.emit = function(ev){
|
||||
flags: this.flags
|
||||
});
|
||||
|
||||
delete this.rooms;
|
||||
delete this.flags;
|
||||
this.rooms = [];
|
||||
this.flags = {};
|
||||
}
|
||||
return this;
|
||||
};
|
||||
@@ -251,22 +250,21 @@ Namespace.prototype.write = function(){
|
||||
|
||||
Namespace.prototype.clients = function(fn){
|
||||
this.adapter.clients(this.rooms, fn);
|
||||
// delete rooms flag for scenario:
|
||||
// reset rooms for scenario:
|
||||
// .in('room').clients() (GH-1978)
|
||||
delete this.rooms;
|
||||
this.rooms = [];
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the compress flag.
|
||||
*
|
||||
* @param {Boolean} if `true`, compresses the sending data
|
||||
* @param {Boolean} compress if `true`, compresses the sending data
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Namespace.prototype.compress = function(compress){
|
||||
this.flags = this.flags || {};
|
||||
this.flags.compress = compress;
|
||||
return this;
|
||||
};
|
||||
|
||||
181
lib/socket.js
181
lib/socket.js
@@ -7,7 +7,7 @@ var Emitter = require('events').EventEmitter;
|
||||
var parser = require('socket.io-parser');
|
||||
var url = require('url');
|
||||
var debug = require('debug')('socket.io:socket');
|
||||
var hasBin = require('has-binary');
|
||||
var assign = require('object-assign');
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
@@ -25,6 +25,7 @@ exports.events = [
|
||||
'error',
|
||||
'connect',
|
||||
'disconnect',
|
||||
'disconnecting',
|
||||
'newListener',
|
||||
'removeListener'
|
||||
];
|
||||
@@ -55,18 +56,21 @@ var emit = Emitter.prototype.emit;
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Socket(nsp, client){
|
||||
function Socket(nsp, client, query){
|
||||
this.nsp = nsp;
|
||||
this.server = nsp.server;
|
||||
this.adapter = this.nsp.adapter;
|
||||
this.id = nsp.name + '#' + client.id;
|
||||
this.id = nsp.name !== '/' ? nsp.name + '#' + client.id : client.id;
|
||||
this.client = client;
|
||||
this.conn = client.conn;
|
||||
this.rooms = {};
|
||||
this.acks = {};
|
||||
this.connected = true;
|
||||
this.disconnected = false;
|
||||
this.handshake = this.buildHandshake();
|
||||
this.handshake = this.buildHandshake(query);
|
||||
this.fns = [];
|
||||
this.flags = {};
|
||||
this._rooms = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,21 +84,24 @@ Socket.prototype.__proto__ = Emitter.prototype;
|
||||
*/
|
||||
|
||||
flags.forEach(function(flag){
|
||||
Socket.prototype.__defineGetter__(flag, function(){
|
||||
this.flags = this.flags || {};
|
||||
this.flags[flag] = true;
|
||||
return this;
|
||||
Object.defineProperty(Socket.prototype, flag, {
|
||||
get: function() {
|
||||
this.flags[flag] = true;
|
||||
return this;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* `request` engine.io shorcut.
|
||||
* `request` engine.io shortcut.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.__defineGetter__('request', function(){
|
||||
return this.conn.request;
|
||||
Object.defineProperty(Socket.prototype, 'request', {
|
||||
get: function() {
|
||||
return this.conn.request;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -103,7 +110,13 @@ Socket.prototype.__defineGetter__('request', function(){
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.buildHandshake = function(){
|
||||
Socket.prototype.buildHandshake = function(query){
|
||||
var self = this;
|
||||
function buildQuery(){
|
||||
var requestQuery = url.parse(self.request.url, true).query;
|
||||
//if socket-specific query exist, replace query strings in requestQuery
|
||||
return assign({}, query, requestQuery);
|
||||
}
|
||||
return {
|
||||
headers: this.request.headers,
|
||||
time: (new Date) + '',
|
||||
@@ -112,7 +125,7 @@ Socket.prototype.buildHandshake = function(){
|
||||
secure: !!this.request.connection.encrypted,
|
||||
issued: +(new Date),
|
||||
url: this.request.url,
|
||||
query: url.parse(this.request.url, true).query || {}
|
||||
query: buildQuery()
|
||||
};
|
||||
};
|
||||
|
||||
@@ -128,14 +141,14 @@ Socket.prototype.emit = function(ev){
|
||||
emit.apply(this, arguments);
|
||||
} else {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var packet = {};
|
||||
packet.type = hasBin(args) ? parser.BINARY_EVENT : parser.EVENT;
|
||||
packet.data = args;
|
||||
var flags = this.flags || {};
|
||||
var packet = {
|
||||
type: parser.EVENT,
|
||||
data: args
|
||||
};
|
||||
|
||||
// access last argument to see if it's an ACK callback
|
||||
if ('function' == typeof args[args.length - 1]) {
|
||||
if (this._rooms || flags.broadcast) {
|
||||
if (typeof args[args.length - 1] === 'function') {
|
||||
if (this._rooms.length || this.flags.broadcast) {
|
||||
throw new Error('Callbacks are not supported when broadcasting');
|
||||
}
|
||||
|
||||
@@ -144,23 +157,20 @@ Socket.prototype.emit = function(ev){
|
||||
packet.id = this.nsp.ids++;
|
||||
}
|
||||
|
||||
if (this._rooms || flags.broadcast) {
|
||||
if (this._rooms.length || this.flags.broadcast) {
|
||||
this.adapter.broadcast(packet, {
|
||||
except: [this.id],
|
||||
rooms: this._rooms,
|
||||
flags: flags
|
||||
flags: this.flags
|
||||
});
|
||||
} else {
|
||||
// dispatch packet
|
||||
this.packet(packet, {
|
||||
volatile: flags.volatile,
|
||||
compress: flags.compress
|
||||
});
|
||||
this.packet(packet, this.flags);
|
||||
}
|
||||
|
||||
// reset flags
|
||||
delete this._rooms;
|
||||
delete this.flags;
|
||||
this._rooms = [];
|
||||
this.flags = {};
|
||||
}
|
||||
return this;
|
||||
};
|
||||
@@ -175,7 +185,6 @@ Socket.prototype.emit = function(ev){
|
||||
|
||||
Socket.prototype.to =
|
||||
Socket.prototype.in = function(name){
|
||||
this._rooms = this._rooms || [];
|
||||
if (!~this._rooms.indexOf(name)) this._rooms.push(name);
|
||||
return this;
|
||||
};
|
||||
@@ -199,7 +208,7 @@ Socket.prototype.write = function(){
|
||||
* Writes a packet.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @param {Object} options
|
||||
* @param {Object} opts options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
@@ -213,23 +222,31 @@ Socket.prototype.packet = function(packet, opts){
|
||||
/**
|
||||
* Joins a room.
|
||||
*
|
||||
* @param {String} room
|
||||
* @param {Function} optional, callback
|
||||
* @param {String|Array} room or array of rooms
|
||||
* @param {Function} fn optional, callback
|
||||
* @return {Socket} self
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.join = function(room, fn){
|
||||
debug('joining room %s', room);
|
||||
Socket.prototype.join = function(rooms, fn){
|
||||
debug('joining room %s', rooms);
|
||||
var self = this;
|
||||
if (this.rooms.hasOwnProperty(room)) {
|
||||
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.add(this.id, room, function(err){
|
||||
this.adapter.addAll(this.id, rooms, function(err){
|
||||
if (err) return fn && fn(err);
|
||||
debug('joined room %s', room);
|
||||
self.rooms[room] = room;
|
||||
debug('joined room %s', rooms);
|
||||
rooms.forEach(function (room) {
|
||||
self.rooms[room] = room;
|
||||
});
|
||||
fn && fn(null);
|
||||
});
|
||||
return this;
|
||||
@@ -239,7 +256,7 @@ Socket.prototype.join = function(room, fn){
|
||||
* Leaves a room.
|
||||
*
|
||||
* @param {String} room
|
||||
* @param {Function} optional, callback
|
||||
* @param {Function} fn optional, callback
|
||||
* @return {Socket} self
|
||||
* @api private
|
||||
*/
|
||||
@@ -268,8 +285,10 @@ Socket.prototype.leaveAll = function(){
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by `Namespace` upon succesful
|
||||
* 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
|
||||
*/
|
||||
@@ -278,7 +297,11 @@ Socket.prototype.onconnect = function(){
|
||||
debug('socket connected - writing packet');
|
||||
this.nsp.connected[this.id] = this;
|
||||
this.join(this.id);
|
||||
this.packet({ type: parser.CONNECT });
|
||||
// the CONNECT packet for the default namespace
|
||||
// has already been sent as an `initialPacket`
|
||||
if (this.nsp.name !== '/') {
|
||||
this.packet({ type: parser.CONNECT });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -332,13 +355,13 @@ Socket.prototype.onevent = function(packet){
|
||||
args.push(this.ack(packet.id));
|
||||
}
|
||||
|
||||
emit.apply(this, args);
|
||||
this.dispatch(args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Produces an ack callback to emit with an event.
|
||||
*
|
||||
* @param {Number} packet id
|
||||
* @param {Number} id packet id
|
||||
* @api private
|
||||
*/
|
||||
|
||||
@@ -351,10 +374,9 @@ Socket.prototype.ack = function(id){
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
debug('sending ack %j', args);
|
||||
|
||||
var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK;
|
||||
self.packet({
|
||||
id: id,
|
||||
type: type,
|
||||
type: parser.ACK,
|
||||
data: args
|
||||
});
|
||||
|
||||
@@ -409,13 +431,14 @@ Socket.prototype.onerror = function(err){
|
||||
* Called upon closing. Called by `Client`.
|
||||
*
|
||||
* @param {String} reason
|
||||
* @param {Error} optional error object
|
||||
* @throw {Error} optional error object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onclose = function(reason){
|
||||
if (!this.connected) return this;
|
||||
debug('closing socket - reason %s', reason);
|
||||
this.emit('disconnecting', reason);
|
||||
this.leaveAll();
|
||||
this.nsp.remove(this);
|
||||
this.client.remove(this);
|
||||
@@ -428,7 +451,7 @@ Socket.prototype.onclose = function(reason){
|
||||
/**
|
||||
* Produces an `error` packet.
|
||||
*
|
||||
* @param {Object} error object
|
||||
* @param {Object} err error object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
@@ -439,7 +462,7 @@ Socket.prototype.error = function(err){
|
||||
/**
|
||||
* Disconnects this client.
|
||||
*
|
||||
* @param {Boolean} if `true`, closes the underlying connection
|
||||
* @param {Boolean} close if `true`, closes the underlying connection
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
@@ -458,13 +481,73 @@ Socket.prototype.disconnect = function(close){
|
||||
/**
|
||||
* Sets the compress flag.
|
||||
*
|
||||
* @param {Boolean} if `true`, compresses the sending data
|
||||
* @param {Boolean} compress if `true`, compresses the sending data
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.compress = function(compress){
|
||||
this.flags = this.flags || {};
|
||||
this.flags.compress = compress;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatch incoming event to socket listeners.
|
||||
*
|
||||
* @param {Array} event that will get emitted
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.dispatch = function(event){
|
||||
debug('dispatching an event %j', event);
|
||||
var self = this;
|
||||
function dispatchSocket(err) {
|
||||
process.nextTick(function(){
|
||||
if (err) {
|
||||
return self.error(err.data || err.message);
|
||||
}
|
||||
emit.apply(self, event);
|
||||
});
|
||||
}
|
||||
this.run(event, dispatchSocket);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up socket middleware.
|
||||
*
|
||||
* @param {Function} middleware function (event, next)
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.use = function(fn){
|
||||
this.fns.push(fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the middleware for an incoming event.
|
||||
*
|
||||
* @param {Array} event that will get emitted
|
||||
* @param {Function} last fn call in the middleware
|
||||
* @api private
|
||||
*/
|
||||
Socket.prototype.run = function(event, fn){
|
||||
var fns = this.fns.slice(0);
|
||||
if (!fns.length) return fn(null);
|
||||
|
||||
function run(i){
|
||||
fns[i](event, function(err){
|
||||
// upon error, short-circuit
|
||||
if (err) return fn(err);
|
||||
|
||||
// if no middleware left, summon callback
|
||||
if (!fns[i + 1]) return fn(null);
|
||||
|
||||
// go on to next
|
||||
run(i + 1);
|
||||
});
|
||||
}
|
||||
|
||||
run(0);
|
||||
};
|
||||
|
||||
37
package.json
37
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "socket.io",
|
||||
"version": "1.4.4",
|
||||
"version": "2.0.0",
|
||||
"description": "node.js realtime framework server",
|
||||
"keywords": [
|
||||
"realtime",
|
||||
@@ -12,29 +12,38 @@
|
||||
"io"
|
||||
],
|
||||
"main": "./lib/index",
|
||||
"files": [
|
||||
"lib/"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Automattic/socket.io"
|
||||
"url": "git://github.com/socketio/socket.io"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha --reporter dot --slow 200ms --bail"
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"engine.io": "1.6.7",
|
||||
"socket.io-parser": "2.2.6",
|
||||
"socket.io-client": "1.4.4",
|
||||
"socket.io-adapter": "0.4.0",
|
||||
"has-binary": "0.1.7",
|
||||
"debug": "2.2.0"
|
||||
"debug": "~2.6.6",
|
||||
"engine.io": "~3.1.0",
|
||||
"object-assign": "~4.1.1",
|
||||
"socket.io-adapter": "~1.1.0",
|
||||
"socket.io-client": "2.0.0",
|
||||
"socket.io-parser": "~3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"del": "^2.2.2",
|
||||
"expect.js": "0.3.1",
|
||||
"istanbul": "0.2.3",
|
||||
"mocha": "2.3.4",
|
||||
"superagent": "0.17.0",
|
||||
"supertest": "0.8.2",
|
||||
"zuul-ngrok": "3.1.0"
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-babel": "^6.1.2",
|
||||
"gulp-istanbul": "^1.1.1",
|
||||
"gulp-mocha": "^4.3.1",
|
||||
"gulp-task-listing": "1.0.1",
|
||||
"istanbul": "^0.4.5",
|
||||
"mocha": "^3.3.0",
|
||||
"superagent": "1.6.1",
|
||||
"supertest": "1.1.0"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
|
||||
11
test/fixtures/server-close.js
vendored
Normal file
11
test/fixtures/server-close.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
var server = require('http').createServer();
|
||||
var ioc = require('socket.io-client');
|
||||
var io = require('../..')(server);
|
||||
|
||||
var srv = server.listen(function() {
|
||||
var socket = ioc('ws://localhost:' + server.address().port);
|
||||
socket.on('connect', function() {
|
||||
io.close();
|
||||
socket.close();
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,15 @@
|
||||
|
||||
var testVersion = process.env.TEST_VERSION;
|
||||
var http = require('http').Server;
|
||||
var io = require('..');
|
||||
var io;
|
||||
if (testVersion === 'compat') {
|
||||
console.log('testing compat version');
|
||||
io = require('../dist');
|
||||
} else {
|
||||
io = require('../lib');
|
||||
}
|
||||
var fs = require('fs');
|
||||
var join = require('path').join;
|
||||
var exec = require('child_process').exec;
|
||||
var ioc = require('socket.io-client');
|
||||
var request = require('supertest');
|
||||
var expect = require('expect.js');
|
||||
@@ -80,9 +87,6 @@ describe('socket.io', function(){
|
||||
srv.set('authorization', function(o, f) { f(null, false); });
|
||||
|
||||
var socket = client(httpSrv);
|
||||
socket.on('connect', function(){
|
||||
expect().fail();
|
||||
});
|
||||
socket.on('error', function(err) {
|
||||
expect(err).to.be('Not authorized');
|
||||
done();
|
||||
@@ -160,7 +164,7 @@ describe('socket.io', function(){
|
||||
if (err) return done(err);
|
||||
var ctype = res.headers['content-type'];
|
||||
expect(ctype).to.be('application/javascript');
|
||||
expect(res.headers.etag).to.be(clientVersion);
|
||||
expect(res.headers.etag).to.be('"' + clientVersion + '"');
|
||||
expect(res.text).to.match(/engine\.io/);
|
||||
expect(res.status).to.be(200);
|
||||
done();
|
||||
@@ -172,7 +176,7 @@ describe('socket.io', function(){
|
||||
io(srv);
|
||||
request(srv)
|
||||
.get('/socket.io/socket.io.js')
|
||||
.set('If-None-Match', clientVersion)
|
||||
.set('If-None-Match', '"' + clientVersion + '"')
|
||||
.end(function(err, res){
|
||||
if (err) return done(err);
|
||||
expect(res.statusCode).to.be(304);
|
||||
@@ -244,7 +248,7 @@ describe('socket.io', function(){
|
||||
request.get('http://localhost:54013/socket.io/default/')
|
||||
.query({ transport: 'polling' })
|
||||
.end(function (err, res) {
|
||||
expect(res.status).to.be(400);
|
||||
expect(res.status).to.be(403);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -255,7 +259,7 @@ describe('socket.io', function(){
|
||||
.query({ transport: 'polling' })
|
||||
.set('origin', 'http://herp.derp')
|
||||
.end(function (err, res) {
|
||||
expect(res.status).to.be(400);
|
||||
expect(res.status).to.be(403);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -298,7 +302,7 @@ describe('socket.io', function(){
|
||||
.set('origin', 'http://herp.derp')
|
||||
.query({ transport: 'polling' })
|
||||
.end(function (err, res) {
|
||||
expect(res.status).to.be(400);
|
||||
expect(res.status).to.be(403);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -350,7 +354,7 @@ describe('socket.io', function(){
|
||||
.set('origin', 'http://foo.example')
|
||||
.query({ transport: 'polling' })
|
||||
.end(function (err, res) {
|
||||
expect(res.status).to.be(400);
|
||||
expect(res.status).to.be(403);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -392,7 +396,7 @@ describe('socket.io', function(){
|
||||
var net = require('net');
|
||||
var server = net.createServer();
|
||||
|
||||
var clientSocket = ioc('ws://0.0.0.0:' + PORT);
|
||||
var clientSocket = ioc('ws://0.0.0.0:' + PORT, { reconnection: false });
|
||||
|
||||
clientSocket.on('disconnect', function init() {
|
||||
expect(Object.keys(sio.nsps['/'].sockets).length).to.equal(0);
|
||||
@@ -412,12 +416,31 @@ describe('socket.io', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('graceful close', function(){
|
||||
function fixture(filename) {
|
||||
return '"' + process.execPath + '" "' +
|
||||
join(__dirname, 'fixtures', filename) + '"';
|
||||
}
|
||||
|
||||
it('should stop socket and timers', function(done){
|
||||
exec(fixture('server-close.js'), done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('namespaces', function(){
|
||||
var Socket = require('../lib/socket');
|
||||
var Namespace = require('../lib/namespace');
|
||||
|
||||
var Socket;
|
||||
if (testVersion === 'compat') {
|
||||
Socket = require('../dist/socket');
|
||||
} else {
|
||||
Socket = require('../lib/socket');
|
||||
}
|
||||
var Namespace;
|
||||
if (testVersion === 'compat') {
|
||||
Namespace = require('../dist/namespace');
|
||||
} else {
|
||||
Namespace = require('../lib/namespace');
|
||||
}
|
||||
it('should be accessible through .sockets', function(){
|
||||
var sio = io();
|
||||
expect(sio.sockets).to.be.a(Namespace);
|
||||
@@ -618,6 +641,31 @@ describe('socket.io', function(){
|
||||
});
|
||||
});
|
||||
|
||||
it('should fire a `disconnecting` event just before leaving all rooms', function(done){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var socket = client(srv);
|
||||
|
||||
sio.on('connection', function(s){
|
||||
s.join('a', function(){
|
||||
s.disconnect();
|
||||
});
|
||||
|
||||
var total = 2;
|
||||
s.on('disconnecting', function(reason){
|
||||
expect(Object.keys(s.rooms)).to.eql([s.id, 'a']);
|
||||
total--;
|
||||
});
|
||||
|
||||
s.on('disconnect', function(reason){
|
||||
expect(Object.keys(s.rooms)).to.eql([]);
|
||||
--total || done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error connecting to non-existent namespace', function(done){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
@@ -640,7 +688,7 @@ describe('socket.io', function(){
|
||||
var clientSocket2 = client(srv);
|
||||
sio.on('connection', function() {
|
||||
connections++;
|
||||
if(connections === 2) {
|
||||
if (connections === 2) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -774,7 +822,7 @@ describe('socket.io', function(){
|
||||
setTimeout(function() {
|
||||
sio.of('/chat').emit('ev', 'data');
|
||||
sio.of('/chat').volatile.emit('ev', 'data');
|
||||
}, 20);
|
||||
}, 50);
|
||||
});
|
||||
|
||||
var socket = client(srv, '/chat');
|
||||
@@ -786,7 +834,7 @@ describe('socket.io', function(){
|
||||
setTimeout(function() {
|
||||
expect(counter).to.be(1);
|
||||
done();
|
||||
}, 200);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it('should emit volatile event', function(done) {
|
||||
@@ -799,7 +847,7 @@ describe('socket.io', function(){
|
||||
// Wait to make sure there are no packets being sent for opening the connection
|
||||
setTimeout(function() {
|
||||
sio.of('/chat').volatile.emit('ev', 'data');
|
||||
}, 20);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
var socket = client(srv, '/chat');
|
||||
@@ -811,7 +859,7 @@ describe('socket.io', function(){
|
||||
setTimeout(function() {
|
||||
expect(counter).to.be(1);
|
||||
done();
|
||||
}, 200);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it('should enable compression by default', function(done){
|
||||
@@ -930,12 +978,12 @@ describe('socket.io', function(){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var socket = client(srv);
|
||||
var socket = client(srv, { reconnection: false });
|
||||
sio.on('connection', function(s){
|
||||
s.on('error', function(err){
|
||||
expect(err).to.be.an(Error);
|
||||
s.on('disconnect', function(reason){
|
||||
expect(reason).to.be('client error');
|
||||
expect(reason).to.be('forced close');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -1128,7 +1176,7 @@ describe('socket.io', function(){
|
||||
// Wait to make sure there are no packets being sent for opening the connection
|
||||
setTimeout(function() {
|
||||
s.volatile.emit('ev', 'data');
|
||||
}, 20);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
var socket = client(srv, { transports: ['polling'] });
|
||||
@@ -1140,7 +1188,7 @@ describe('socket.io', function(){
|
||||
setTimeout(function() {
|
||||
expect(counter).to.be(1);
|
||||
done();
|
||||
}, 200);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it('should emit volatile event (ws)', function(done) {
|
||||
@@ -1179,7 +1227,7 @@ describe('socket.io', function(){
|
||||
setTimeout(function() {
|
||||
s.volatile.emit('ev', 'data');
|
||||
s.volatile.emit('ev', 'data');
|
||||
}, 20);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
var socket = client(srv, { transports: ['polling'] });
|
||||
@@ -1191,7 +1239,7 @@ describe('socket.io', function(){
|
||||
setTimeout(function() {
|
||||
expect(counter).to.be(1);
|
||||
done();
|
||||
}, 200);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it('should emit only one consecutive volatile event (ws)', function(done) {
|
||||
@@ -1495,9 +1543,7 @@ describe('socket.io', function(){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function() {
|
||||
var addr = srv.listen().address();
|
||||
var url = 'ws://localhost:' + addr.port + '?key1=1&key2=2';
|
||||
var socket = ioc(url);
|
||||
var socket = client(srv, {query: {key1: 1, key2: 2}});
|
||||
sio.on('connection', function(s) {
|
||||
var parsed = require('url').parse(s.request.url);
|
||||
var query = require('querystring').parse(parsed.query);
|
||||
@@ -1507,6 +1553,22 @@ describe('socket.io', function(){
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should see query parameters sent from secondary namespace connections in handshake object', function(done){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
var client1 = client(srv);
|
||||
var client2 = client(srv, '/connection2', {query: {key1: 'aa', key2: '&=bb'}});
|
||||
sio.on('connection', function(s){
|
||||
});
|
||||
sio.of('/connection2').on('connection', function(s){
|
||||
expect(s.handshake.query.key1).to.be('aa');
|
||||
expect(s.handshake.query.key2).to.be('&=bb');
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
it('should handle very large json', function(done){
|
||||
this.timeout(30000);
|
||||
@@ -1578,11 +1640,10 @@ describe('socket.io', function(){
|
||||
var clientSocket = client(srv, { reconnectionAttempts: 10, reconnectionDelay: 100 });
|
||||
clientSocket.once('connect', function(){
|
||||
srv.close(function(){
|
||||
srv.listen(port, function(){
|
||||
clientSocket.on('reconnect', function(){
|
||||
clientSocket.emit('ev', 'payload');
|
||||
});
|
||||
clientSocket.on('reconnect', function(){
|
||||
clientSocket.emit('ev', 'payload');
|
||||
});
|
||||
sio.listen(port);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1622,7 +1683,7 @@ describe('socket.io', function(){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var socket = client(srv);
|
||||
var socket = client(srv, { reconnection: false });
|
||||
sio.on('connection', function(s){
|
||||
s.conn.on('upgrade', function(){
|
||||
console.log('\033[96mNote: warning expected and normal in test.\033[39m');
|
||||
@@ -1639,7 +1700,7 @@ describe('socket.io', function(){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var socket = client(srv);
|
||||
var socket = client(srv, { reconnection: false });
|
||||
sio.on('connection', function(s){
|
||||
s.once('error', function(err){
|
||||
expect(err.message).to.match(/Illegal attachments/);
|
||||
@@ -1656,7 +1717,7 @@ describe('socket.io', function(){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var socket = client(srv);
|
||||
var socket = client(srv, { reconnection: false });
|
||||
sio.on('connection', function(s){
|
||||
s.once('error', function(err){
|
||||
expect(err.message).to.match(/Illegal attachments/);
|
||||
@@ -1669,8 +1730,10 @@ describe('socket.io', function(){
|
||||
});
|
||||
});
|
||||
|
||||
it('should not crash when messing with Object prototype', function(done){
|
||||
it('should not crash when messing with Object prototype (and other globals)', function(done){
|
||||
Object.prototype.foo = 'bar';
|
||||
global.File = '';
|
||||
global.Blob = [];
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
@@ -2022,10 +2085,30 @@ describe('socket.io', function(){
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('allows to join several rooms at once', function(done) {
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
|
||||
srv.listen(function(){
|
||||
var socket = client(srv);
|
||||
sio.on('connection', function(s){
|
||||
s.join(['a', 'b', 'c'], function(){
|
||||
expect(Object.keys(s.rooms)).to.eql([s.id, 'a', 'b', 'c']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('middleware', function(done){
|
||||
var Socket = require('../lib/socket');
|
||||
var Socket;
|
||||
if (testVersion === 'compat') {
|
||||
Socket = require('../dist/socket');
|
||||
} else {
|
||||
Socket = require('../lib/socket');
|
||||
}
|
||||
|
||||
it('should call functions', function(done){
|
||||
var srv = http();
|
||||
@@ -2062,9 +2145,6 @@ describe('socket.io', function(){
|
||||
});
|
||||
srv.listen(function(){
|
||||
var socket = client(srv);
|
||||
socket.on('connect', function(){
|
||||
done(new Error('nope'));
|
||||
});
|
||||
socket.on('error', function(err){
|
||||
expect(err).to.be('Authentication error');
|
||||
done();
|
||||
@@ -2083,9 +2163,6 @@ describe('socket.io', function(){
|
||||
});
|
||||
srv.listen(function(){
|
||||
var socket = client(srv);
|
||||
socket.on('connect', function(){
|
||||
done(new Error('nope'));
|
||||
});
|
||||
socket.on('error', function(err){
|
||||
expect(err).to.eql({ a: 'b', c: 3 });
|
||||
done();
|
||||
@@ -2163,4 +2240,97 @@ describe('socket.io', function(){
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('socket middleware', function(done){
|
||||
var Socket = require('../lib/socket');
|
||||
|
||||
it('should call functions', function(done){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
var run = 0;
|
||||
|
||||
srv.listen(function(){
|
||||
var socket = client(srv, { multiplex: false });
|
||||
|
||||
socket.emit('join', 'woot');
|
||||
|
||||
sio.on('connection', function(socket){
|
||||
socket.use(function(event, next){
|
||||
expect(event).to.eql(['join', 'woot']);
|
||||
event.unshift('wrap');
|
||||
run++;
|
||||
next();
|
||||
});
|
||||
socket.use(function(event, next){
|
||||
expect(event).to.eql(['wrap', 'join', 'woot']);
|
||||
run++;
|
||||
next();
|
||||
});
|
||||
socket.on('wrap', function(data1, data2){
|
||||
expect(data1).to.be('join');
|
||||
expect(data2).to.be('woot');
|
||||
expect(run).to.be(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass errors', function(done){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
|
||||
srv.listen(function(){
|
||||
var clientSocket = client(srv, { multiplex: false });
|
||||
|
||||
clientSocket.emit('join', 'woot');
|
||||
|
||||
clientSocket.on('error', function(err){
|
||||
expect(err).to.be('Authentication error');
|
||||
done();
|
||||
});
|
||||
|
||||
sio.on('connection', function(socket){
|
||||
socket.use(function(event, next){
|
||||
next(new Error('Authentication error'));
|
||||
});
|
||||
socket.use(function(event, next){
|
||||
done(new Error('nope'));
|
||||
});
|
||||
|
||||
socket.on('join', function(){
|
||||
done(new Error('nope'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass `data` of error object', function(done){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
|
||||
srv.listen(function(){
|
||||
var clientSocket = client(srv, { multiplex: false });
|
||||
|
||||
clientSocket.emit('join', 'woot');
|
||||
|
||||
clientSocket.on('error', function(err){
|
||||
expect(err).to.eql({ a: 'b', c: 3 });
|
||||
done();
|
||||
});
|
||||
|
||||
sio.on('connection', function(socket){
|
||||
socket.use(function(event, next){
|
||||
var err = new Error('Authentication error');
|
||||
err.data = { a: 'b', c: 3 };
|
||||
next(err);
|
||||
});
|
||||
|
||||
socket.on('join', function(){
|
||||
done(new Error('nope'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user