mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-12 08:27:57 -05:00
Compare commits
9 Commits
2.0.1
...
dynamic-na
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
213577204a | ||
|
|
dd72676c25 | ||
|
|
3bf7be73a0 | ||
|
|
0360553c99 | ||
|
|
de5cbdb833 | ||
|
|
0ce0ce1dbc | ||
|
|
cc7ce79251 | ||
|
|
1b77c27f7b | ||
|
|
1f8bb8a0ec |
26
.github/ISSUE_TEMPLATE.md
vendored
26
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,26 +0,0 @@
|
||||
|
||||
*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
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,18 +0,0 @@
|
||||
|
||||
### 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,5 +9,3 @@ lib-cov
|
||||
benchmarks/*.png
|
||||
node_modules
|
||||
coverage
|
||||
.idea
|
||||
dist
|
||||
|
||||
4
.npmignore
Normal file
4
.npmignore
Normal file
@@ -0,0 +1,4 @@
|
||||
support
|
||||
test
|
||||
examples
|
||||
.gitignore
|
||||
12
.travis.yml
12
.travis.yml
@@ -1,12 +1,16 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- "4"
|
||||
- "6"
|
||||
- "7"
|
||||
- "0.8"
|
||||
- "0.10"
|
||||
- "0.11"
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- node_js: "0.11"
|
||||
|
||||
notifications:
|
||||
irc: "irc.freenode.org#socket.io"
|
||||
|
||||
232
History.md
232
History.md
@@ -1,236 +1,4 @@
|
||||
|
||||
2.0.1 / 2017-05-09
|
||||
===================
|
||||
|
||||
* [fix] Update path of client file (#2934)
|
||||
|
||||
2.0.0 / 2017-05-09
|
||||
===================
|
||||
|
||||
* [feat] Move binary detection to the parser (#2923)
|
||||
* [feat] Allow to join several rooms at once (#2879)
|
||||
* [feat] Merge Engine.IO and Socket.IO handshake packets (#2833)
|
||||
* [feat] Allow the use of custom parsers (#2829)
|
||||
|
||||
* [fix] Use path.resolve by default and require.resolve as a fallback (#2797)
|
||||
* [fix] Properly close the connection on error (#2681)
|
||||
* [fix] Prevent null from being accepted as argument (#2606)
|
||||
|
||||
* [perf] Use shared instance of the encoder (#2825)
|
||||
* [perf] Reset properties instead of deleting them (#2826)
|
||||
* [perf] micro-optimisations (#2793)
|
||||
|
||||
* [chore] Merge history of 1.7.x and 0.9.x branches (#2930)
|
||||
* [chore] Added backers and sponsors on the README (#2933)
|
||||
* [chore] Bump dependencies (#2926)
|
||||
* [chore] Bump socket.io-adapter to version 1.0.0 (#2867)
|
||||
* [chore] Bump engine.io to version 2.0.2 (#2864)
|
||||
* [chore] Bump engine.io to version 2.0.0 (#2832)
|
||||
* [chore] Update issue template with fiddle (#2811)
|
||||
* [chore] Update copyright year LICENSE to 2017 (#2803)
|
||||
|
||||
* [docs] Add an example of custom parser (#2929)
|
||||
* [docs] Replace non-breaking space with proper whitespace (#2913)
|
||||
* [docs] Update emit cheatsheet (#2906)
|
||||
* [docs] Explicitly document that Server extends EventEmitter (#2874)
|
||||
* [docs] Add server.engine.generateId attribute (#2880)
|
||||
* [docs] Fix wrong space character in README (#2900)
|
||||
* [docs] Fix documentation for 'connect' event (#2898)
|
||||
* [docs] Add webpack build example (#2828)
|
||||
* [docs] Update the wording to match the code example (#2853)
|
||||
* [docs] Small addition to the Express Readme Part (#2846)
|
||||
* [docs] Add a 'Features' section in the README (#2824)
|
||||
* [docs] Add httpd cluster example (#2819)
|
||||
* [docs] Add haproxy cluster example (#2818)
|
||||
* [docs] Add nginx cluster example (#2817)
|
||||
* [docs] Implement whiteboard example (#2810)
|
||||
* [docs] Fix documentation for `local` flag (#2816)
|
||||
* [docs] Add emit cheatsheet (#2815)
|
||||
* [docs] Add pingInterval/pingTimeout/transports options in the API documentation (#2814)
|
||||
* [docs] Add an example for socket.join() method (#2813)
|
||||
* [docs] Fix a typo on `clients` method in the API documentation (#2812)
|
||||
* [docs] Fix wrong argument name in API.md (#2802)
|
||||
* [docs] Add install script on Readme.md (#2780)
|
||||
* [docs] API documentation (#2784)
|
||||
|
||||
1.7.4 / 2017-05-07
|
||||
===================
|
||||
|
||||
* [chore] Bump engine.io to version 1.8.4
|
||||
|
||||
0.9.18 / 2017-05-07
|
||||
===================
|
||||
|
||||
* Remove process.EventEmitter usage for Node 7.x
|
||||
|
||||
1.7.3 / 2017-02-17
|
||||
===================
|
||||
|
||||
* [chore] Bump engine.io to version 1.8.3
|
||||
|
||||
1.7.2 / 2016-12-11
|
||||
===================
|
||||
|
||||
* [chore] Bump engine.io to version 1.8.2 (#2782)
|
||||
* [fix] Fixes socket.use error packet (#2772)
|
||||
|
||||
1.7.1 / 2016-11-28
|
||||
===================
|
||||
|
||||
1.7.0 / 2016-11-27
|
||||
===================
|
||||
|
||||
* [docs] Comment connected socket availability for adapters (#2081)
|
||||
* [docs] Fixed grammar issues in the README.md (#2159)
|
||||
* [feature] serve sourcemap for socket.io-client (#2482)
|
||||
* [feature] Add a `local` flag (#2628)
|
||||
* [chore] Bump engine.io to version 1.8.1 (#2765)
|
||||
* [chore] Update client location and serve minified file (#2766)
|
||||
|
||||
1.6.0 / 2016-11-20
|
||||
==================
|
||||
|
||||
* [fix] Make ETag header comply with standard. (#2603)
|
||||
* [feature] Loading client script on demand. (#2567)
|
||||
* [test] Fix leaking clientSocket (#2721)
|
||||
* [feature] Add support for all event emitter methods (#2601)
|
||||
* [chore] Update year to 2016 (#2456)
|
||||
* [feature] Add support for socket middleware (#2306)
|
||||
* [feature] add support for Server#close(callback) (#2748)
|
||||
* [fix] Don't drop query variables on handshake (#2745)
|
||||
* [example] Add disconnection/reconnection logs to the chat example (#2675)
|
||||
* [perf] Minor code optimizations (#2219)
|
||||
* [chore] Bump debug to version 2.3.3 (#2754)
|
||||
* [chore] Bump engine.io to version 1.8.0 (#2755)
|
||||
* [chore] Bump socket.io-adapter to version 0.5.0 (#2756)
|
||||
|
||||
1.5.1 / 2016-10-24
|
||||
==================
|
||||
|
||||
* [fix] Avoid swallowing exceptions thrown by user event handlers (#2682)
|
||||
* [test] Use client function to unify `client` in test script (#2731)
|
||||
* [docs] Add link to LICENSE (#2221)
|
||||
* [docs] Fix JSDoc of optional parameters (#2465)
|
||||
* [docs] Fix typo (#2724)
|
||||
* [docs] Link readme npm package badge to npm registry page (#2612)
|
||||
* [docs] Minor fixes (#2526)
|
||||
* [chore] Bump socket.io-parser to 2.3.0 (#2730)
|
||||
* [chore] Add Github issue and PR templates (#2733)
|
||||
* [chore] Bump engine.io to 1.7.2 (#2729)
|
||||
* [chore] Bump socket.io-parser to 2.3.1 (#2734)
|
||||
|
||||
1.5.0 / 2016-10-06
|
||||
==================
|
||||
|
||||
* [feature] stop append /# before id when no namespace (#2508)
|
||||
* [feature] Add a 'disconnecting' event to access to socket.rooms upon disconnection (#2332)
|
||||
* [fix] Fix query string management (#2422)
|
||||
* [fix] add quote to exec paths, prevent error when spaces in path (#2508)
|
||||
* [docs] Prevent mixup for new programmers (#2599)
|
||||
* [example] Fix chat display in Firefox (#2477)
|
||||
* [chore] Add gulp & babel in the build process (#2471)
|
||||
* [chore] Bump engine.io to 1.7.0 (#2707)
|
||||
* [chore] Remove unused zuul-ngrok dependency (#2708)
|
||||
* [chore] Point towards current master of socket.io-client (#2710)
|
||||
* [chore] Restrict files included in npm package (#2709)
|
||||
* [chore] Link build badge to master branch (#2549)
|
||||
|
||||
1.4.8 / 2016-06-23
|
||||
==================
|
||||
|
||||
* package: bump `engine.io`
|
||||
|
||||
1.4.7 / 2016-06-23
|
||||
==================
|
||||
|
||||
* package: bump `engine.io`
|
||||
|
||||
1.4.6 / 2016-05-02
|
||||
==================
|
||||
|
||||
* package: bump engine.io
|
||||
|
||||
1.4.5 / 2016-01-26
|
||||
==================
|
||||
|
||||
* fix closing the underlying `http.Server`
|
||||
|
||||
1.4.4 / 2016-01-10
|
||||
==================
|
||||
|
||||
* package: bump `engine.io`
|
||||
|
||||
1.4.3 / 2016-01-08
|
||||
==================
|
||||
|
||||
* bump `socket.io-client`
|
||||
|
||||
1.4.2 / 2016-01-07
|
||||
==================
|
||||
|
||||
* bump `engine.io`
|
||||
|
||||
1.4.1 / 2016-01-07
|
||||
==================
|
||||
|
||||
* version bump
|
||||
|
||||
1.4.0 / 2015-11-28
|
||||
==================
|
||||
|
||||
* socket.io: increase large binary data test timeout
|
||||
* package: bump `engine.io` for release
|
||||
* trigger callback even when joining an already joined room
|
||||
* package: bump parser
|
||||
* namespace: clear rooms flag after a clients call (fixes #1978)
|
||||
* package: bump `socket.io-parser`
|
||||
* fixed tests with large data
|
||||
* fixed a typo in the example code
|
||||
* package: bump mocha
|
||||
* package: bump `has-binary` and `zuul-ngrok`
|
||||
* package: bump `engine.io` and `socket.io-client`
|
||||
* README: clarified documentation of Socket.in
|
||||
* README: fixed up legacy repo links
|
||||
* test: better timeout for stress test
|
||||
* socket: don't set request property which has a getter
|
||||
* removed proxy index file
|
||||
* support flags on namespace
|
||||
* improve Socket#packet and Client#packet
|
||||
* socket: warn node_redis-style about missing `error`
|
||||
* test: added failing test
|
||||
* test: increase timeout for large binary data test
|
||||
* package: bump `has-binary` to work with all objects (fixes #1955)
|
||||
* fix origin verification default https port [evanlucas]
|
||||
* support compression [nkzawa]
|
||||
* changed type of `Client#sockets`, `Namespace#sockets` and `Socket#rooms` to maps (instead of arrays)
|
||||
|
||||
1.3.7 / 2015-09-21
|
||||
==================
|
||||
|
||||
* package: bump `socket.io-client` for node4 compatibility
|
||||
* package: bump `engine.io` for node4 compatibility
|
||||
|
||||
1.3.6 / 2015-07-14
|
||||
==================
|
||||
|
||||
* package: bump `engine.io` to fix build on windows
|
||||
|
||||
1.3.5 / 2015-03-03
|
||||
==================
|
||||
|
||||
* package: bump `socket.io-parser`
|
||||
|
||||
1.3.4 / 2015-02-14
|
||||
==================
|
||||
|
||||
* package: bump `socket.io-client`
|
||||
|
||||
1.3.3 / 2015-02-03
|
||||
==================
|
||||
|
||||
* socket: warn node_redis-style about missing `error`
|
||||
* package: bump parser to better handle bad binary packets
|
||||
|
||||
1.3.2 / 2015-01-19
|
||||
==================
|
||||
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014-2017 Automattic <dev@cloudup.com>
|
||||
Copyright (c) 2014-2015 Automattic <dev@cloudup.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
11
Makefile
11
Makefile
@@ -1,8 +1,15 @@
|
||||
|
||||
REPORTER = dot
|
||||
|
||||
test:
|
||||
@./node_modules/.bin/gulp test
|
||||
@./node_modules/.bin/mocha \
|
||||
--reporter $(REPORTER) \
|
||||
--slow 200ms \
|
||||
--bail
|
||||
|
||||
test-cov:
|
||||
@./node_modules/.bin/gulp test-cov
|
||||
@./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- \
|
||||
--reporter $(REPORTER) \
|
||||
test/
|
||||
|
||||
.PHONY: test
|
||||
|
||||
532
Readme.md
532
Readme.md
@@ -1,90 +1,9 @@
|
||||
|
||||
# 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
|
||||
```
|
||||
[](http://travis-ci.org/Automattic/socket.io)
|
||||

|
||||

|
||||
|
||||
## How to use
|
||||
|
||||
@@ -94,9 +13,9 @@ HTTP server listening on port `3000`.
|
||||
```js
|
||||
var server = require('http').createServer();
|
||||
var io = require('socket.io')(server);
|
||||
io.on('connection', function(client){
|
||||
client.on('event', function(data){});
|
||||
client.on('disconnect', function(){});
|
||||
io.on('connection', function(socket){
|
||||
socket.on('event', function(data){});
|
||||
socket.on('disconnect', function(){});
|
||||
});
|
||||
server.listen(3000);
|
||||
```
|
||||
@@ -105,7 +24,7 @@ server.listen(3000);
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(client){});
|
||||
io.on('connection', function(socket){});
|
||||
io.listen(3000);
|
||||
```
|
||||
|
||||
@@ -114,7 +33,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. Also make sure to call `.listen` on the `server`, not the `app`.
|
||||
function.
|
||||
|
||||
```js
|
||||
var app = require('express')();
|
||||
@@ -137,13 +56,356 @@ io.on('connection', function(){ /* … */ });
|
||||
server.listen(3000);
|
||||
```
|
||||
|
||||
## Documentation
|
||||
## API
|
||||
|
||||
Please see the documentation [here](/docs/README.md). Contributions are welcome!
|
||||
### 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/learnboost/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/Automattic/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/troygoode/node-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/Automattic/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/Automattic/socket.io-adapter).
|
||||
|
||||
### Socket#to(room:String):Socket
|
||||
### Socket#in(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#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`.
|
||||
|
||||
## Debug / logging
|
||||
|
||||
Socket.IO is powered by [debug](https://github.com/visionmedia/debug).
|
||||
Socket.IO is powered by [debug](http://github.com/visionmedia/debug).
|
||||
In order to see all the debug output, run your app with the environment variable
|
||||
`DEBUG` including the desired scope.
|
||||
|
||||
@@ -153,90 +415,6 @@ 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](LICENSE)
|
||||
MIT
|
||||
|
||||
619
docs/API.md
619
docs/API.md
@@ -1,619 +0,0 @@
|
||||
|
||||
## 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`.
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
## 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
58
docs/emit.md
@@ -1,58 +0,0 @@
|
||||
|
||||
## 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
|
||||
$ npm start
|
||||
$ node .
|
||||
```
|
||||
|
||||
And point your browser to `http://localhost:3000`. Optionally, specify
|
||||
|
||||
@@ -14,6 +14,8 @@ app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// Chatroom
|
||||
|
||||
// usernames which are currently connected to the chat
|
||||
var usernames = {};
|
||||
var numUsers = 0;
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
@@ -30,10 +32,10 @@ io.on('connection', function (socket) {
|
||||
|
||||
// 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;
|
||||
// add the client's username to the global list
|
||||
usernames[username] = username;
|
||||
++numUsers;
|
||||
addedUser = true;
|
||||
socket.emit('login', {
|
||||
@@ -62,7 +64,9 @@ io.on('connection', function (socket) {
|
||||
|
||||
// when the user disconnects.. perform this
|
||||
socket.on('disconnect', function () {
|
||||
// remove the username from global usernames list
|
||||
if (addedUser) {
|
||||
delete usernames[socket.username];
|
||||
--numUsers;
|
||||
|
||||
// echo globally that this client has left
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
"private": true,
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "4.13.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
"express": "3.4.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ $(function() {
|
||||
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
|
||||
];
|
||||
|
||||
// Initialize variables
|
||||
// Initialize varibles
|
||||
var $window = $(window);
|
||||
var $usernameInput = $('.usernameInput'); // Input for username
|
||||
var $messages = $('.messages'); // Messages area
|
||||
@@ -263,20 +263,4 @@ $(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,6 +128,7 @@ ul {
|
||||
}
|
||||
|
||||
.username {
|
||||
float: left;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
padding-right: 15px;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
|
||||
# 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.
|
||||
@@ -1,51 +0,0 @@
|
||||
|
||||
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"
|
||||
@@ -1,2 +0,0 @@
|
||||
FROM haproxy:1.7-alpine
|
||||
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
|
||||
@@ -1,31 +0,0 @@
|
||||
# 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
|
||||
@@ -1,15 +0,0 @@
|
||||
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" ]
|
||||
@@ -1,87 +0,0 @@
|
||||
// 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
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "socket.io-chat",
|
||||
"version": "0.0.0",
|
||||
"description": "A simple chat client using socket.io",
|
||||
"main": "index.js",
|
||||
"author": "Grant Timmerman",
|
||||
"private": true,
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<!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>
|
||||
@@ -1,286 +0,0 @@
|
||||
$(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);
|
||||
})
|
||||
|
||||
});
|
||||
@@ -1,149 +0,0 @@
|
||||
/* Fix user-agent */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-weight: 300;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
html, input {
|
||||
font-family:
|
||||
"HelveticaNeue-Light",
|
||||
"Helvetica Neue Light",
|
||||
"Helvetica Neue",
|
||||
Helvetica,
|
||||
Arial,
|
||||
"Lucida Grande",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Pages */
|
||||
|
||||
.pages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Login Page */
|
||||
|
||||
.login.page {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.login.page .form {
|
||||
height: 100px;
|
||||
margin-top: -100px;
|
||||
position: absolute;
|
||||
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login.page .form .usernameInput {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #fff;
|
||||
outline: none;
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.login.page .title {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
.login.page .usernameInput {
|
||||
font-size: 200%;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.login.page .title, .login.page .usernameInput {
|
||||
color: #fff;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* Chat page */
|
||||
|
||||
.chat.page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
.messages {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.inputMessage {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: gray;
|
||||
font-size: 70%;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
|
||||
.chatArea {
|
||||
height: 100%;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-y: scroll;
|
||||
padding: 10px 20px 10px 20px;
|
||||
}
|
||||
|
||||
.message.typing .messageBody {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
padding-right: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
|
||||
.inputMessage {
|
||||
border: 10px solid #000;
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
left: 0;
|
||||
outline: none;
|
||||
padding-left: 10px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
|
||||
# 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.
|
||||
@@ -1,51 +0,0 @@
|
||||
|
||||
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"
|
||||
@@ -1,2 +0,0 @@
|
||||
FROM httpd:2.4-alpine
|
||||
COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf
|
||||
@@ -1,52 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -1,15 +0,0 @@
|
||||
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" ]
|
||||
@@ -1,82 +0,0 @@
|
||||
// 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
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "socket.io-chat",
|
||||
"version": "0.0.0",
|
||||
"description": "A simple chat client using socket.io",
|
||||
"main": "index.js",
|
||||
"author": "Grant Timmerman",
|
||||
"private": true,
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<!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>
|
||||
@@ -1,286 +0,0 @@
|
||||
$(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);
|
||||
})
|
||||
|
||||
});
|
||||
@@ -1,149 +0,0 @@
|
||||
/* Fix user-agent */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-weight: 300;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
html, input {
|
||||
font-family:
|
||||
"HelveticaNeue-Light",
|
||||
"Helvetica Neue Light",
|
||||
"Helvetica Neue",
|
||||
Helvetica,
|
||||
Arial,
|
||||
"Lucida Grande",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Pages */
|
||||
|
||||
.pages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Login Page */
|
||||
|
||||
.login.page {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.login.page .form {
|
||||
height: 100px;
|
||||
margin-top: -100px;
|
||||
position: absolute;
|
||||
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login.page .form .usernameInput {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #fff;
|
||||
outline: none;
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.login.page .title {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
.login.page .usernameInput {
|
||||
font-size: 200%;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.login.page .title, .login.page .usernameInput {
|
||||
color: #fff;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* Chat page */
|
||||
|
||||
.chat.page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
.messages {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.inputMessage {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: gray;
|
||||
font-size: 70%;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
|
||||
.chatArea {
|
||||
height: 100%;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-y: scroll;
|
||||
padding: 10px 20px 10px 20px;
|
||||
}
|
||||
|
||||
.message.typing .messageBody {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
padding-right: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
|
||||
.inputMessage {
|
||||
border: 10px solid #000;
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
left: 0;
|
||||
outline: none;
|
||||
padding-left: 10px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
|
||||
# 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.
|
||||
@@ -1,51 +0,0 @@
|
||||
|
||||
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"
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
@@ -1,35 +0,0 @@
|
||||
# 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;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
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" ]
|
||||
@@ -1,82 +0,0 @@
|
||||
// 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
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "socket.io-chat",
|
||||
"version": "0.0.0",
|
||||
"description": "A simple chat client using socket.io",
|
||||
"main": "index.js",
|
||||
"author": "Grant Timmerman",
|
||||
"private": true,
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<!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>
|
||||
@@ -1,286 +0,0 @@
|
||||
$(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);
|
||||
})
|
||||
|
||||
});
|
||||
@@ -1,149 +0,0 @@
|
||||
/* Fix user-agent */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-weight: 300;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
html, input {
|
||||
font-family:
|
||||
"HelveticaNeue-Light",
|
||||
"Helvetica Neue Light",
|
||||
"Helvetica Neue",
|
||||
Helvetica,
|
||||
Arial,
|
||||
"Lucida Grande",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Pages */
|
||||
|
||||
.pages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Login Page */
|
||||
|
||||
.login.page {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.login.page .form {
|
||||
height: 100px;
|
||||
margin-top: -100px;
|
||||
position: absolute;
|
||||
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login.page .form .usernameInput {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #fff;
|
||||
outline: none;
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.login.page .title {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
.login.page .usernameInput {
|
||||
font-size: 200%;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.login.page .title, .login.page .usernameInput {
|
||||
color: #fff;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* Chat page */
|
||||
|
||||
.chat.page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
.messages {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.inputMessage {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: gray;
|
||||
font-size: 70%;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
|
||||
.chatArea {
|
||||
height: 100%;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-y: scroll;
|
||||
padding: 10px 20px 10px 20px;
|
||||
}
|
||||
|
||||
.message.typing .messageBody {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
padding-right: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
|
||||
.inputMessage {
|
||||
border: 10px solid #000;
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
left: 0;
|
||||
outline: none;
|
||||
padding-left: 10px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
|
||||
# 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
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<!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>
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
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));
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
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));
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
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));
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
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));
|
||||
@@ -1,125 +0,0 @@
|
||||
|
||||
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;
|
||||
@@ -1,55 +0,0 @@
|
||||
|
||||
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));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
client1: './src/client1.js',
|
||||
client2: './src/client2.js',
|
||||
client3: './src/client3.js',
|
||||
client4: './src/client4.js'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../public'),
|
||||
filename: '[name].bundle.js'
|
||||
}
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
|
||||
# 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.
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
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));
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
|
||||
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
# 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.
|
||||
@@ -1,13 +0,0 @@
|
||||
<!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>
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
var socket = require('socket.io-client')('http://localhost:3000');
|
||||
|
||||
console.log('init');
|
||||
|
||||
socket.on('connect', onConnect);
|
||||
|
||||
function onConnect(){
|
||||
console.log('connect ' + socket.id);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
module.exports = function () { return function () {}; };
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
path: './dist',
|
||||
filename: 'app.js'
|
||||
},
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
|
||||
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'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
# 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
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
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));
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<!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>
|
||||
@@ -1,100 +0,0 @@
|
||||
'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;
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -1,44 +0,0 @@
|
||||
|
||||
/**
|
||||
* 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
69
gulpfile.js
@@ -1,69 +0,0 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
122
lib/client.js
122
lib/client.js
@@ -5,7 +5,6 @@
|
||||
|
||||
var parser = require('socket.io-parser');
|
||||
var debug = require('debug')('socket.io:client');
|
||||
var url = require('url');
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
@@ -17,19 +16,19 @@ module.exports = Client;
|
||||
* Client constructor.
|
||||
*
|
||||
* @param {Server} server instance
|
||||
* @param {Socket} conn
|
||||
* @param {Socket} connection
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Client(server, conn){
|
||||
this.server = server;
|
||||
this.conn = conn;
|
||||
this.encoder = server.encoder;
|
||||
this.decoder = new server.parser.Decoder();
|
||||
this.encoder = new parser.Encoder();
|
||||
this.decoder = new parser.Decoder();
|
||||
this.id = conn.id;
|
||||
this.request = conn.request;
|
||||
this.setup();
|
||||
this.sockets = {};
|
||||
this.sockets = [];
|
||||
this.nsps = {};
|
||||
this.connectBuffer = [];
|
||||
}
|
||||
@@ -55,32 +54,45 @@ Client.prototype.setup = function(){
|
||||
/**
|
||||
* Connects a client to a namespace.
|
||||
*
|
||||
* @param {String} name namespace
|
||||
* @param {String} namespace name
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.connect = function(name, query){
|
||||
debug('connecting to namespace %s', name);
|
||||
var nsp = this.server.nsps[name];
|
||||
if (!nsp) {
|
||||
this.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'});
|
||||
return;
|
||||
}
|
||||
|
||||
if ('/' != name && !this.nsps['/']) {
|
||||
this.connectBuffer.push(name);
|
||||
return;
|
||||
}
|
||||
|
||||
Client.prototype.connect = function(name){
|
||||
var self = this;
|
||||
var socket = nsp.add(this, query, function(){
|
||||
self.sockets[socket.id] = socket;
|
||||
self.nsps[nsp.name] = socket;
|
||||
debug('connecting to namespace %s', name);
|
||||
|
||||
if ('/' == nsp.name && self.connectBuffer.length > 0) {
|
||||
self.connectBuffer.forEach(self.connect, self);
|
||||
self.connectBuffer = [];
|
||||
function connectNamespace() {
|
||||
var nsp = self.server.of(name);
|
||||
if ('/' != name && !self.nsps['/']) {
|
||||
self.connectBuffer.push(name);
|
||||
return;
|
||||
}
|
||||
|
||||
var socket = nsp.add(self, function(){
|
||||
self.sockets.push(socket);
|
||||
self.nsps[nsp.name] = socket;
|
||||
|
||||
if ('/' == nsp.name && self.connectBuffer.length > 0) {
|
||||
self.connectBuffer.forEach(self.connect, self);
|
||||
self.connectBuffer = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (self.server.nsps[name]) {
|
||||
// Namespace already created, connect
|
||||
connectNamespace();
|
||||
return;
|
||||
}
|
||||
|
||||
self.server.checkNamespace(name, function(allow) {
|
||||
if (allow) {
|
||||
connectNamespace();
|
||||
return
|
||||
}
|
||||
|
||||
self.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -91,12 +103,12 @@ Client.prototype.connect = function(name, query){
|
||||
*/
|
||||
|
||||
Client.prototype.disconnect = function(){
|
||||
for (var id in this.sockets) {
|
||||
if (this.sockets.hasOwnProperty(id)) {
|
||||
this.sockets[id].disconnect();
|
||||
}
|
||||
var socket;
|
||||
// we don't use a for loop because the length of
|
||||
// `sockets` changes upon each iteration
|
||||
while (socket = this.sockets.shift()) {
|
||||
socket.disconnect();
|
||||
}
|
||||
this.sockets = {};
|
||||
this.close();
|
||||
};
|
||||
|
||||
@@ -107,9 +119,10 @@ Client.prototype.disconnect = function(){
|
||||
*/
|
||||
|
||||
Client.prototype.remove = function(socket){
|
||||
if (this.sockets.hasOwnProperty(socket.id)) {
|
||||
var nsp = this.sockets[socket.id].nsp.name;
|
||||
delete this.sockets[socket.id];
|
||||
var i = this.sockets.indexOf(socket);
|
||||
if (~i) {
|
||||
var nsp = this.sockets[i].nsp.name;
|
||||
this.sockets.splice(i, 1);
|
||||
delete this.nsps[nsp];
|
||||
} else {
|
||||
debug('ignoring remove for %s', socket.id);
|
||||
@@ -134,26 +147,29 @@ Client.prototype.close = function(){
|
||||
* Writes a packet to the transport.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @param {Object} opts
|
||||
* @param {Boolean} whether packet is already encoded
|
||||
* @param {Boolean} whether packet is volatile
|
||||
* @param {Boolean} whether packet should be compressed
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.packet = function(packet, opts){
|
||||
opts = opts || {};
|
||||
Client.prototype.packet = function(packet, preEncoded, volatile, compress){
|
||||
var self = this;
|
||||
|
||||
// this writes to the actual connection
|
||||
function writeToEngine(encodedPackets) {
|
||||
if (opts.volatile && !self.conn.transport.writable) return;
|
||||
if (volatile && !self.conn.transport.writable) return;
|
||||
for (var i = 0; i < encodedPackets.length; i++) {
|
||||
self.conn.write(encodedPackets[i], { compress: opts.compress });
|
||||
self.conn.write(encodedPackets[i], { compress: compress });
|
||||
}
|
||||
}
|
||||
|
||||
if ('open' == this.conn.readyState) {
|
||||
debug('writing packet %j', packet);
|
||||
if (!opts.preEncoded) { // not broadcasting, need to encode
|
||||
this.encoder.encode(packet, writeToEngine); // encode, then write results to engine
|
||||
if(!preEncoded) { // not broadcasting, need to encode
|
||||
this.encoder.encode(packet, function (encodedPackets) { // encode, then write results to engine
|
||||
writeToEngine(encodedPackets);
|
||||
});
|
||||
} else { // a broadcast pre-encodes a packet
|
||||
writeToEngine(packet);
|
||||
}
|
||||
@@ -185,13 +201,11 @@ Client.prototype.ondata = function(data){
|
||||
|
||||
Client.prototype.ondecoded = function(packet) {
|
||||
if (parser.CONNECT == packet.type) {
|
||||
this.connect(url.parse(packet.nsp).pathname, url.parse(packet.nsp, true).query);
|
||||
this.connect(packet.nsp);
|
||||
} else {
|
||||
var socket = this.nsps[packet.nsp];
|
||||
if (socket) {
|
||||
process.nextTick(function() {
|
||||
socket.onpacket(packet);
|
||||
});
|
||||
socket.onpacket(packet);
|
||||
} else {
|
||||
debug('no socket for namespace %s', packet.nsp);
|
||||
}
|
||||
@@ -201,17 +215,15 @@ Client.prototype.ondecoded = function(packet) {
|
||||
/**
|
||||
* Handles an error.
|
||||
*
|
||||
* @param {Object} err object
|
||||
* @param {Objcet} error object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.onerror = function(err){
|
||||
for (var id in this.sockets) {
|
||||
if (this.sockets.hasOwnProperty(id)) {
|
||||
this.sockets[id].onerror(err);
|
||||
}
|
||||
}
|
||||
this.conn.close();
|
||||
this.sockets.forEach(function(socket){
|
||||
socket.onerror(err);
|
||||
});
|
||||
this.onclose('client error');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -228,12 +240,10 @@ Client.prototype.onclose = function(reason){
|
||||
this.destroy();
|
||||
|
||||
// `nsps` and `sockets` are cleaned up seamlessly
|
||||
for (var id in this.sockets) {
|
||||
if (this.sockets.hasOwnProperty(id)) {
|
||||
this.sockets[id].onclose(reason);
|
||||
}
|
||||
var socket;
|
||||
while (socket = this.sockets.shift()) {
|
||||
socket.onclose(reason);
|
||||
}
|
||||
this.sockets = {};
|
||||
|
||||
this.decoder.destroy(); // clean up decoder
|
||||
};
|
||||
|
||||
221
lib/index.js
221
lib/index.js
@@ -5,16 +5,13 @@
|
||||
|
||||
var http = require('http');
|
||||
var read = require('fs').readFileSync;
|
||||
var path = require('path');
|
||||
var exists = require('fs').existsSync;
|
||||
var parse = require('url').parse;
|
||||
var engine = require('engine.io');
|
||||
var client = require('socket.io-client');
|
||||
var clientVersion = require('socket.io-client/package').version;
|
||||
var Client = require('./client');
|
||||
var 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');
|
||||
|
||||
@@ -28,29 +25,27 @@ module.exports = Server;
|
||||
* Socket.IO client source.
|
||||
*/
|
||||
|
||||
var clientSource = undefined;
|
||||
var clientSourceMap = undefined;
|
||||
var clientSource = read(require.resolve('socket.io-client/socket.io.js'), 'utf-8');
|
||||
|
||||
/**
|
||||
* Server constructor.
|
||||
*
|
||||
* @param {http.Server|Number|Object} srv http server, port or options
|
||||
* @param {Object} [opts]
|
||||
* @param {http.Server|Number|Object} http server, port or options
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Server(srv, opts){
|
||||
if (!(this instanceof Server)) return new Server(srv, opts);
|
||||
if ('object' == typeof srv && srv instanceof Object && !srv.listen) {
|
||||
if ('object' == typeof srv && !srv.listen) {
|
||||
opts = srv;
|
||||
srv = null;
|
||||
}
|
||||
opts = opts || {};
|
||||
this.nsps = {};
|
||||
this.nspValidators = [];
|
||||
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('/');
|
||||
@@ -60,8 +55,8 @@ function Server(srv, opts){
|
||||
/**
|
||||
* Server request verification function, that checks for allowed origins
|
||||
*
|
||||
* @param {http.IncomingMessage} req request
|
||||
* @param {Function} fn callback to be called with the result: `fn(err, success)`
|
||||
* @param {http.IncomingMessage} request
|
||||
* @param {Function} callback to be called with the result: `fn(err, success)`
|
||||
*/
|
||||
|
||||
Server.prototype.checkRequest = function(req, fn) {
|
||||
@@ -93,7 +88,7 @@ Server.prototype.checkRequest = function(req, fn) {
|
||||
/**
|
||||
* Sets/gets whether client code is being served.
|
||||
*
|
||||
* @param {Boolean} v whether to serve client code
|
||||
* @param {Boolean} whether to serve client code
|
||||
* @return {Server|Boolean} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
@@ -101,21 +96,6 @@ 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.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;
|
||||
};
|
||||
|
||||
@@ -131,7 +111,7 @@ var oldSettings = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Backwards compatibility.
|
||||
* Backwards compatiblity.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
@@ -158,10 +138,57 @@ Server.prototype.set = function(key, val){
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up server middleware to validate incoming namespaces not already created on the server.
|
||||
*
|
||||
* @return {Server} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.useNamespaceValidator = function(fn){
|
||||
this.nspValidators.push(fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the middleware for an incoming namespace not already created on the server.
|
||||
*
|
||||
* @param name of incomming namespace
|
||||
* @param {Function} last fn call in the middleware
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.checkNamespace = function(name, fn){
|
||||
var fns = this.nspValidators.slice(0);
|
||||
if (!fns.length) return fn(false);
|
||||
|
||||
var namespaceAllowed = false; // Deny unknown namespaces by default
|
||||
|
||||
function run(i){
|
||||
fns[i](name, function(err, allow){
|
||||
// upon error, short-circuit
|
||||
if (err) return fn(false);
|
||||
|
||||
// if one piece of middleware explicitly denies namespace, short-circuit
|
||||
if (allow === false) return fn(false);
|
||||
|
||||
namespaceAllowed = namespaceAllowed || allow === true;
|
||||
|
||||
// if no middleware left, summon callback
|
||||
if (!fns[i + 1]) return fn(namespaceAllowed);
|
||||
|
||||
// go on to next
|
||||
run(i + 1);
|
||||
});
|
||||
}
|
||||
|
||||
run(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the client serving path.
|
||||
*
|
||||
* @param {String} v pathname
|
||||
* @param {String} pathname
|
||||
* @return {Server|String} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
@@ -175,7 +202,7 @@ Server.prototype.path = function(v){
|
||||
/**
|
||||
* Sets the adapter for rooms.
|
||||
*
|
||||
* @param {Adapter} v pathname
|
||||
* @param {Adapter} pathname
|
||||
* @return {Server|Adapter} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
@@ -194,7 +221,7 @@ Server.prototype.adapter = function(v){
|
||||
/**
|
||||
* Sets the allowed origins for requests.
|
||||
*
|
||||
* @param {String} v origins
|
||||
* @param {String} origins
|
||||
* @return {Server|Adapter} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
@@ -243,29 +270,20 @@ Server.prototype.attach = function(srv, opts){
|
||||
opts = opts || {};
|
||||
opts.path = opts.path || this.path();
|
||||
// set origins verification
|
||||
opts.allowRequest = opts.allowRequest || this.checkRequest.bind(this);
|
||||
opts.allowRequest = this.checkRequest.bind(this);
|
||||
|
||||
var self = this;
|
||||
// initialize engine
|
||||
debug('creating engine.io instance with opts %j', opts);
|
||||
this.eio = engine.attach(srv, opts);
|
||||
|
||||
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;
|
||||
// attach static file serving
|
||||
if (this._serveClient) this.attachServe(srv);
|
||||
|
||||
// initialize engine
|
||||
debug('creating engine.io instance with opts %j', opts);
|
||||
self.eio = engine.attach(srv, opts);
|
||||
// Export http server
|
||||
this.httpServer = srv;
|
||||
|
||||
// attach static file serving
|
||||
if (self._serveClient) self.attachServe(srv);
|
||||
|
||||
// Export http server
|
||||
self.httpServer = srv;
|
||||
|
||||
// bind to engine events
|
||||
self.bind(self.eio);
|
||||
});
|
||||
// bind to engine events
|
||||
this.bind(this.eio);
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -273,21 +291,18 @@ Server.prototype.attach = function(srv, opts){
|
||||
/**
|
||||
* Attaches the static file serving.
|
||||
*
|
||||
* @param {Function|http.Server} srv http server
|
||||
* @param {Function|http.Server} http server
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.attachServe = function(srv){
|
||||
debug('attaching client serving req handler');
|
||||
var url = this._path + '/socket.io.js';
|
||||
var urlMap = this._path + '/socket.io.js.map';
|
||||
var evs = srv.listeners('request').slice(0);
|
||||
var self = this;
|
||||
srv.removeAllListeners('request');
|
||||
srv.on('request', function(req, res) {
|
||||
if (0 === req.url.indexOf(urlMap)) {
|
||||
self.serveMap(req, res);
|
||||
} else if (0 === req.url.indexOf(url)) {
|
||||
if (0 === req.url.indexOf(url)) {
|
||||
self.serve(req, res);
|
||||
} else {
|
||||
for (var i = 0; i < evs.length; i++) {
|
||||
@@ -306,13 +321,9 @@ 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 (expectedEtag == etag) {
|
||||
if (clientVersion == etag) {
|
||||
debug('serve client 304');
|
||||
res.writeHead(304);
|
||||
res.end();
|
||||
@@ -322,45 +333,15 @@ Server.prototype.serve = function(req, res){
|
||||
|
||||
debug('serve client source');
|
||||
res.setHeader('Content-Type', 'application/javascript');
|
||||
res.setHeader('ETag', expectedEtag);
|
||||
res.setHeader('ETag', clientVersion);
|
||||
res.writeHead(200);
|
||||
res.end(clientSource);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a request serving `/socket.io.js.map`
|
||||
*
|
||||
* @param {http.Request} req
|
||||
* @param {http.Response} res
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.serveMap = function(req, res){
|
||||
// Per the standard, ETags must be quoted:
|
||||
// https://tools.ietf.org/html/rfc7232#section-2.3
|
||||
var expectedEtag = '"' + clientVersion + '"';
|
||||
|
||||
var etag = req.headers['if-none-match'];
|
||||
if (etag) {
|
||||
if (expectedEtag == etag) {
|
||||
debug('serve client 304');
|
||||
res.writeHead(304);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
debug('serve client sourcemap');
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('ETag', expectedEtag);
|
||||
res.writeHead(200);
|
||||
res.end(clientSourceMap);
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds socket.io to an engine.io instance.
|
||||
*
|
||||
* @param {engine.Server} engine engine.io (or compatible) server
|
||||
* @param {engine.Server} engine.io (or compatible) server
|
||||
* @return {Server} self
|
||||
* @api public
|
||||
*/
|
||||
@@ -374,7 +355,7 @@ Server.prototype.bind = function(engine){
|
||||
/**
|
||||
* Called with each incoming transport connection.
|
||||
*
|
||||
* @param {engine.Socket} conn
|
||||
* @param {engine.Socket} socket
|
||||
* @return {Server} self
|
||||
* @api public
|
||||
*/
|
||||
@@ -389,44 +370,38 @@ Server.prototype.onconnection = function(conn){
|
||||
/**
|
||||
* Looks up a namespace.
|
||||
*
|
||||
* @param {String} name nsp name
|
||||
* @param {Function} [fn] optional, nsp `connection` ev handler
|
||||
* @param {String} nsp name
|
||||
* @param {Function} optional, nsp `connection` ev handler
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.of = function(name, fn){
|
||||
if (String(name)[0] !== '/') name = '/' + name;
|
||||
|
||||
var nsp = this.nsps[name];
|
||||
if (!nsp) {
|
||||
|
||||
if (!this.nsps[name]) {
|
||||
debug('initializing namespace %s', name);
|
||||
nsp = new Namespace(this, name);
|
||||
var nsp = new Namespace(this, name);
|
||||
this.nsps[name] = nsp;
|
||||
}
|
||||
if (fn) nsp.on('connect', fn);
|
||||
return nsp;
|
||||
if (fn) this.nsps[name].on('connect', fn);
|
||||
return this.nsps[name];
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes server connection
|
||||
*
|
||||
* @param {Function} [fn] optional, called as `fn([err])` on error OR all conns closed
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.close = function(fn){
|
||||
for (var id in this.nsps['/'].sockets) {
|
||||
if (this.nsps['/'].sockets.hasOwnProperty(id)) {
|
||||
this.nsps['/'].sockets[id].onclose();
|
||||
}
|
||||
}
|
||||
Server.prototype.close = function(){
|
||||
this.nsps['/'].sockets.forEach(function(socket){
|
||||
socket.onclose();
|
||||
});
|
||||
|
||||
this.engine.close();
|
||||
|
||||
if (this.httpServer) {
|
||||
this.httpServer.close(fn);
|
||||
} else {
|
||||
fn && fn();
|
||||
if(this.httpServer){
|
||||
this.httpServer.close();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -434,23 +409,17 @@ Server.prototype.close = function(fn){
|
||||
* Expose main namespace (/).
|
||||
*/
|
||||
|
||||
var emitterMethods = Object.keys(Emitter.prototype).filter(function(key){
|
||||
return typeof Emitter.prototype[key] === 'function';
|
||||
});
|
||||
|
||||
emitterMethods.concat(['to', 'in', 'use', 'send', 'write', 'clients', 'compress']).forEach(function(fn){
|
||||
['on', 'to', 'in', 'use', 'emit', 'send', 'write', 'clients'].forEach(function(fn){
|
||||
Server.prototype[fn] = function(){
|
||||
return this.sockets[fn].apply(this.sockets, arguments);
|
||||
var nsp = this.sockets[fn];
|
||||
return nsp.apply(this.sockets, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
Namespace.flags.forEach(function(flag){
|
||||
Object.defineProperty(Server.prototype, flag, {
|
||||
get: function() {
|
||||
this.sockets.flags = this.sockets.flags || {};
|
||||
this.sockets.flags[flag] = true;
|
||||
return this;
|
||||
}
|
||||
Server.prototype.__defineGetter__(flag, function(name){
|
||||
this.flags.push(name);
|
||||
return this;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ 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.
|
||||
@@ -28,11 +29,7 @@ exports.events = [
|
||||
* Flags.
|
||||
*/
|
||||
|
||||
exports.flags = [
|
||||
'json',
|
||||
'volatile',
|
||||
'local'
|
||||
];
|
||||
exports.flags = ['json'];
|
||||
|
||||
/**
|
||||
* `EventEmitter#emit` reference.
|
||||
@@ -51,12 +48,11 @@ var emit = Emitter.prototype.emit;
|
||||
function Namespace(server, name){
|
||||
this.name = name;
|
||||
this.server = server;
|
||||
this.sockets = {};
|
||||
this.sockets = [];
|
||||
this.connected = {};
|
||||
this.fns = [];
|
||||
this.ids = 0;
|
||||
this.rooms = [];
|
||||
this.flags = {};
|
||||
this.acks = {};
|
||||
this.initAdapter();
|
||||
}
|
||||
|
||||
@@ -71,11 +67,10 @@ Namespace.prototype.__proto__ = Emitter.prototype;
|
||||
*/
|
||||
|
||||
exports.flags.forEach(function(flag){
|
||||
Object.defineProperty(Namespace.prototype, flag, {
|
||||
get: function() {
|
||||
this.flags[flag] = true;
|
||||
return this;
|
||||
}
|
||||
Namespace.prototype.__defineGetter__(flag, function(){
|
||||
this.flags = this.flags || {};
|
||||
this.flags[flag] = true;
|
||||
return this;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,7 +102,7 @@ Namespace.prototype.use = function(fn){
|
||||
* Executes the middleware for an incoming client.
|
||||
*
|
||||
* @param {Socket} socket that will get added
|
||||
* @param {Function} fn last fn call in the middleware
|
||||
* @param {Function} last fn call in the middleware
|
||||
* @api private
|
||||
*/
|
||||
|
||||
@@ -140,7 +135,8 @@ Namespace.prototype.run = function(socket, fn){
|
||||
*/
|
||||
|
||||
Namespace.prototype.to =
|
||||
Namespace.prototype.in = function(name){
|
||||
Namespace.prototype['in'] = function(name){
|
||||
this.rooms = this.rooms || [];
|
||||
if (!~this.rooms.indexOf(name)) this.rooms.push(name);
|
||||
return this;
|
||||
};
|
||||
@@ -152,9 +148,9 @@ Namespace.prototype.in = function(name){
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Namespace.prototype.add = function(client, query, fn){
|
||||
Namespace.prototype.add = function(client, fn){
|
||||
debug('adding socket to nsp %s', this.name);
|
||||
var socket = new Socket(this, client, query);
|
||||
var socket = new Socket(this, client);
|
||||
var self = this;
|
||||
this.run(socket, function(err){
|
||||
process.nextTick(function(){
|
||||
@@ -162,7 +158,7 @@ Namespace.prototype.add = function(client, query, fn){
|
||||
if (err) return socket.error(err.data || err.message);
|
||||
|
||||
// track socket
|
||||
self.sockets[socket.id] = socket;
|
||||
self.sockets.push(socket);
|
||||
|
||||
// it's paramount that the internal `onconnect` logic
|
||||
// fires before user-set events to prevent state order
|
||||
@@ -189,8 +185,9 @@ Namespace.prototype.add = function(client, query, fn){
|
||||
*/
|
||||
|
||||
Namespace.prototype.remove = function(socket){
|
||||
if (this.sockets.hasOwnProperty(socket.id)) {
|
||||
delete this.sockets[socket.id];
|
||||
var i = this.sockets.indexOf(socket);
|
||||
if (~i) {
|
||||
this.sockets.splice(i, 1);
|
||||
} else {
|
||||
debug('ignoring remove for %s', socket.id);
|
||||
}
|
||||
@@ -209,7 +206,10 @@ Namespace.prototype.emit = function(ev){
|
||||
} else {
|
||||
// set up packet object
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var packet = { type: parser.EVENT, data: args };
|
||||
var parserType = parser.EVENT; // default
|
||||
if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary
|
||||
|
||||
var packet = { type: parserType, data: args };
|
||||
|
||||
if ('function' == typeof args[args.length - 1]) {
|
||||
throw new Error('Callbacks are not supported when broadcasting');
|
||||
@@ -220,8 +220,8 @@ Namespace.prototype.emit = function(ev){
|
||||
flags: this.flags
|
||||
});
|
||||
|
||||
this.rooms = [];
|
||||
this.flags = {};
|
||||
delete this.rooms;
|
||||
delete this.flags;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
@@ -250,21 +250,5 @@ Namespace.prototype.write = function(){
|
||||
|
||||
Namespace.prototype.clients = function(fn){
|
||||
this.adapter.clients(this.rooms, fn);
|
||||
// reset rooms for scenario:
|
||||
// .in('room').clients() (GH-1978)
|
||||
this.rooms = [];
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the compress flag.
|
||||
*
|
||||
* @param {Boolean} compress if `true`, compresses the sending data
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Namespace.prototype.compress = function(compress){
|
||||
this.flags.compress = compress;
|
||||
return this;
|
||||
};
|
||||
|
||||
199
lib/socket.js
199
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 assign = require('object-assign');
|
||||
var hasBin = require('has-binary');
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
@@ -25,7 +25,6 @@ exports.events = [
|
||||
'error',
|
||||
'connect',
|
||||
'disconnect',
|
||||
'disconnecting',
|
||||
'newListener',
|
||||
'removeListener'
|
||||
];
|
||||
@@ -56,21 +55,19 @@ var emit = Emitter.prototype.emit;
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Socket(nsp, client, query){
|
||||
function Socket(nsp, client){
|
||||
this.nsp = nsp;
|
||||
this.server = nsp.server;
|
||||
this.adapter = this.nsp.adapter;
|
||||
this.id = nsp.name !== '/' ? nsp.name + '#' + client.id : client.id;
|
||||
this.id = client.id;
|
||||
this.request = client.request;
|
||||
this.client = client;
|
||||
this.conn = client.conn;
|
||||
this.rooms = {};
|
||||
this.rooms = [];
|
||||
this.acks = {};
|
||||
this.connected = true;
|
||||
this.disconnected = false;
|
||||
this.handshake = this.buildHandshake(query);
|
||||
this.fns = [];
|
||||
this.flags = {};
|
||||
this._rooms = [];
|
||||
this.handshake = this.buildHandshake();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,24 +81,21 @@ Socket.prototype.__proto__ = Emitter.prototype;
|
||||
*/
|
||||
|
||||
flags.forEach(function(flag){
|
||||
Object.defineProperty(Socket.prototype, flag, {
|
||||
get: function() {
|
||||
this.flags[flag] = true;
|
||||
return this;
|
||||
}
|
||||
Socket.prototype.__defineGetter__(flag, function(){
|
||||
this.flags = this.flags || {};
|
||||
this.flags[flag] = true;
|
||||
return this;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* `request` engine.io shortcut.
|
||||
* `request` engine.io shorcut.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Object.defineProperty(Socket.prototype, 'request', {
|
||||
get: function() {
|
||||
return this.conn.request;
|
||||
}
|
||||
Socket.prototype.__defineGetter__('request', function(){
|
||||
return this.conn.request;
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -110,13 +104,7 @@ Object.defineProperty(Socket.prototype, 'request', {
|
||||
* @api private
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
Socket.prototype.buildHandshake = function(){
|
||||
return {
|
||||
headers: this.request.headers,
|
||||
time: (new Date) + '',
|
||||
@@ -125,7 +113,7 @@ Socket.prototype.buildHandshake = function(query){
|
||||
secure: !!this.request.connection.encrypted,
|
||||
issued: +(new Date),
|
||||
url: this.request.url,
|
||||
query: buildQuery()
|
||||
query: url.parse(this.request.url, true).query || {}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -141,14 +129,13 @@ Socket.prototype.emit = function(ev){
|
||||
emit.apply(this, arguments);
|
||||
} else {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var packet = {
|
||||
type: parser.EVENT,
|
||||
data: args
|
||||
};
|
||||
var packet = {};
|
||||
packet.type = hasBin(args) ? parser.BINARY_EVENT : parser.EVENT;
|
||||
packet.data = args;
|
||||
|
||||
// access last argument to see if it's an ACK callback
|
||||
if (typeof args[args.length - 1] === 'function') {
|
||||
if (this._rooms.length || this.flags.broadcast) {
|
||||
if ('function' == typeof args[args.length - 1]) {
|
||||
if (this._rooms || (this.flags && this.flags.broadcast)) {
|
||||
throw new Error('Callbacks are not supported when broadcasting');
|
||||
}
|
||||
|
||||
@@ -157,7 +144,7 @@ Socket.prototype.emit = function(ev){
|
||||
packet.id = this.nsp.ids++;
|
||||
}
|
||||
|
||||
if (this._rooms.length || this.flags.broadcast) {
|
||||
if (this._rooms || (this.flags && this.flags.broadcast)) {
|
||||
this.adapter.broadcast(packet, {
|
||||
except: [this.id],
|
||||
rooms: this._rooms,
|
||||
@@ -165,12 +152,12 @@ Socket.prototype.emit = function(ev){
|
||||
});
|
||||
} else {
|
||||
// dispatch packet
|
||||
this.packet(packet, this.flags);
|
||||
this.packet(packet);
|
||||
}
|
||||
|
||||
// reset flags
|
||||
this._rooms = [];
|
||||
this.flags = {};
|
||||
delete this._rooms;
|
||||
delete this.flags;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
@@ -185,6 +172,7 @@ 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;
|
||||
};
|
||||
@@ -208,45 +196,33 @@ Socket.prototype.write = function(){
|
||||
* Writes a packet.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @param {Object} opts options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.packet = function(packet, opts){
|
||||
Socket.prototype.packet = function(packet, preEncoded){
|
||||
packet.nsp = this.nsp.name;
|
||||
opts = opts || {};
|
||||
opts.compress = false !== opts.compress;
|
||||
this.client.packet(packet, opts);
|
||||
var volatile = this.flags && this.flags.volatile;
|
||||
var compress = !this.flags || false !== this.flags.compress;
|
||||
this.client.packet(packet, preEncoded, volatile, compress);
|
||||
};
|
||||
|
||||
/**
|
||||
* Joins a room.
|
||||
*
|
||||
* @param {String|Array} room or array of rooms
|
||||
* @param {Function} fn optional, callback
|
||||
* @param {String} room
|
||||
* @param {Function} optional, callback
|
||||
* @return {Socket} self
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.join = function(rooms, fn){
|
||||
debug('joining room %s', rooms);
|
||||
Socket.prototype.join = function(room, fn){
|
||||
debug('joining room %s', room);
|
||||
var self = this;
|
||||
if (!Array.isArray(rooms)) {
|
||||
rooms = [rooms];
|
||||
}
|
||||
rooms = rooms.filter(function (room) {
|
||||
return !self.rooms.hasOwnProperty(room);
|
||||
});
|
||||
if (!rooms.length) {
|
||||
fn && fn(null);
|
||||
return this;
|
||||
}
|
||||
this.adapter.addAll(this.id, rooms, function(err){
|
||||
if (~this.rooms.indexOf(room)) return this;
|
||||
this.adapter.add(this.id, room, function(err){
|
||||
if (err) return fn && fn(err);
|
||||
debug('joined room %s', rooms);
|
||||
rooms.forEach(function (room) {
|
||||
self.rooms[room] = room;
|
||||
});
|
||||
debug('joined room %s', room);
|
||||
self.rooms.push(room);
|
||||
fn && fn(null);
|
||||
});
|
||||
return this;
|
||||
@@ -256,7 +232,7 @@ Socket.prototype.join = function(rooms, fn){
|
||||
* Leaves a room.
|
||||
*
|
||||
* @param {String} room
|
||||
* @param {Function} fn optional, callback
|
||||
* @param {Function} optional, callback
|
||||
* @return {Socket} self
|
||||
* @api private
|
||||
*/
|
||||
@@ -267,7 +243,10 @@ Socket.prototype.leave = function(room, fn){
|
||||
this.adapter.del(this.id, room, function(err){
|
||||
if (err) return fn && fn(err);
|
||||
debug('left room %s', room);
|
||||
delete self.rooms[room];
|
||||
var idx = self.rooms.indexOf(room);
|
||||
if (idx >= 0) {
|
||||
self.rooms.splice(idx, 1);
|
||||
}
|
||||
fn && fn(null);
|
||||
});
|
||||
return this;
|
||||
@@ -281,27 +260,21 @@ Socket.prototype.leave = function(room, fn){
|
||||
|
||||
Socket.prototype.leaveAll = function(){
|
||||
this.adapter.delAll(this.id);
|
||||
this.rooms = {};
|
||||
this.rooms = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by `Namespace` upon successful
|
||||
* Called by `Namespace` upon succesful
|
||||
* middleware execution (ie: authorization).
|
||||
* Socket is added to namespace array before
|
||||
* call to join, so adapters can access it.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onconnect = function(){
|
||||
debug('socket connected - writing packet');
|
||||
this.nsp.connected[this.id] = this;
|
||||
this.join(this.id);
|
||||
// the CONNECT packet for the default namespace
|
||||
// has already been sent as an `initialPacket`
|
||||
if (this.nsp.name !== '/') {
|
||||
this.packet({ type: parser.CONNECT });
|
||||
}
|
||||
this.packet({ type: parser.CONNECT });
|
||||
this.nsp.connected[this.id] = this;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -355,13 +328,13 @@ Socket.prototype.onevent = function(packet){
|
||||
args.push(this.ack(packet.id));
|
||||
}
|
||||
|
||||
this.dispatch(args);
|
||||
emit.apply(this, args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Produces an ack callback to emit with an event.
|
||||
*
|
||||
* @param {Number} id packet id
|
||||
* @param {Number} packet id
|
||||
* @api private
|
||||
*/
|
||||
|
||||
@@ -374,13 +347,12 @@ 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: parser.ACK,
|
||||
type: type,
|
||||
data: args
|
||||
});
|
||||
|
||||
sent = true;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -431,14 +403,13 @@ Socket.prototype.onerror = function(err){
|
||||
* Called upon closing. Called by `Client`.
|
||||
*
|
||||
* @param {String} reason
|
||||
* @throw {Error} optional error object
|
||||
* @param {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);
|
||||
@@ -451,7 +422,7 @@ Socket.prototype.onclose = function(reason){
|
||||
/**
|
||||
* Produces an `error` packet.
|
||||
*
|
||||
* @param {Object} err error object
|
||||
* @param {Object} error object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
@@ -462,7 +433,7 @@ Socket.prototype.error = function(err){
|
||||
/**
|
||||
* Disconnects this client.
|
||||
*
|
||||
* @param {Boolean} close if `true`, closes the underlying connection
|
||||
* @param {Boolean} if `true`, closes the underlying connection
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
@@ -481,73 +452,13 @@ Socket.prototype.disconnect = function(close){
|
||||
/**
|
||||
* Sets the compress flag.
|
||||
*
|
||||
* @param {Boolean} compress if `true`, compresses the sending data
|
||||
* @param {Boolean} if `true`, compresses the sending data
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.compress = function(compress){
|
||||
this.flags = this.flags || {};
|
||||
this.flags.compress = compress;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
|
||||
38
package.json
38
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "socket.io",
|
||||
"version": "2.0.1",
|
||||
"version": "1.3.2",
|
||||
"description": "node.js realtime framework server",
|
||||
"keywords": [
|
||||
"realtime",
|
||||
@@ -11,39 +11,27 @@
|
||||
"socket",
|
||||
"io"
|
||||
],
|
||||
"main": "./lib/index",
|
||||
"files": [
|
||||
"lib/"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/socketio/socket.io"
|
||||
"url": "git://github.com/Automattic/socket.io"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "gulp test"
|
||||
"test": "mocha --reporter dot --slow 200ms --bail"
|
||||
},
|
||||
"dependencies": {
|
||||
"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.1",
|
||||
"socket.io-parser": "~3.1.1"
|
||||
"engine.io": "automattic/engine.io#ddc64a",
|
||||
"socket.io-parser": "2.2.3",
|
||||
"socket.io-client": "automattic/socket.io-client#210d65",
|
||||
"socket.io-adapter": "automattic/socket.io-adapter#ae79d8",
|
||||
"has-binary": "0.1.6",
|
||||
"debug": "2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"del": "^2.2.2",
|
||||
"mocha": "1.16.2",
|
||||
"expect.js": "0.3.1",
|
||||
"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"
|
||||
"supertest": "0.8.2",
|
||||
"superagent": "0.17.0",
|
||||
"istanbul": "0.2.3"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
|
||||
11
test/fixtures/server-close.js
vendored
11
test/fixtures/server-close.js
vendored
@@ -1,11 +0,0 @@
|
||||
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,15 +1,8 @@
|
||||
var testVersion = process.env.TEST_VERSION;
|
||||
|
||||
var http = require('http').Server;
|
||||
var io;
|
||||
if (testVersion === 'compat') {
|
||||
console.log('testing compat version');
|
||||
io = require('../dist');
|
||||
} else {
|
||||
io = require('../lib');
|
||||
}
|
||||
var io = require('..');
|
||||
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');
|
||||
@@ -87,6 +80,9 @@ 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();
|
||||
@@ -164,7 +160,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();
|
||||
@@ -176,7 +172,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);
|
||||
@@ -248,7 +244,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(403);
|
||||
expect(res.status).to.be(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -259,7 +255,7 @@ describe('socket.io', function(){
|
||||
.query({ transport: 'polling' })
|
||||
.set('origin', 'http://herp.derp')
|
||||
.end(function (err, res) {
|
||||
expect(res.status).to.be(403);
|
||||
expect(res.status).to.be(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -302,7 +298,7 @@ describe('socket.io', function(){
|
||||
.set('origin', 'http://herp.derp')
|
||||
.query({ transport: 'polling' })
|
||||
.end(function (err, res) {
|
||||
expect(res.status).to.be(403);
|
||||
expect(res.status).to.be(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -332,32 +328,6 @@ describe('socket.io', function(){
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow request if custom function in opts.allowRequest returns true', function(done){
|
||||
var sockets = io(http().listen(54022), { allowRequest: function (req, callback) {
|
||||
return callback(null, true);
|
||||
}, origins: 'http://foo.example:*' });
|
||||
|
||||
request.get('http://localhost:54022/socket.io/default/')
|
||||
.query({ transport: 'polling' })
|
||||
.end(function (err, res) {
|
||||
expect(res.status).to.be(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should disallow request if custom function in opts.allowRequest returns false', function(done){
|
||||
var sockets = io(http().listen(54023), { allowRequest: function (req, callback) {
|
||||
return callback(null, false);
|
||||
} });
|
||||
request.get('http://localhost:54023/socket.io/default/')
|
||||
.set('origin', 'http://foo.example')
|
||||
.query({ transport: 'polling' })
|
||||
.end(function (err, res) {
|
||||
expect(res.status).to.be(403);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('close', function(){
|
||||
@@ -372,12 +342,12 @@ describe('socket.io', function(){
|
||||
var clientSocket = client(srv, { reconnection: false });
|
||||
|
||||
clientSocket.on('disconnect', function init() {
|
||||
expect(Object.keys(sio.nsps['/'].sockets).length).to.equal(0);
|
||||
expect(sio.nsps['/'].sockets.length).to.equal(0);
|
||||
server.listen(PORT);
|
||||
});
|
||||
|
||||
clientSocket.on('connect', function init() {
|
||||
expect(Object.keys(sio.nsps['/'].sockets).length).to.equal(1);
|
||||
expect(sio.nsps['/'].sockets.length).to.equal(1);
|
||||
sio.close();
|
||||
});
|
||||
|
||||
@@ -396,15 +366,15 @@ describe('socket.io', function(){
|
||||
var net = require('net');
|
||||
var server = net.createServer();
|
||||
|
||||
var clientSocket = ioc('ws://0.0.0.0:' + PORT, { reconnection: false });
|
||||
var clientSocket = ioc('ws://0.0.0.0:' + PORT);
|
||||
|
||||
clientSocket.on('disconnect', function init() {
|
||||
expect(Object.keys(sio.nsps['/'].sockets).length).to.equal(0);
|
||||
expect(sio.nsps['/'].sockets.length).to.equal(0);
|
||||
server.listen(PORT);
|
||||
});
|
||||
|
||||
clientSocket.on('connect', function init() {
|
||||
expect(Object.keys(sio.nsps['/'].sockets).length).to.equal(1);
|
||||
expect(sio.nsps['/'].sockets.length).to.equal(1);
|
||||
sio.close();
|
||||
});
|
||||
|
||||
@@ -416,31 +386,12 @@ 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;
|
||||
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');
|
||||
}
|
||||
var Socket = require('../lib/socket');
|
||||
var Namespace = require('../lib/namespace');
|
||||
|
||||
it('should be accessible through .sockets', function(){
|
||||
var sio = io();
|
||||
expect(sio.sockets).to.be.a(Namespace);
|
||||
@@ -454,12 +405,6 @@ describe('socket.io', function(){
|
||||
expect(sio.emit).to.be.a('function');
|
||||
expect(sio.send).to.be.a('function');
|
||||
expect(sio.write).to.be.a('function');
|
||||
expect(sio.clients).to.be.a('function');
|
||||
expect(sio.compress).to.be.a('function');
|
||||
expect(sio.json).to.be(sio);
|
||||
expect(sio.volatile).to.be(sio);
|
||||
expect(sio.sockets.flags).to.eql({ json: true, volatile: true });
|
||||
delete sio.sockets.flags;
|
||||
});
|
||||
|
||||
it('should automatically connect', function(done){
|
||||
@@ -641,31 +586,6 @@ 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);
|
||||
@@ -677,7 +597,7 @@ describe('socket.io', function(){
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should not reuse same-namespace connections', function(done){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
@@ -688,7 +608,7 @@ describe('socket.io', function(){
|
||||
var clientSocket2 = client(srv);
|
||||
sio.on('connection', function() {
|
||||
connections++;
|
||||
if (connections === 2) {
|
||||
if(connections === 2) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -716,7 +636,7 @@ describe('socket.io', function(){
|
||||
});
|
||||
function getClients() {
|
||||
sio.of('/chat').clients(function(error, sids) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(error).to.be.undefined;
|
||||
expect(sids).to.contain(chatSids[0]);
|
||||
expect(sids).to.contain(chatSids[1]);
|
||||
expect(sids).to.not.contain(otherSid);
|
||||
@@ -759,7 +679,7 @@ describe('socket.io', function(){
|
||||
});
|
||||
function getClients() {
|
||||
sio.of('/chat').in('foo').clients(function(error, sids) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(error).to.be.undefined;
|
||||
expect(sids).to.contain(chatFooSid);
|
||||
expect(sids).to.not.contain(chatBarSid);
|
||||
expect(sids).to.not.contain(otherSid);
|
||||
@@ -802,7 +722,7 @@ describe('socket.io', function(){
|
||||
});
|
||||
function getClients() {
|
||||
sio.of('/chat').clients(function(error, sids) {
|
||||
expect(error).to.not.be.ok();
|
||||
expect(error).to.be.undefined;
|
||||
expect(sids).to.contain(chatFooSid);
|
||||
expect(sids).to.contain(chatBarSid);
|
||||
expect(sids).to.not.contain(otherSid);
|
||||
@@ -811,83 +731,74 @@ describe('socket.io', function(){
|
||||
}
|
||||
});
|
||||
|
||||
it('should not emit volatile event after regular event', function(done) {
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
|
||||
var counter = 0;
|
||||
srv.listen(function(){
|
||||
sio.of('/chat').on('connection', function(s){
|
||||
// Wait to make sure there are no packets being sent for opening the connection
|
||||
setTimeout(function() {
|
||||
sio.of('/chat').emit('ev', 'data');
|
||||
sio.of('/chat').volatile.emit('ev', 'data');
|
||||
}, 50);
|
||||
});
|
||||
|
||||
var socket = client(srv, '/chat');
|
||||
socket.on('ev', function() {
|
||||
counter++;
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
expect(counter).to.be(1);
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it('should emit volatile event', function(done) {
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
|
||||
var counter = 0;
|
||||
srv.listen(function(){
|
||||
sio.of('/chat').on('connection', function(s){
|
||||
// Wait to make sure there are no packets being sent for opening the connection
|
||||
setTimeout(function() {
|
||||
sio.of('/chat').volatile.emit('ev', 'data');
|
||||
}, 100);
|
||||
});
|
||||
|
||||
var socket = client(srv, '/chat');
|
||||
socket.on('ev', function() {
|
||||
counter++;
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
expect(counter).to.be(1);
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it('should enable compression by default', function(done){
|
||||
it('should allow connections to dynamic namespaces', function(done){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var socket = client(srv, '/chat');
|
||||
sio.of('/chat').on('connection', function(s){
|
||||
s.conn.once('packetCreate', function(packet) {
|
||||
expect(packet.options.compress).to.be(true);
|
||||
done();
|
||||
});
|
||||
sio.of('/chat').emit('woot', 'hi');
|
||||
var namespace = '/dynamic';
|
||||
var dynamic = client(srv,namespace);
|
||||
sio.useNamespaceValidator(function(nsp, next) {
|
||||
expect(nsp).to.be(namespace);
|
||||
next(null, true);
|
||||
});
|
||||
dynamic.on('error', function(err) {
|
||||
expect().fail();
|
||||
});
|
||||
dynamic.on('connect', function() {
|
||||
expect(sio.nsps[namespace]).to.be.a(Namespace);
|
||||
expect(sio.nsps[namespace].sockets.length).to.be(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable compression', function(done){
|
||||
it('should not allow connections to dynamic namespaces if not supported', function(done){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var socket = client(srv, '/chat');
|
||||
sio.of('/chat').on('connection', function(s){
|
||||
s.conn.once('packetCreate', function(packet) {
|
||||
expect(packet.options.compress).to.be(false);
|
||||
done();
|
||||
});
|
||||
sio.of('/chat').compress(false).emit('woot', 'hi');
|
||||
var namespace = '/dynamic';
|
||||
sio.useNamespaceValidator(function(nsp, next) {
|
||||
expect(nsp).to.be(namespace);
|
||||
next(null, false);
|
||||
});
|
||||
sio.on('connect', function(socket) {
|
||||
if (socket.nsp.name === namespace) {
|
||||
expect().fail();
|
||||
}
|
||||
});
|
||||
|
||||
var dynamic = client(srv,namespace);
|
||||
dynamic.on('connect', function(){
|
||||
expect().fail();
|
||||
});
|
||||
dynamic.on('error', function(err) {
|
||||
expect(err).to.be("Invalid namespace");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should not allow connections to dynamic namespaces if there is an error', function(done){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var namespace = '/dynamic';
|
||||
sio.useNamespaceValidator(function(nsp, next) {
|
||||
expect(nsp).to.be(namespace);
|
||||
next(new Error(), true);
|
||||
});
|
||||
sio.on('connect', function(socket) {
|
||||
if (socket.nsp.name === namespace) {
|
||||
expect().fail();
|
||||
}
|
||||
});
|
||||
|
||||
var dynamic = client(srv,namespace);
|
||||
dynamic.on('connect', function(){
|
||||
expect().fail();
|
||||
});
|
||||
dynamic.on('error', function(err) {
|
||||
expect(err).to.be("Invalid namespace");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -978,12 +889,12 @@ describe('socket.io', function(){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var socket = client(srv, { reconnection: false });
|
||||
var socket = client(srv);
|
||||
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('forced close');
|
||||
expect(reason).to.be('client error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -1176,7 +1087,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');
|
||||
}, 100);
|
||||
}, 20);
|
||||
});
|
||||
|
||||
var socket = client(srv, { transports: ['polling'] });
|
||||
@@ -1188,7 +1099,7 @@ describe('socket.io', function(){
|
||||
setTimeout(function() {
|
||||
expect(counter).to.be(1);
|
||||
done();
|
||||
}, 500);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it('should emit volatile event (ws)', function(done) {
|
||||
@@ -1227,7 +1138,7 @@ describe('socket.io', function(){
|
||||
setTimeout(function() {
|
||||
s.volatile.emit('ev', 'data');
|
||||
s.volatile.emit('ev', 'data');
|
||||
}, 100);
|
||||
}, 20);
|
||||
});
|
||||
|
||||
var socket = client(srv, { transports: ['polling'] });
|
||||
@@ -1239,7 +1150,7 @@ describe('socket.io', function(){
|
||||
setTimeout(function() {
|
||||
expect(counter).to.be(1);
|
||||
done();
|
||||
}, 500);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it('should emit only one consecutive volatile event (ws)', function(done) {
|
||||
@@ -1543,7 +1454,9 @@ describe('socket.io', function(){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function() {
|
||||
var socket = client(srv, {query: {key1: 1, key2: 2}});
|
||||
var addr = srv.listen().address();
|
||||
var url = 'ws://localhost:' + addr.port + '?key1=1&key2=2';
|
||||
var socket = ioc(url);
|
||||
sio.on('connection', function(s) {
|
||||
var parsed = require('url').parse(s.request.url);
|
||||
var query = require('querystring').parse(parsed.query);
|
||||
@@ -1553,27 +1466,11 @@ 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);
|
||||
var srv = http();
|
||||
var sio = io(srv, { perMessageDeflate: false });
|
||||
var sio = io(srv);
|
||||
var received = 0;
|
||||
srv.listen(function(){
|
||||
var socket = client(srv);
|
||||
@@ -1598,9 +1495,10 @@ describe('socket.io', function(){
|
||||
});
|
||||
|
||||
it('should handle very large binary data', function(done){
|
||||
this.timeout(30000);
|
||||
this.timeout(10000);
|
||||
|
||||
var srv = http();
|
||||
var sio = io(srv, { perMessageDeflate: false });
|
||||
var sio = io(srv);
|
||||
var received = 0;
|
||||
srv.listen(function(){
|
||||
var socket = client(srv);
|
||||
@@ -1640,10 +1538,11 @@ describe('socket.io', function(){
|
||||
var clientSocket = client(srv, { reconnectionAttempts: 10, reconnectionDelay: 100 });
|
||||
clientSocket.once('connect', function(){
|
||||
srv.close(function(){
|
||||
clientSocket.on('reconnect', function(){
|
||||
clientSocket.emit('ev', 'payload');
|
||||
srv.listen(port, function(){
|
||||
clientSocket.on('reconnect', function(){
|
||||
clientSocket.emit('ev', 'payload');
|
||||
});
|
||||
});
|
||||
sio.listen(port);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1653,13 +1552,13 @@ describe('socket.io', function(){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var socket = client(srv, '/chat');
|
||||
sio.of('/chat').on('connection', function(s){
|
||||
var socket = client(srv);
|
||||
sio.on('connection', function(s){
|
||||
s.conn.once('packetCreate', function(packet) {
|
||||
expect(packet.options.compress).to.be(true);
|
||||
done();
|
||||
});
|
||||
sio.of('/chat').emit('woot', 'hi');
|
||||
s.emit('woot', 'hi');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1668,13 +1567,13 @@ describe('socket.io', function(){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var socket = client(srv, '/chat');
|
||||
sio.of('/chat').on('connection', function(s){
|
||||
var socket = client(srv);
|
||||
sio.on('connection', function(s){
|
||||
s.conn.once('packetCreate', function(packet) {
|
||||
expect(packet.options.compress).to.be(false);
|
||||
done();
|
||||
});
|
||||
sio.of('/chat').compress(false).emit('woot', 'hi');
|
||||
s.compress(false).emit('woot', 'hi');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1683,7 +1582,7 @@ describe('socket.io', function(){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var socket = client(srv, { reconnection: false });
|
||||
var socket = client(srv);
|
||||
sio.on('connection', function(s){
|
||||
s.conn.on('upgrade', function(){
|
||||
console.log('\033[96mNote: warning expected and normal in test.\033[39m');
|
||||
@@ -1700,7 +1599,7 @@ describe('socket.io', function(){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var socket = client(srv, { reconnection: false });
|
||||
var socket = client(srv);
|
||||
sio.on('connection', function(s){
|
||||
s.once('error', function(err){
|
||||
expect(err.message).to.match(/Illegal attachments/);
|
||||
@@ -1712,57 +1611,6 @@ describe('socket.io', function(){
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty binary packet', function(done){
|
||||
var srv = http();
|
||||
var sio = io(srv);
|
||||
srv.listen(function(){
|
||||
var socket = client(srv, { reconnection: false });
|
||||
sio.on('connection', function(s){
|
||||
s.once('error', function(err){
|
||||
expect(err.message).to.match(/Illegal attachments/);
|
||||
done();
|
||||
});
|
||||
s.conn.on('upgrade', function(){
|
||||
socket.io.engine.write('5');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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(){
|
||||
var socket = client(srv);
|
||||
|
||||
sio.on('connection', function(s){
|
||||
s.disconnect(true);
|
||||
sio.close();
|
||||
setTimeout(function(){
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should always trigger the callback (if provided) when joining a room', 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.join('a', done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('messaging many', function(){
|
||||
@@ -2027,15 +1875,15 @@ describe('socket.io', function(){
|
||||
var socket = client(srv);
|
||||
sio.on('connection', function(s){
|
||||
s.join('a', function(){
|
||||
expect(Object.keys(s.rooms)).to.eql([s.id, 'a']);
|
||||
expect(s.rooms).to.eql([s.id, 'a']);
|
||||
s.join('b', function(){
|
||||
expect(Object.keys(s.rooms)).to.eql([s.id, 'a', 'b']);
|
||||
expect(s.rooms).to.eql([s.id, 'a', 'b']);
|
||||
s.join( 'c', function(){
|
||||
expect(Object.keys(s.rooms)).to.eql([s.id, 'a', 'b', 'c']);
|
||||
expect(s.rooms).to.eql([s.id, 'a', 'b', 'c']);
|
||||
s.leave('b', function(){
|
||||
expect(Object.keys(s.rooms)).to.eql([s.id, 'a', 'c']);
|
||||
expect(s.rooms).to.eql([s.id, 'a', 'c']);
|
||||
s.leaveAll();
|
||||
expect(Object.keys(s.rooms)).to.eql([]);
|
||||
expect(s.rooms).to.eql([]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -2071,13 +1919,13 @@ describe('socket.io', function(){
|
||||
var socket = client(srv);
|
||||
sio.on('connection', function(s){
|
||||
s.join('a', function(){
|
||||
expect(Object.keys(s.rooms)).to.eql([s.id, 'a']);
|
||||
expect(s.rooms).to.eql([s.id, 'a']);
|
||||
s.join('b', function(){
|
||||
expect(Object.keys(s.rooms)).to.eql([s.id, 'a', 'b']);
|
||||
expect(s.rooms).to.eql([s.id, 'a', 'b']);
|
||||
s.leave('unknown', function(){
|
||||
expect(Object.keys(s.rooms)).to.eql([s.id, 'a', 'b']);
|
||||
expect(s.rooms).to.eql([s.id, 'a', 'b']);
|
||||
s.leaveAll();
|
||||
expect(Object.keys(s.rooms)).to.eql([]);
|
||||
expect(s.rooms).to.eql([]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -2085,30 +1933,10 @@ 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;
|
||||
if (testVersion === 'compat') {
|
||||
Socket = require('../dist/socket');
|
||||
} else {
|
||||
Socket = require('../lib/socket');
|
||||
}
|
||||
var Socket = require('../lib/socket');
|
||||
|
||||
it('should call functions', function(done){
|
||||
var srv = http();
|
||||
@@ -2145,6 +1973,9 @@ 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();
|
||||
@@ -2163,6 +1994,9 @@ 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();
|
||||
@@ -2240,97 +2074,4 @@ 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