Compare commits

..

9 Commits

Author SHA1 Message Date
Roman Shtylman
213577204a Merge branch 'dynamic-namespaces' of https://github.com/markitx/socket.io into dynamic-namespace
Conflicts:
	lib/client.js
	test/socket.io.js
2015-02-04 18:17:49 -08:00
Dylan Lingelbach
dd72676c25 Make name more descriptive 2015-02-04 13:43:21 -06:00
Dylan Lingelbach
3bf7be73a0 Style cleanup 2015-02-04 13:40:40 -06:00
Dylan Lingelbach
0360553c99 Merge branch 'master' into dynamic-namespaces 2015-02-04 13:10:40 -06:00
Dylan Lingelbach
de5cbdb833 Improve tests 2015-02-04 12:33:18 -06:00
Dylan Lingelbach
0ce0ce1dbc Merge pull request #1 from markitx/dynamic-namespaces
Dynamic namespaces
2015-01-09 11:56:51 -06:00
Dylan Lingelbach
cc7ce79251 Ensure server sends connect message 2014-12-19 15:32:20 -06:00
Dylan Lingelbach
1b77c27f7b Fix test 2014-12-19 15:27:08 -06:00
Dylan Lingelbach
1f8bb8a0ec Support dynamic namespaces 2014-12-19 13:22:41 -06:00
84 changed files with 1296 additions and 5190 deletions

View File

@@ -1,32 +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)
For bug reports and feature requests for the **Swift client**, please open an issue [there](https://github.com/socketio/socket.io-client-swift).
For bug reports and feature requests for the **Java client**, please open an issue [there](https://github.com/socketio/socket.io-client-java).
### You want to:
* [x] report a *bug*
* [ ] request a *feature*
### Current behaviour
*What is actually happening?*
### Steps to reproduce (if the current behaviour is a bug)
**Note**: the best way (and by that we mean **the only way**) to get a quick answer is to provide a failing test case by forking the following [fiddle](https://github.com/socketio/socket.io-fiddle).
### Expected behaviour
*What is expected?*
### Setup
- OS:
- browser:
- socket.io version:
### Other information (e.g. stacktraces, related issues, suggestions how to fix)

View File

@@ -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)

3
.gitignore vendored
View File

@@ -9,6 +9,3 @@ lib-cov
benchmarks/*.png
node_modules
coverage
.idea
dist
.nyc_output

4
.npmignore Normal file
View File

@@ -0,0 +1,4 @@
support
test
examples
.gitignore

View File

@@ -1,12 +1,16 @@
language: node_js
sudo: false
node_js:
- '8'
- '10'
notifications:
irc: "irc.freenode.org#socket.io"
- "0.8"
- "0.10"
- "0.11"
git:
depth: 1
cache:
directories:
- node_modules
matrix:
fast_finish: true
allow_failures:
- node_js: "0.11"
notifications:
irc: "irc.freenode.org#socket.io"

475
History.md Normal file
View File

@@ -0,0 +1,475 @@
1.3.2 / 2015-01-19
==================
* no change on this release
1.3.1 / 2015-01-19
==================
* no change on this release
* package: bump `engine.io`
1.3.0 / 2015-01-19
==================
* package: bump `engine.io`
* add test for reconnection after server restarts [rase-]
* update license with up-to-date year range [fay-jai]
* fix leaving unknown rooms [defunctzombie]
* allow null origins when allowed origins is a function [drewblaisdell]
* fix tests on node 0.11
* package: fix `npm test` to run on windows
* package: bump `debug` v2.1.0 [coderaiser]
* added tests for volatile [rase-]
1.2.1 / 2014-11-21
==================
* fix protocol violations and improve error handling (GH-1880)
* package: bump `engine.io` for websocket leak fix [3rd-Eden]
* style tweaks
1.2.0 / 2014-10-27
==================
* package: bump `engine.io`
* downloads badge
* add test to check that empty rooms are autopruned
* added Server#origins(v:Function) description for dynamic CORS
* added test coverage for Server#origins(function) for dynamic CORS
* added optional Server#origins(function) for dynamic CORS
* fix usage example for Server#close
* package: fix main file for example application 'chat'
* package: bump `socket.io-parser`
* update README http ctor to createServer()
* bump adapter with a lot of fixes for room bookkeeping
1.1.0 / 2014-09-04
==================
* examples: minor fix of escaping
* testing for equivalence of namespaces starting with / or without
* update index.js
* added relevant tests
* take "" and "/" as equivalent namespaces on server
* use svg instead of png to get better image quality in readme
* make CI build faster
* fix splice arguments and `socket.rooms` value update in `socket.leaveAll`.
* client cannot connect to non-existing namespaces
* bump engine.io version to get the cached IP address
* fixed handshake object address property and made the test case more strict.
* package: bump `engine.io`
* fixed the failing test where server crashes on disconnect involving connectBuffer
* npmignore: ignore `.gitignore` (fixes #1607)
* test: added failing case for `socket.disconnect` and nsps
* fix repo in package.json
* improve Close documentation
* use ephemeral ports
* fix: We should use the standard http protocol to handler the etag header.
* override default browser font-family for inputs
* update has-binary-data to 1.0.3
* add close specs
* add ability to stop the http server even if not created inside socket.io
* make sure server gets close
* Add test case for checking that reconnect_failed is fired only once upon failure
* package: bump `socket.io-parser` for `component-emitter` dep fix
1.0.6 / 2014-06-19
==================
* package: bump `socket.io-client`
1.0.5 / 2014-06-16
==================
* package: bump `engine.io` to fix jsonp `\n` bug and CORS warnings
* index: fix typo [yanatan16]
* add `removeListener` to blacklisted events
* examples: clearer instructions to install chat example
* index: fix namespace `connectBuffer` issue
1.0.4 / 2014-06-02
==================
* package: bump socket.io-client
1.0.3 / 2014-05-31
==================
* package: bump `socket.io-client`
* package: bump `socket.io-parser` for binary ACK fix
* package: bump `engine.io` for binary UTF8 fix
* example: fix XSS in chat example
1.0.2 / 2014-05-28
==================
* package: bump `socket.io-parser` for windows fix
1.0.1 / 2014-05-28
==================
* bump due to bad npm tag
1.0.0 / 2014-05-28
==================
* stable release
1.0.0-pre5 / 2014-05-22
=======================
* package: bump `socket.io-client` for parser fixes
* package: bump `engine.io`
1.0.0-pre4 / 2014-05-19
=======================
* package: bump client
1.0.0-pre3 / 2014-05-17
=======================
* package: bump parser
* package: bump engine.io
1.0.0-pre2 / 2014-04-27
=======================
* package: bump `engine.io`
* added backwards compatible of engine.io maxHttpBufferSize
* added test that server and client using same protocol
* added support for setting allowed origins
* added information about logging
* the set function in server can be used to set some attributes for BC
* fix error in callback call 'done' instead of 'next' in docs
* package: bump `socket.io-parser`
* package: bump `expect.js`
* added some new tests, including binary with acks
1.0.0-pre / 2014-03-14
======================
* implemented `engine.io`
* implemented `socket.io-adapter`
* implemented `socket.io-protocol`
* implemented `debug` and improved instrumentation
* added binary support
* added new `require('io')(srv)` signature
* simplified `socket.io-client` serving
0.9.14 / 2013-03-29
===================
* manager: fix memory leak with SSL [jpallen]
0.9.13 / 2012-12-13
===================
* package: fixed `base64id` requirement
0.9.12 / 2012-12-13
===================
* manager: fix for latest node which is returning a clone with `listeners` [viirya]
0.9.11 / 2012-11-02
===================
* package: move redis to optionalDependenices [3rd-Eden]
* bumped client
0.9.10 / 2012-08-10
===================
* Don't lowercase log messages
* Always set the HTTP response in case an error should be returned to the client
* Create or destroy the flash policy server on configuration change
* Honour configuration to disable flash policy server
* Add express 3.0 instructions on Readme.md
* Bump client
0.9.9 / 2012-08-01
==================
* Fixed sync disconnect xhrs handling
* Put license text in its own file (#965)
* Add warning to .listen() to ease the migration to Express 3.x
* Restored compatibility with node 0.4.x
0.9.8 / 2012-07-24
==================
* Bumped client.
0.9.7 / 2012-07-24
==================
* Prevent crash when socket leaves a room twice.
* Corrects unsafe usage of for..in
* Fix for node 0.8 with `gzip compression` [vadimi]
* Update redis to support Node 0.8.x
* Made ID generation securely random
* Fix Redis Store race condition in manager onOpen unsubscribe callback
* Fix for EventEmitters always reusing the same Array instance for listeners
0.9.6 / 2012-04-17
==================
* Fixed XSS in jsonp-polling.
0.9.5 / 2012-04-05
==================
* Added test for polling and socket close.
* Ensure close upon request close.
* Fix disconnection reason being lost for polling transports.
* Ensure that polling transports work with Connection: close.
* Log disconnection reason.
0.9.4 / 2012-04-01
==================
* Disconnecting from namespace improvement (#795) [DanielBaulig]
* Bumped client with polling reconnection loop (#438)
0.9.3 / 2012-03-28
==================
* Fix "Syntax error" on FF Web Console with XHR Polling [mikito]
0.9.2 / 2012-03-13
==================
* More sensible close `timeout default` (fixes disconnect issue)
0.9.1-1 / 2012-03-02
====================
* Bumped client with NPM dependency fix.
0.9.1 / 2012-03-02
==================
* Changed heartbeat timeout and interval defaults (60 and 25 seconds)
* Make tests work both on 0.4 and 0.6
* Updated client (improvements + bug fixes).
0.9.0 / 2012-02-26
==================
* Make it possible to use a regexp to match the socket.io resource URL.
We need this because we have to prefix the socket.io URL with a variable ID.
* Supplemental fix to gavinuhma/authfix, it looks like the same Access-Control-Origin logic is needed in the http and xhr-polling transports
* Updated express dep for windows compatibility.
* Combine two substr calls into one in decodePayload to improve performance
* Minor documentation fix
* Minor. Conform to style of other files.
* Switching setting to 'match origin protocol'
* Revert "Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect()."
* Revert "Handle leaked dispatch:[id] subscription."
* Merge pull request #667 from dshaw/patch/redis-disconnect
* Handle leaked dispatch:[id] subscription.
* Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect().
* Prevent memory leaking on uncompleted requests & add max post size limitation
* Fix for testcase
* Set Access-Control-Allow-Credentials true, regardless of cookie
* Remove assertvarnish from package as it breaks on 0.6
* Correct irc channel
* Added proper return after reserved field error
* Fixes manager.js failure to close connection after transport error has happened
* Added implicit port 80 for origin checks. fixes #638
* Fixed bug #432 in 0.8.7
* Set Access-Control-Allow-Origin header to origin to enable withCredentials
* Adding configuration variable matchOriginProtocol
* Fixes location mismatch error in Safari.
* Use tty to detect if we should add colors or not by default.
* Updated the package location.
0.8.7 / 2011-11-05
==================
* Fixed memory leaks in closed clients.
* Fixed memory leaks in namespaces.
* Fixed websocket handling for malformed requests from proxies. [einaros]
* Node 0.6 compatibility. [einaros] [3rd-Eden]
* Adapted tests and examples.
0.8.6 / 2011-10-27
==================
* Added JSON decoding on jsonp-polling transport.
* Fixed README example.
* Major speed optimizations [3rd-Eden] [einaros] [visionmedia]
* Added decode/encode benchmarks [visionmedia]
* Added support for black-listing client sent events.
* Fixed logging options, closes #540 [3rd-Eden]
* Added vary header for gzip [3rd-Eden]
* Properly cleaned up async websocket / flashsocket tests, after patching node-websocket-client
* Patched to properly shut down when a finishClose call is made during connection establishment
* Added support for socket.io version on url and far-future Expires [3rd-Eden] [getify]
* Began IE10 compatibility [einaros] [tbranyen]
* Misc WebSocket fixes [einaros]
* Added UTF8 to respone headers for htmlfile [3rd-Eden]
0.8.5 / 2011-10-07
==================
* Added websocket draft HyBi-16 support. [einaros]
* Fixed websocket continuation bugs. [einaros]
* Fixed flashsocket transport name.
* Fixed websocket tests.
* Ensured `parser#decodePayload` doesn't choke.
* Added http referrer verification to manager verifyOrigin.
* Added access control for cross domain xhr handshakes [3rd-Eden]
* Added support for automatic generation of socket.io files [3rd-Eden]
* Added websocket binary support [einaros]
* Added gzip support for socket.io.js [3rd-Eden]
* Expose socket.transport [3rd-Eden]
* Updated client.
0.8.4 / 2011-09-06
==================
* Client build
0.8.3 / 2011-09-03
==================
* Fixed `\n` parsing for non-JSON packets (fixes #479).
* Fixed parsing of certain unicode characters (fixes #451).
* Fixed transport message packet logging.
* Fixed emission of `error` event resulting in an uncaught exception if unhandled (fixes #476).
* Fixed; allow for falsy values as the configuration value of `log level` (fixes #491).
* Fixed repository URI in `package.json`. Fixes #504.
* Added text/plain content-type to handshake responses [einaros]
* Improved single byte writes [einaros]
* Updated socket.io-flashsocket default port from 843 to 10843 [3rd-Eden]
* Updated client.
0.8.2 / 2011-08-29
==================
* Updated client.
0.8.1 / 2011-08-29
==================
* Fixed utf8 bug in send framing in websocket [einaros]
* Fixed typo in docs [Znarkus]
* Fixed bug in send framing for over 64kB of data in websocket [einaros]
* Corrected ping handling in websocket transport [einaros]
0.8.0 / 2011-08-28
==================
* Updated to work with two-level websocket versioning. [einaros]
* Added hybi07 support. [einaros]
* Added hybi10 support. [einaros]
* Added http referrer verification to manager.js verifyOrigin. [einaors]
0.7.11 / 2011-08-27
===================
* Updated socket.io-client.
0.7.10 / 2011-08-27
===================
* Updated socket.io-client.
0.7.9 / 2011-08-12
==================
* Updated socket.io-client.
* Make sure we only do garbage collection when the server we receive is actually run.
0.7.8 / 2011-08-08
==================
* Changed; make sure sio#listen passes options to both HTTP server and socket.io manager.
* Added docs for sio#listen.
* Added options parameter support for Manager constructor.
* Added memory leaks tests and test-leaks Makefile task.
* Removed auto npm-linking from make test.
* Make sure that you can disable heartbeats. [3rd-Eden]
* Fixed rooms memory leak [3rd-Eden]
* Send response once we got all POST data, not immediately [Pita]
* Fixed onLeave behavior with missing clientsk [3rd-Eden]
* Prevent duplicate references in rooms.
* Added alias for `to` to `in` and `in` to `to`.
* Fixed roomClients definition.
* Removed dependency on redis for installation without npm [3rd-Eden]
* Expose path and querystring in handshakeData [3rd-Eden]
0.7.7 / 2011-07-12
==================
* Fixed double dispatch handling with emit to closed clients.
* Added test for emitting to closed clients to prevent regression.
* Fixed race condition in redis test.
* Changed Transport#end instrumentation.
* Leveraged $emit instead of emit internally.
* Made tests faster.
* Fixed double disconnect events.
* Fixed disconnect logic
* Simplified remote events handling in Socket.
* Increased testcase timeout.
* Fixed unknown room emitting (GH-291). [3rd-Eden]
* Fixed `address` in handshakeData. [3rd-Eden]
* Removed transports definition in chat example.
* Fixed room cleanup
* Fixed; make sure the client is cleaned up after booting.
* Make sure to mark the client as non-open if the connection is closed.
* Removed unneeded `buffer` declarations.
* Fixed; make sure to clear socket handlers and subscriptions upon transport close.
0.7.6 / 2011-06-30
==================
* Fixed general dispatching when a client has closed.
0.7.5 / 2011-06-30
==================
* Fixed dispatching to clients that are disconnected.
0.7.4 / 2011-06-30
==================
* Fixed; only clear handlers if they were set. [level09]
0.7.3 / 2011-06-30
==================
* Exposed handshake data to clients.
* Refactored dispatcher interface.
* Changed; Moved id generation method into the manager.
* Added sub-namespace authorization. [3rd-Eden]
* Changed; normalized SocketNamespace local eventing [dvv]
* Changed; Use packet.reason or default to 'packet' [3rd-Eden]
* Changed console.error to console.log.
* Fixed; bind both servers at the same time do that the test never times out.
* Added 304 support.
* Removed `Transport#name` for abstract interface.
* Changed; lazily require http and https module only when needed. [3rd-Eden]
0.7.2 / 2011-06-22
==================
* Make sure to write a packet (of type `noop`) when closing a poll.
This solves a problem with cross-domain requests being flagged as aborted and
reconnection being triggered.
* Added `noop` message type.
0.7.1 / 2011-06-21
==================
* Fixed cross-domain XHR.
* Added CORS test to xhr-polling suite.
0.7.0 / 2010-06-21
==================
* http://socket.io/announcement.html

View File

@@ -1,6 +1,6 @@
(The MIT License)
Copyright (c) 2014-2018 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

15
Makefile Normal file
View File

@@ -0,0 +1,15 @@
REPORTER = dot
test:
@./node_modules/.bin/mocha \
--reporter $(REPORTER) \
--slow 200ms \
--bail
test-cov:
@./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- \
--reporter $(REPORTER) \
test/
.PHONY: test

555
Readme.md
View File

@@ -1,91 +1,9 @@
# socket.io
[![Backers on Open Collective](https://opencollective.com/socketio/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/socketio/sponsors/badge.svg)](#sponsors)
[![Build Status](https://secure.travis-ci.org/socketio/socket.io.svg?branch=master)](https://travis-ci.org/socketio/socket.io)
[![Dependency Status](https://david-dm.org/socketio/socket.io.svg)](https://david-dm.org/socketio/socket.io)
[![devDependency Status](https://david-dm.org/socketio/socket.io/dev-status.svg)](https://david-dm.org/socketio/socket.io#info=devDependencies)
[![NPM version](https://badge.fury.io/js/socket.io.svg)](https://www.npmjs.com/package/socket.io)
![Downloads](https://img.shields.io/npm/dm/socket.io.svg?style=flat)
[![](https://slackin-socketio.now.sh/badge.svg)](https://slackin-socketio.now.sh)
## Features
Socket.IO enables real-time bidirectional event-based communication. It consists of:
- a Node.js server (this repository)
- a [Javascript client library](https://github.com/socketio/socket.io-client) for the browser (or a Node.js client)
Some implementations in other languages are also available:
- [Java](https://github.com/socketio/socket.io-client-java)
- [C++](https://github.com/socketio/socket.io-client-cpp)
- [Swift](https://github.com/socketio/socket.io-client-swift)
- [Dart](https://github.com/rikulo/socket.io-client-dart)
Its main features are:
#### Reliability
Connections are established even in the presence of:
- proxies and load balancers.
- personal firewall and antivirus software.
For this purpose, it relies on [Engine.IO](https://github.com/socketio/engine.io), which first establishes a long-polling connection, then tries to upgrade to better transports that are "tested" on the side, like WebSocket. Please see the [Goals](https://github.com/socketio/engine.io#goals) section for more information.
#### Auto-reconnection support
Unless instructed otherwise a disconnected client will try to reconnect forever, until the server is available again. Please see the available reconnection options [here](https://github.com/socketio/socket.io-client/blob/master/docs/API.md#new-managerurl-options).
#### Disconnection detection
A heartbeat mechanism is implemented at the Engine.IO level, allowing both the server and the client to know when the other one is not responding anymore.
That functionality is achieved with timers set on both the server and the client, with timeout values (the `pingInterval` and `pingTimeout` parameters) shared during the connection handshake. Those timers require any subsequent client calls to be directed to the same server, hence the `sticky-session` requirement when using multiples nodes.
#### Binary support
Any serializable data structures can be emitted, including:
- [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) and [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) in the browser
- [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) and [Buffer](https://nodejs.org/api/buffer.html) in Node.js
#### Simple and convenient API
Sample code:
```js
io.on('connection', socket => {
socket.emit('request', /* … */); // emit an event to the socket
io.emit('broadcast', /* … */); // emit an event to all connected sockets
socket.on('reply', () => { /* … */ }); // listen to the event
});
```
#### Cross-browser
Browser support is tested in Saucelabs:
[![Sauce Test Status](https://saucelabs.com/browser-matrix/socket.svg)](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
```
[![Build Status](https://secure.travis-ci.org/Automattic/socket.io.svg)](http://travis-ci.org/Automattic/socket.io)
![NPM version](https://badge.fury.io/js/socket.io.svg)
![Downloads](http://img.shields.io/npm/dm/socket.io.svg?style=flat)
## How to use
@@ -93,11 +11,11 @@ The following example attaches socket.io to a plain Node.JS
HTTP server listening on port `3000`.
```js
const server = require('http').createServer();
const io = require('socket.io')(server);
io.on('connection', client => {
client.on('event', data => { /* … */ });
client.on('disconnect', () => { /* … */ });
var server = require('http').createServer();
var io = require('socket.io')(server);
io.on('connection', function(socket){
socket.on('event', function(data){});
socket.on('disconnect', function(){});
});
server.listen(3000);
```
@@ -105,8 +23,8 @@ server.listen(3000);
### Standalone
```js
const io = require('socket.io')();
io.on('connection', client => { ... });
var io = require('socket.io')();
io.on('connection', function(socket){});
io.listen(3000);
```
@@ -115,13 +33,13 @@ 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
const app = require('express')();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
io.on('connection', () => { /* … */ });
var app = require('express')();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
io.on('connection', function(){ /* … */ });
server.listen(3000);
```
@@ -131,20 +49,363 @@ Like Express.JS, Koa works by exposing an application as a request
handler function, but only by calling the `callback` method.
```js
const app = require('koa')();
const server = require('http').createServer(app.callback());
const io = require('socket.io')(server);
io.on('connection', () => { /* … */ });
var app = require('koa')();
var server = require('http').createServer(app.callback());
var io = require('socket.io')(server);
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.
@@ -154,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

View File

@@ -1,880 +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.adapter](#namespaceadapter)
- [namespace.to(room)](#namespacetoroom)
- [namespace.in(room)](#namespaceinroom)
- [namespace.emit(eventName[, ...args])](#namespaceemiteventname-args)
- [namespace.clients(callback)](#namespaceclientscallback)
- [namespace.use(fn)](#namespaceusefn)
- [Event: 'connect'](#event-connect)
- [Event: 'connection'](#event-connect)
- [Flag: 'volatile'](#flag-volatile)
- [Flag: 'local'](#flag-local)
- [Flag: 'binary'](#flag-binary)
- [Class: Socket](#socket)
- [socket.id](#socketid)
- [socket.rooms](#socketrooms)
- [socket.client](#socketclient)
- [socket.conn](#socketconn)
- [socket.request](#socketrequest)
- [socket.handshake](#sockethandshake)
- [socket.use(fn)](#socketusefn)
- [socket.send([...args][, ack])](#socketsendargs-ack)
- [socket.emit(eventName[, ...args][, ack])](#socketemiteventname-args-ack)
- [socket.on(eventName, callback)](#socketoneventname-callback)
- [socket.once(eventName, listener)](#socketonceeventname-listener)
- [socket.removeListener(eventName, listener)](#socketremovelistenereventname-listener)
- [socket.removeAllListeners([eventName])](#socketremovealllistenerseventname)
- [socket.eventNames()](#socketeventnames)
- [socket.join(room[, callback])](#socketjoinroom-callback)
- [socket.join(rooms[, callback])](#socketjoinrooms-callback)
- [socket.leave(room[, callback])](#socketleaveroom-callback)
- [socket.to(room)](#sockettoroom)
- [socket.in(room)](#socketinroom)
- [socket.compress(value)](#socketcompressvalue)
- [socket.disconnect(close)](#socketdisconnectclose)
- [Flag: 'broadcast'](#flag-broadcast)
- [Flag: 'volatile'](#flag-volatile-1)
- [Flag: 'binary'](#flag-binary-1)
- [Event: 'disconnect'](#event-disconnect)
- [Event: 'error'](#event-error)
- [Event: 'disconnecting'](#event-disconnecting)
- [Class: Client](#client)
- [client.conn](#clientconn)
- [client.request](#clientrequest)
### Server
Exposed by `require('socket.io')`.
#### new Server(httpServer[, options])
- `httpServer` _(http.Server)_ the server to bind to.
- `options` _(Object)_
- `path` _(String)_: name of the path to capture (`/socket.io`)
- `serveClient` _(Boolean)_: whether to serve the client files (`true`)
- `adapter` _(Adapter)_: the adapter to use. Defaults to an instance of the `Adapter` that ships with socket.io which is memory based. See [socket.io-adapter](https://github.com/socketio/socket.io-adapter)
- `origins` _(String)_: the allowed origins (`*:*`)
- `parser` _(Parser)_: the parser to use. Defaults to an instance of the `Parser` that ships with socket.io. See [socket.io-parser](https://github.com/socketio/socket.io-parser).
Works with and without `new`:
```js
const io = require('socket.io')();
// or
const Server = require('socket.io');
const io = new Server();
```
The same options passed to socket.io are always passed to the `engine.io` `Server` that gets created. See engine.io [options](https://github.com/socketio/engine.io#methods-1) as reference.
Among those options:
- `pingTimeout` _(Number)_: how many ms without a pong packet to consider the connection closed (`60000`)
- `pingInterval` _(Number)_: how many ms before sending a new ping packet (`25000`).
Those two parameters will impact the delay before a client knows the server is not available anymore. For example, if the underlying TCP connection is not closed properly due to a network issue, a client may have to wait up to `pingTimeout + pingInterval` ms before getting a `disconnect` event.
- `transports` _(Array<String>)_: transports to allow connections to (`['polling', 'websocket']`).
**Note:** The order is important. By default, a long-polling connection is established first, and then upgraded to WebSocket if possible. Using `['websocket']` means there will be no fallback if a WebSocket connection cannot be opened.
```js
const server = require('http').createServer();
const io = require('socket.io')(server, {
path: '/test',
serveClient: false,
// below are engine.IO options
pingInterval: 10000,
pingTimeout: 5000,
cookie: false
});
server.listen(3000);
```
#### new Server(port[, options])
- `port` _(Number)_ a port to listen to (a new `http.Server` will be created)
- `options` _(Object)_
See [above](#new-serverhttpserver-options) for available options.
```js
const server = require('http').createServer();
const io = require('socket.io')(3000, {
path: '/test',
serveClient: false,
// below are engine.IO options
pingInterval: 10000,
pingTimeout: 5000,
cookie: false
});
```
#### new Server(options)
- `options` _(Object)_
See [above](#new-serverhttpserver-options) for available options.
```js
const io = require('socket.io')({
path: '/test',
serveClient: false,
});
// either
const server = require('http').createServer();
io.attach(server, {
pingInterval: 10000,
pingTimeout: 5000,
cookie: false
});
server.listen(3000);
// or
io.attach(3000, {
pingInterval: 10000,
pingTimeout: 5000,
cookie: false
});
```
#### server.sockets
* _(Namespace)_
The default (`/`) namespace.
#### server.serveClient([value])
- `value` _(Boolean)_
- **Returns** `Server|Boolean`
If `value` is `true` the attached server (see `Server#attach`) will serve the client files. Defaults to `true`. This method has no effect after `attach` is called. If no arguments are supplied this method returns the current value.
```js
// pass a server and the `serveClient` option
const io = require('socket.io')(http, { serveClient: false });
// or pass no server and then you can call the method
const io = require('socket.io')();
io.serveClient(false);
io.attach(http);
```
#### server.path([value])
- `value` _(String)_
- **Returns** `Server|String`
Sets the path `value` under which `engine.io` and the static files will be served. Defaults to `/socket.io`. If no arguments are supplied this method returns the current value.
```js
const io = require('socket.io')();
io.path('/myownpath');
// client-side
const socket = io({
path: '/myownpath'
});
```
#### server.adapter([value])
- `value` _(Adapter)_
- **Returns** `Server|Adapter`
Sets the adapter `value`. Defaults to an instance of the `Adapter` that ships with socket.io which is memory based. See [socket.io-adapter](https://github.com/socketio/socket.io-adapter). If no arguments are supplied this method returns the current value.
```js
const io = require('socket.io')(3000);
const redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
```
#### server.origins([value])
- `value` _(String|String[])_
- **Returns** `Server|String`
Sets the allowed origins `value`. Defaults to any origins being allowed. If no arguments are supplied this method returns the current value.
```js
io.origins(['https://foo.example.com:443']);
```
#### server.origins(fn)
- `fn` _(Function)_
- **Returns** `Server`
Provides a function taking two arguments `origin:String` and `callback(error, success)`, where `success` is a boolean value indicating whether origin is allowed or not. If `success` is set to `false`, `error` must be provided as a string value that will be appended to the server response, e.g. "Origin not allowed".
__Potential drawbacks__:
* in some situations, when it is not possible to determine `origin` it may have value of `*`
* As this function will be executed for every request, it is advised to make this function work as fast as possible
* If `socket.io` is used together with `Express`, the CORS headers will be affected only for `socket.io` requests. For Express you can use [cors](https://github.com/expressjs/cors).
```js
io.origins((origin, callback) => {
if (origin !== 'https://foo.example.com') {
return callback('origin not allowed', false);
}
callback(null, true);
});
```
#### server.attach(httpServer[, options])
- `httpServer` _(http.Server)_ the server to attach to
- `options` _(Object)_
Attaches the `Server` to an engine.io instance on `httpServer` with the supplied `options` (optionally).
#### server.attach(port[, options])
- `port` _(Number)_ the port to listen on
- `options` _(Object)_
Attaches the `Server` to an engine.io instance on a new http.Server with the supplied `options` (optionally).
#### server.listen(httpServer[, options])
Synonym of [server.attach(httpServer[, options])](#serverattachhttpserver-options).
#### server.listen(port[, options])
Synonym of [server.attach(port[, options])](#serverattachport-options).
#### server.bind(engine)
- `engine` _(engine.Server)_
- **Returns** `Server`
Advanced use only. Binds the server to a specific engine.io `Server` (or compatible API) instance.
#### server.onconnection(socket)
- `socket` _(engine.Socket)_
- **Returns** `Server`
Advanced use only. Creates a new `socket.io` client from the incoming engine.io (or compatible API) `Socket`.
#### server.of(nsp)
- `nsp` _(String|RegExp|Function)_
- **Returns** `Namespace`
Initializes and retrieves the given `Namespace` by its pathname identifier `nsp`. If the namespace was already initialized it returns it immediately.
```js
const adminNamespace = io.of('/admin');
```
A regex or a function can also be provided, in order to create namespace in a dynamic way:
```js
const dynamicNsp = io.of(/^\/dynamic-\d+$/).on('connect', (socket) => {
const newNamespace = socket.nsp; // newNamespace.name === '/dynamic-101'
// broadcast to all clients in the given sub-namespace
newNamespace.emit('hello');
});
// client-side
const socket = io('/dynamic-101');
// broadcast to all clients in each sub-namespace
dynamicNsp.emit('hello');
// use a middleware for each sub-namespace
dynamicNsp.use((socket, next) => { /* ... */ });
```
With a function:
```js
io.of((name, query, next) => {
next(null, checkToken(query.token));
}).on('connect', (socket) => { /* ... */ });
```
#### server.close([callback])
- `callback` _(Function)_
Closes the socket.io server. The `callback` argument is optional and will be called when all connections are closed.
```js
const Server = require('socket.io');
const PORT = 3030;
const server = require('http').Server();
const io = Server(PORT);
io.close(); // Close current server
server.listen(PORT); // PORT is free to use
io = Server(server);
```
#### server.engine.generateId
Overwrites the default method to generate your custom socket id.
The function is called with a node request object (`http.IncomingMessage`) as first parameter.
```js
io.engine.generateId = (req) => {
return "custom:id:" + custom_id++; // custom id must be unique
}
```
### Namespace
Represents a pool of sockets connected under a given scope identified
by a pathname (eg: `/chat`).
A client always connects to `/` (the main namespace), then potentially connect to other namespaces (while using the same underlying connection).
#### namespace.name
* _(String)_
The namespace identifier property.
#### namespace.connected
* _(Object<Socket>)_
The hash of `Socket` objects that are connected to this namespace, indexed by `id`.
#### namespace.adapter
* _(Adapter)_
The `Adapter` used for the namespace. Useful when using the `Adapter` based on [Redis](https://github.com/socketio/socket.io-redis), as it exposes methods to manage sockets and rooms accross your cluster.
**Note:** the adapter of the main namespace can be accessed with `io.of('/').adapter`.
#### namespace.to(room)
- `room` _(String)_
- **Returns** `Namespace` for chaining
Sets a modifier for a subsequent event emission that the event will only be _broadcasted_ to clients that have joined the given `room`.
To emit to multiple rooms, you can call `to` several times.
```js
const io = require('socket.io')();
const adminNamespace = io.of('/admin');
adminNamespace.to('level1').emit('an event', { some: 'data' });
```
#### namespace.in(room)
Synonym of [namespace.to(room)](#namespacetoroom).
#### namespace.emit(eventName[, ...args])
- `eventName` _(String)_
- `args`
Emits an event to all connected clients. The following two are equivalent:
```js
const io = require('socket.io')();
io.emit('an event sent to all connected clients'); // main namespace
const chat = io.of('/chat');
chat.emit('an event sent to all connected clients in chat namespace');
```
**Note:** acknowledgements are not supported when emitting from namespace.
#### namespace.clients(callback)
- `callback` _(Function)_
Gets a list of client IDs connected to this namespace (across all nodes if applicable).
```js
const io = require('socket.io')();
io.of('/chat').clients((error, clients) => {
if (error) throw error;
console.log(clients); // => [PZDoMHjiu8PYfRiKAAAF, Anw2LatarvGVVXEIAAAD]
});
```
An example to get all clients in namespace's room:
```js
io.of('/chat').in('general').clients((error, clients) => {
if (error) throw error;
console.log(clients); // => [Anw2LatarvGVVXEIAAAD]
});
```
As with broadcasting, the default is all clients from the default namespace ('/'):
```js
io.clients((error, clients) => {
if (error) throw error;
console.log(clients); // => [6em3d4TJP8Et9EMNAAAA, G5p55dHhGgUnLUctAAAB]
});
```
#### namespace.use(fn)
- `fn` _(Function)_
Registers a middleware, which is a function that gets executed for every incoming `Socket`, and receives as parameters the socket and a function to optionally defer execution to the next registered middleware.
Errors passed to middleware callbacks are sent as special `error` packets to clients.
```js
io.use((socket, next) => {
if (socket.request.headers.cookie) return next();
next(new Error('Authentication error'));
});
```
#### Event: 'connect'
- `socket` _(Socket)_ socket connection with client
Fired upon a connection from client.
```js
io.on('connect', (socket) => {
// ...
});
io.of('/admin').on('connect', (socket) => {
// ...
});
```
#### Event: 'connection'
Synonym of [Event: 'connect'](#event-connect).
#### Flag: 'volatile'
Sets a modifier for a subsequent event emission that the event data may be lost if the clients are not ready to receive messages (because of network slowness or other issues, or because theyre connected through long polling and is in the middle of a request-response cycle).
```js
io.volatile.emit('an event', { some: 'data' }); // the clients may or may not receive it
```
#### Flag: 'binary'
Specifies whether there is binary data in the emitted data. Increases performance when specified. Can be `true` or `false`.
```js
io.binary(false).emit('an event', { some: 'data' });
```
#### Flag: 'local'
Sets a modifier for a subsequent event emission that the event data will only be _broadcast_ to the current node (when the [Redis adapter](https://github.com/socketio/socket.io-redis) is used).
```js
io.local.emit('an event', { some: 'data' });
```
### Socket
A `Socket` is the fundamental class for interacting with browser clients. A `Socket` belongs to a certain `Namespace` (by default `/`) and uses an underlying `Client` to communicate.
It should be noted the `Socket` doesn't relate directly to the actual underlying TCP/IP `socket` and it is only the name of the class.
Within each `Namespace`, you can also define arbitrary channels (called `room`) that the `Socket` can join and leave. That provides a convenient way to broadcast to a group of `Socket`s (see `Socket#to` below).
The `Socket` class inherits from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). The `Socket` class overrides the `emit` method, and does not modify any other `EventEmitter` method. All methods documented here which also appear as `EventEmitter` methods (apart from `emit`) are implemented by `EventEmitter`, and documentation for `EventEmitter` applies.
#### socket.id
* _(String)_
A unique identifier for the session, that comes from the underlying `Client`.
#### socket.rooms
* _(Object)_
A hash of strings identifying the rooms this client is in, indexed by room name.
```js
io.on('connection', (socket) => {
socket.join('room 237', () => {
let rooms = Object.keys(socket.rooms);
console.log(rooms); // [ <socket.id>, 'room 237' ]
});
});
```
#### socket.client
* _(Client)_
A reference to the underlying `Client` object.
#### socket.conn
* _(engine.Socket)_
A reference to the underlying `Client` transport connection (engine.io `Socket` object). This allows access to the IO transport layer, which still (mostly) abstracts the actual TCP/IP socket.
#### socket.request
* _(Request)_
A getter proxy that returns the reference to the `request` that originated the underlying engine.io `Client`. Useful for accessing request headers such as `Cookie` or `User-Agent`.
#### socket.handshake
* _(Object)_
The handshake details:
```js
{
headers: /* the headers sent as part of the handshake */,
time: /* the date of creation (as string) */,
address: /* the ip of the client */,
xdomain: /* whether the connection is cross-domain */,
secure: /* whether the connection is secure */,
issued: /* the date of creation (as unix timestamp) */,
url: /* the request URL string */,
query: /* the query object */
}
```
Usage:
```js
io.use((socket, next) => {
let handshake = socket.handshake;
// ...
});
io.on('connection', (socket) => {
let handshake = socket.handshake;
// ...
});
```
#### socket.use(fn)
- `fn` _(Function)_
Registers a middleware, which is a function that gets executed for every incoming `Packet` and receives as parameter the packet and a function to optionally defer execution to the next registered middleware.
Errors passed to middleware callbacks are sent as special `error` packets to clients.
```js
io.on('connection', (socket) => {
socket.use((packet, next) => {
if (packet.doge === true) return next();
next(new Error('Not a doge error'));
});
});
```
#### socket.send([...args][, ack])
- `args`
- `ack` _(Function)_
- **Returns** `Socket`
Sends a `message` event. See [socket.emit(eventName[, ...args][, ack])](#socketemiteventname-args-ack).
#### socket.emit(eventName[, ...args][, ack])
*(overrides `EventEmitter.emit`)*
- `eventName` _(String)_
- `args`
- `ack` _(Function)_
- **Returns** `Socket`
Emits an event to the socket identified by the string name. Any other parameters can be included. All serializable datastructures are supported, including `Buffer`.
```js
socket.emit('hello', 'world');
socket.emit('with-binary', 1, '2', { 3: '4', 5: Buffer.alloc(6) });
```
The `ack` argument is optional and will be called with the client's answer.
```js
io.on('connection', (socket) => {
socket.emit('an event', { some: 'data' });
socket.emit('ferret', 'tobi', (data) => {
console.log(data); // data will be 'woot'
});
// the client code
// client.on('ferret', (name, fn) => {
// fn('woot');
// });
});
```
#### socket.on(eventName, callback)
*(inherited from `EventEmitter`)*
- `eventName` _(String)_
- `callback` _(Function)_
- **Returns** `Socket`
Register a new handler for the given event.
```js
socket.on('news', (data) => {
console.log(data);
});
// with several arguments
socket.on('news', (arg1, arg2, arg3) => {
// ...
});
// or with acknowledgement
socket.on('news', (data, callback) => {
callback(0);
});
```
#### socket.once(eventName, listener)
#### socket.removeListener(eventName, listener)
#### socket.removeAllListeners([eventName])
#### socket.eventNames()
Inherited from `EventEmitter` (along with other methods not mentioned here). See Node.js documentation for the `events` module.
#### socket.join(room[, callback])
- `room` _(String)_
- `callback` _(Function)_
- **Returns** `Socket` for chaining
Adds the client to the `room`, and fires optionally a callback with `err` signature (if any).
```js
io.on('connection', (socket) => {
socket.join('room 237', () => {
let rooms = Object.keys(socket.rooms);
console.log(rooms); // [ <socket.id>, 'room 237' ]
io.to('room 237').emit('a new user has joined the room'); // broadcast to everyone in the room
});
});
```
The mechanics of joining rooms are handled by the `Adapter` that has been configured (see `Server#adapter` above), defaulting to [socket.io-adapter](https://github.com/socketio/socket.io-adapter).
For your convenience, each socket automatically joins a room identified by its id (see `Socket#id`). This makes it easy to broadcast messages to other sockets:
```js
io.on('connection', (socket) => {
socket.on('say to someone', (id, msg) => {
// send a private message to the socket with the given id
socket.to(id).emit('my message', msg);
});
});
```
#### socket.join(rooms[, callback])
- `rooms` _(Array)_
- `callback` _(Function)_
- **Returns** `Socket` for chaining
Adds the client to the list of room, and fires optionally a callback with `err` signature (if any).
#### socket.leave(room[, callback])
- `room` _(String)_
- `callback` _(Function)_
- **Returns** `Socket` for chaining
Removes the client from `room`, and fires optionally a callback with `err` signature (if any).
**Rooms are left automatically upon disconnection**.
#### socket.to(room)
- `room` _(String)_
- **Returns** `Socket` for chaining
Sets a modifier for a subsequent event emission that the event will only be _broadcasted_ to clients that have joined the given `room` (the socket itself being excluded).
To emit to multiple rooms, you can call `to` several times.
```js
io.on('connection', (socket) => {
// to one room
socket.to('others').emit('an event', { some: 'data' });
// to multiple rooms
socket.to('room1').to('room2').emit('hello');
// a private message to another socket
socket.to(/* another socket id */).emit('hey');
});
```
**Note:** acknowledgements are not supported when broadcasting.
#### socket.in(room)
Synonym of [socket.to(room)](#sockettoroom).
#### socket.compress(value)
- `value` _(Boolean)_ whether to following packet will be compressed
- **Returns** `Socket` for chaining
Sets a modifier for a subsequent event emission that the event data will only be _compressed_ if the value is `true`. Defaults to `true` when you don't call the method.
```js
io.on('connection', (socket) => {
socket.compress(false).emit('uncompressed', "that's rough");
});
```
#### socket.disconnect(close)
- `close` _(Boolean)_ whether to close the underlying connection
- **Returns** `Socket`
Disconnects this client. If value of close is `true`, closes the underlying connection. Otherwise, it just disconnects the namespace.
```js
io.on('connection', (socket) => {
setTimeout(() => socket.disconnect(true), 5000);
});
```
#### Flag: 'broadcast'
Sets a modifier for a subsequent event emission that the event data will only be _broadcast_ to every sockets but the sender.
```js
io.on('connection', (socket) => {
socket.broadcast.emit('an event', { some: 'data' }); // everyone gets it but the sender
});
```
#### Flag: 'volatile'
Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to receive messages (because of network slowness or other issues, or because theyre connected through long polling and is in the middle of a request-response cycle).
```js
io.on('connection', (socket) => {
socket.volatile.emit('an event', { some: 'data' }); // the client may or may not receive it
});
```
#### Flag: 'binary'
Specifies whether there is binary data in the emitted data. Increases performance when specified. Can be `true` or `false`.
```js
var io = require('socket.io')();
io.on('connection', function(socket){
socket.binary(false).emit('an event', { some: 'data' }); // The data to send has no binary data
});
```
#### Event: 'disconnect'
- `reason` _(String)_ the reason of the disconnection (either client or server-side)
Fired upon disconnection.
```js
io.on('connection', (socket) => {
socket.on('disconnect', (reason) => {
// ...
});
});
```
#### Event: 'error'
- `error` _(Object)_ error object
Fired when an error occurs.
```js
io.on('connection', (socket) => {
socket.on('error', (error) => {
// ...
});
});
```
#### Event: 'disconnecting'
- `reason` _(String)_ the reason of the disconnection (either client or server-side)
Fired when the client is going to be disconnected (but hasn't left its `rooms` yet).
```js
io.on('connection', (socket) => {
socket.on('disconnecting', (reason) => {
let rooms = Object.keys(socket.rooms);
// ...
});
});
```
These are reserved events (along with `connect`, `newListener` and `removeListener`) which cannot be used as event names.
### Client
The `Client` class represents an incoming transport (engine.io) connection. A `Client` can be associated with many multiplexed `Socket`s that belong to different `Namespace`s.
#### client.conn
* _(engine.Socket)_
A reference to the underlying `engine.io` `Socket` connection.
#### client.request
* _(Request)_
A getter proxy that returns the reference to the `request` that originated the engine.io connection. Useful for accessing request headers such as `Cookie` or `User-Agent`.

View File

@@ -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)

View File

@@ -1,64 +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)
io.to(<socketid>).emit('hey', 'I just met you');
// sending with acknowledgement
socket.emit('question', 'do you think so?', function (answer) {});
// sending without compression
socket.compress(false).emit('uncompressed', "that's rough");
// sending a message that might be dropped if the client is not ready to receive messages
socket.volatile.emit('maybe', 'do you really need it?');
// specifying whether the data to send has binary data
socket.binary(false).emit('what', 'I have no binaries!');
// sending to all clients on this node (when using multiple nodes)
io.local.emit('hi', 'my lovely babies');
// sending to all connected clients
io.emit('an event sent to all connected clients');
};
```
**Note:** The following events are reserved and should not be used as event names by your application:
- `error`
- `connect`
- `disconnect`
- `disconnecting`
- `newListener`
- `removeListener`
- `ping`
- `pong`

View File

@@ -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

View File

@@ -1,27 +1,28 @@
// Setup basic express server
var express = require('express');
var app = express();
var path = require('path');
var server = require('http').createServer(app);
var io = require('../..')(server);
var port = process.env.PORT || 3000;
server.listen(port, () => {
server.listen(port, function () {
console.log('Server listening at port %d', port);
});
// Routing
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(__dirname + '/public'));
// Chatroom
// usernames which are currently connected to the chat
var usernames = {};
var numUsers = 0;
io.on('connection', (socket) => {
io.on('connection', function (socket) {
var addedUser = false;
// when the client emits 'new message', this listens and executes
socket.on('new message', (data) => {
socket.on('new message', function (data) {
// we tell the client to execute 'new message'
socket.broadcast.emit('new message', {
username: socket.username,
@@ -30,11 +31,11 @@ io.on('connection', (socket) => {
});
// when the client emits 'add user', this listens and executes
socket.on('add user', (username) => {
if (addedUser) return;
socket.on('add user', function (username) {
// 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', {
@@ -48,22 +49,24 @@ io.on('connection', (socket) => {
});
// when the client emits 'typing', we broadcast it to others
socket.on('typing', () => {
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', () => {
socket.on('stop typing', function () {
socket.broadcast.emit('stop typing', {
username: socket.username
});
});
// when the user disconnects.. perform this
socket.on('disconnect', () => {
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

View File

@@ -7,9 +7,6 @@
"private": true,
"license": "BSD",
"dependencies": {
"express": "4.13.4"
},
"scripts": {
"start": "node index.js"
"express": "3.4.8"
}
}
}

View File

@@ -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
@@ -25,7 +25,7 @@ $(function() {
var socket = io();
const addParticipantsMessage = (data) => {
function addParticipantsMessage (data) {
var message = '';
if (data.numUsers === 1) {
message += "there's 1 participant";
@@ -36,7 +36,7 @@ $(function() {
}
// Sets the client's username
const setUsername = () => {
function setUsername () {
username = cleanInput($usernameInput.val().trim());
// If the username is valid
@@ -52,7 +52,7 @@ $(function() {
}
// Sends a chat message
const sendMessage = () => {
function sendMessage () {
var message = $inputMessage.val();
// Prevent markup from being injected into the message
message = cleanInput(message);
@@ -69,13 +69,13 @@ $(function() {
}
// Log a message
const log = (message, options) => {
function log (message, options) {
var $el = $('<li>').addClass('log').text(message);
addMessageElement($el, options);
}
// Adds the visual chat message to the message list
const addChatMessage = (data, options) => {
function addChatMessage (data, options) {
// Don't fade the message in if there is an 'X was typing'
var $typingMessages = getTypingMessages(data);
options = options || {};
@@ -100,14 +100,14 @@ $(function() {
}
// Adds the visual chat typing message
const addChatTyping = (data) => {
function addChatTyping (data) {
data.typing = true;
data.message = 'is typing';
addChatMessage(data);
}
// Removes the visual chat typing message
const removeChatTyping = (data) => {
function removeChatTyping (data) {
getTypingMessages(data).fadeOut(function () {
$(this).remove();
});
@@ -118,7 +118,7 @@ $(function() {
// options.fade - If the element should fade-in (default = true)
// options.prepend - If the element should prepend
// all other messages (default = false)
const addMessageElement = (el, options) => {
function addMessageElement (el, options) {
var $el = $(el);
// Setup default options
@@ -145,12 +145,12 @@ $(function() {
}
// Prevents input from having injected markup
const cleanInput = (input) => {
return $('<div/>').text(input).html();
function cleanInput (input) {
return $('<div/>').text(input).text();
}
// Updates the typing event
const updateTyping = () => {
function updateTyping () {
if (connected) {
if (!typing) {
typing = true;
@@ -158,7 +158,7 @@ $(function() {
}
lastTypingTime = (new Date()).getTime();
setTimeout(() => {
setTimeout(function () {
var typingTimer = (new Date()).getTime();
var timeDiff = typingTimer - lastTypingTime;
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
@@ -170,14 +170,14 @@ $(function() {
}
// Gets the 'X is typing' messages of a user
const getTypingMessages = (data) => {
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
const getUsernameColor = (username) => {
function getUsernameColor (username) {
// Compute hash code
var hash = 7;
for (var i = 0; i < username.length; i++) {
@@ -190,7 +190,7 @@ $(function() {
// Keyboard events
$window.keydown(event => {
$window.keydown(function (event) {
// Auto-focus the current input when a key is typed
if (!(event.ctrlKey || event.metaKey || event.altKey)) {
$currentInput.focus();
@@ -207,26 +207,26 @@ $(function() {
}
});
$inputMessage.on('input', () => {
$inputMessage.on('input', function() {
updateTyping();
});
// Click events
// Focus input when clicking anywhere on login page
$loginPage.click(() => {
$loginPage.click(function () {
$currentInput.focus();
});
// Focus input when clicking on the message input's border
$inputMessage.click(() => {
$inputMessage.click(function () {
$inputMessage.focus();
});
// Socket events
// Whenever the server emits 'login', log the login message
socket.on('login', (data) => {
socket.on('login', function (data) {
connected = true;
// Display the welcome message
var message = "Welcome to Socket.IO Chat ";
@@ -237,46 +237,30 @@ $(function() {
});
// Whenever the server emits 'new message', update the chat body
socket.on('new message', (data) => {
socket.on('new message', function (data) {
addChatMessage(data);
});
// Whenever the server emits 'user joined', log it in the chat body
socket.on('user joined', (data) => {
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', (data) => {
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', (data) => {
socket.on('typing', function (data) {
addChatTyping(data);
});
// Whenever the server emits 'stop typing', kill the typing message
socket.on('stop typing', (data) => {
socket.on('stop typing', function (data) {
removeChatTyping(data);
});
socket.on('disconnect', () => {
log('you have been disconnected');
});
socket.on('reconnect', () => {
log('you have been reconnected');
if (username) {
socket.emit('add user', username);
}
});
socket.on('reconnect_error', () => {
log('attempt to reconnect has failed');
});
});

View File

@@ -128,6 +128,7 @@ ul {
}
.username {
float: left;
font-weight: 700;
overflow: hidden;
padding-right: 15px;

View File

@@ -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.

View File

@@ -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"

View File

@@ -1,2 +0,0 @@
FROM haproxy:1.7-alpine
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg

View File

@@ -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

View File

@@ -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" ]

View File

@@ -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
});
}
});
});

View File

@@ -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"
}
}

View File

@@ -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>

View File

@@ -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);
})
});

View File

@@ -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%;
}

View File

@@ -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.

View File

@@ -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"

View File

@@ -1,2 +0,0 @@
FROM httpd:2.4-alpine
COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf

View File

@@ -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

View File

@@ -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" ]

View File

@@ -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
});
}
});
});

View File

@@ -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"
}
}

View File

@@ -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>

View File

@@ -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);
})
});

View File

@@ -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%;
}

View File

@@ -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.

View File

@@ -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"

View File

@@ -1,3 +0,0 @@
FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf

View File

@@ -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;
}
}

View File

@@ -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" ]

View File

@@ -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
});
}
});
});

View File

@@ -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": "MIT",
"dependencies": {
"express": "4.13.4",
"socket.io": "^1.7.2",
"socket.io-redis": "^3.0.0"
},
"scripts": {
"start": "node index.js"
}
}

View File

@@ -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>

View File

@@ -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);
})
});

View File

@@ -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%;
}

View File

@@ -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: `Buffer.allocUnsafe(1000), where buf[i] = i`
## How to use
```
$ npm i && npm start
```
## Results
| bytes / packet | CONNECT packet | string | numeric | binary |
|----------------|----------------|--------|---------|-----------|
| default | 1 | 5903 | 3904 | 43 + 1000 |
| msgpack | 20 | 3919 | 2646 | 1029 |
| JSON | 20 | 5930 | 3931 | 3625 |
| schemapack | 20 | 3895 | 2005 | 1005 |
## Comparison
`default parser`
- supports any serializable datastructure, including Blob and File
- **but** binary payload is encoded as 2 packets
`msgpack`
- the size of payloads containing mostly numeric values will be greatly reduced
- **but** rely on [ArrayBuffer](https://caniuse.com/#feat=typedarrays) in the browser (IE > 9)
`JSON`
- optimized
- **but** does not support binary payloads
`schemapack`
- the most efficient in both speed and size
- **but** you have to provide a schema for each packet

View File

@@ -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"
}
}

View File

@@ -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>

View File

@@ -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));

View File

@@ -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));

View File

@@ -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));

View File

@@ -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));

View File

@@ -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;

View File

@@ -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 = Buffer.allocUnsafe(1e3);
for (var i = 0; i < 1e3; i++) {
string.push('' + i);
numeric.push(i);
binary[i] = i;
}
server1.on('connect', onConnect(1000));
server2.on('connect', onConnect(2000));
server3.on('connect', onConnect(3000));
server4.on('connect', onConnect(4000));
function onConnect (delay) {
return function (socket) {
console.log('connect ' + socket.id);
setTimeout(() => {
socket.emit('string', string);
socket.emit('numeric', numeric);
socket.emit('binary', binary);
}, delay);
socket.on('disconnect', () => console.log('disconnect ' + socket.id));
};
}

View File

@@ -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'
}
};

View File

@@ -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.

View File

@@ -1,16 +0,0 @@
const server = require('http').createServer();
const io = require('socket.io')(server, {
serveClient: false,
wsEngine: 'ws' // uws is not supported since it is a native module
});
const port = process.env.PORT || 3000;
io.on('connect', onConnect);
server.listen(port, () => console.log('server listening on port ' + port));
function onConnect(socket){
console.log('connect ' + socket.id);
socket.on('disconnect', () => console.log('disconnect ' + socket.id));
}

View File

@@ -1,15 +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",
"devDependencies": {
"socket.io": "^2.0.3",
"webpack": "^2.6.1"
}
}

View File

@@ -1,9 +0,0 @@
module.exports = {
entry: './lib/index.js',
target: 'node',
output: {
path: require('path').join(__dirname, '../dist'),
filename: 'server.js'
}
};

View File

@@ -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.

View File

@@ -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>

View File

@@ -1,12 +0,0 @@
import io from 'socket.io-client';
const socket = io('http://localhost:3000');
console.log('init');
socket.on('connect', onConnect);
function onConnect(){
console.log('connect ' + socket.id);
}

View File

@@ -1,21 +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-json-parser": "webpack --config ./support/webpack.config.json-parser.js",
"build-all": "npm run build && npm run build-slim && npm run build-json-parser"
},
"author": "Damien Arrachequesne",
"license": "MIT",
"dependencies": {
"socket.io-client": "^2.0.2",
"socket.io-json-parser": "^2.1.0"
},
"devDependencies": {
"strip-loader": "^0.1.2",
"webpack": "^2.6.1"
}
}

View File

@@ -1,2 +0,0 @@
module.exports = function () { return function () {}; };

View File

@@ -1,8 +0,0 @@
module.exports = {
entry: './lib/index.js',
output: {
path: require('path').join(__dirname, '../dist'),
filename: 'app.js'
}
};

View File

@@ -1,33 +0,0 @@
var webpack = require('webpack');
module.exports = {
entry: './lib/index.js',
output: {
path: require('path').join(__dirname, '../dist'),
filename: 'app.json-parser.js'
},
// generate sourcemap
devtool: 'source-map',
plugins: [
// replace require('debug')() with an noop function
new webpack.NormalModuleReplacementPlugin(/debug/, process.cwd() + '/support/noop.js'),
// replace socket.io-parser with socket.io-json-parser
new webpack.NormalModuleReplacementPlugin(/socket\.io-parser/, 'socket.io-json-parser'),
// use uglifyJS (IE9+ support)
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
],
module: {
loaders: [
{
// strip `debug()` calls
test: /\.js$/,
loader: 'strip-loader?strip[]=debug'
}
]
}
};

View File

@@ -1,31 +0,0 @@
var webpack = require('webpack');
module.exports = {
entry: './lib/index.js',
output: {
path: require('path').join(__dirname, '../dist'),
filename: 'app.slim.js'
},
// generate sourcemap
devtool: 'source-map',
plugins: [
// replace require('debug')() with an noop function
new webpack.NormalModuleReplacementPlugin(/debug/, process.cwd() + '/support/noop.js'),
// use uglifyJS (IE9+ support)
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
],
module: {
loaders: [
{
// strip `debug()` calls
test: /\.js$/,
loader: 'strip-loader?strip[]=debug'
}
]
}
};

View File

@@ -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

View File

@@ -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));

View File

@@ -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"
}

View File

@@ -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>

View File

@@ -1,106 +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);
//Touch support for mobile devices
canvas.addEventListener('touchstart', onMouseDown, false);
canvas.addEventListener('touchend', onMouseUp, false);
canvas.addEventListener('touchcancel', onMouseUp, false);
canvas.addEventListener('touchmove', throttle(onMouseMove, 10), false);
for (var i = 0; i < colors.length; i++){
colors[i].addEventListener('click', onColorUpdate, false);
}
socket.on('drawing', onDrawingEvent);
window.addEventListener('resize', onResize, false);
onResize();
function drawLine(x0, y0, x1, y1, color, emit){
context.beginPath();
context.moveTo(x0, y0);
context.lineTo(x1, y1);
context.strokeStyle = color;
context.lineWidth = 2;
context.stroke();
context.closePath();
if (!emit) { return; }
var w = canvas.width;
var h = canvas.height;
socket.emit('drawing', {
x0: x0 / w,
y0: y0 / h,
x1: x1 / w,
y1: y1 / h,
color: color
});
}
function onMouseDown(e){
drawing = true;
current.x = e.clientX||e.touches[0].clientX;
current.y = e.clientY||e.touches[0].clientY;
}
function onMouseUp(e){
if (!drawing) { return; }
drawing = false;
drawLine(current.x, current.y, e.clientX||e.touches[0].clientX, e.clientY||e.touches[0].clientY, current.color, true);
}
function onMouseMove(e){
if (!drawing) { return; }
drawLine(current.x, current.y, e.clientX||e.touches[0].clientX, e.clientY||e.touches[0].clientY, current.color, true);
current.x = e.clientX||e.touches[0].clientX;
current.y = e.clientY||e.touches[0].clientY;
}
function onColorUpdate(e){
current.color = e.target.className.split(' ')[1];
}
// limit the number of events per second
function throttle(callback, delay) {
var previousCall = new Date().getTime();
return function() {
var time = new Date().getTime();
if ((time - previousCall) >= delay) {
previousCall = time;
callback.apply(null, arguments);
}
};
}
function onDrawingEvent(data){
var w = canvas.width;
var h = canvas.height;
drawLine(data.x0 * w, data.y0 * h, data.x1 * w, data.y1 * h, data.color);
}
// make the canvas fill its parent
function onResize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
})();

View File

@@ -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; }

2
index.js Normal file
View File

@@ -0,0 +1,2 @@
module.exports = require('./lib');

View File

@@ -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,53 +54,45 @@ Client.prototype.setup = function(){
/**
* Connects a client to a namespace.
*
* @param {String} name namespace
* @param {Object} query the query parameters
* @param {String} namespace name
* @api private
*/
Client.prototype.connect = function(name, query){
if (this.server.nsps[name]) {
debug('connecting to namespace %s', name);
return this.doConnect(name, query);
Client.prototype.connect = function(name){
var self = this;
debug('connecting to namespace %s', name);
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 = [];
}
});
}
this.server.checkNamespace(name, query, (dynamicNsp) => {
if (dynamicNsp) {
debug('dynamic namespace %s was created', dynamicNsp.name);
this.doConnect(name, query);
} else {
debug('creation of namespace %s was denied', name);
this.packet({ type: parser.ERROR, nsp: name, data: 'Invalid namespace' });
}
});
};
/**
* Connects a client to a namespace.
*
* @param {String} name namespace
* @param {String} query the query parameters
* @api private
*/
Client.prototype.doConnect = function(name, query){
var nsp = this.server.of(name);
if ('/' != name && !this.nsps['/']) {
this.connectBuffer.push(name);
if (self.server.nsps[name]) {
// Namespace already created, connect
connectNamespace();
return;
}
var self = this;
var socket = nsp.add(this, query, function(){
self.sockets[socket.id] = socket;
self.nsps[nsp.name] = socket;
if ('/' == nsp.name && self.connectBuffer.length > 0) {
self.connectBuffer.forEach(self.connect, self);
self.connectBuffer = [];
self.server.checkNamespace(name, function(allow) {
if (allow) {
connectNamespace();
return
}
self.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'});
});
};
@@ -112,12 +103,12 @@ Client.prototype.doConnect = 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();
};
@@ -128,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);
@@ -155,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);
}
@@ -206,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);
}
@@ -222,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');
};
/**
@@ -249,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
};

View File

@@ -1,4 +1,3 @@
'use strict';
/**
* Module dependencies.
@@ -6,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 clientVersion = require('socket.io-client/package.json').version;
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 ParentNamespace = require('./parent-namespace');
var Adapter = require('socket.io-adapter');
var parser = require('socket.io-parser');
var debug = require('debug')('socket.io:server');
var url = require('url');
@@ -29,30 +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.parentNsps = new Map();
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('/');
@@ -62,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) {
@@ -82,11 +75,9 @@ Server.prototype.checkRequest = function(req, fn) {
? parts.port
: defaultPort;
var ok =
~this._origins.indexOf(parts.protocol + '//' + parts.hostname + ':' + parts.port) ||
~this._origins.indexOf(parts.hostname + ':' + parts.port) ||
~this._origins.indexOf(parts.hostname + ':*') ||
~this._origins.indexOf('*:' + parts.port);
debug('origin %s is %svalid', origin, !!ok ? '' : 'not ');
return fn(null, !!ok);
} catch (ex) {
}
@@ -97,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
*/
@@ -105,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;
};
@@ -135,7 +111,7 @@ var oldSettings = {
};
/**
* Backwards compatibility.
* Backwards compatiblity.
*
* @api public
*/
@@ -162,41 +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 {String} name name of incoming namespace
* @param {Object} query the query parameters
* @param {Function} fn callback
* @param name of incomming namespace
* @param {Function} last fn call in the middleware
* @api private
*/
Server.prototype.checkNamespace = function(name, query, fn){
if (this.parentNsps.size === 0) return fn(false);
Server.prototype.checkNamespace = function(name, fn){
var fns = this.nspValidators.slice(0);
if (!fns.length) return fn(false);
const keysIterator = this.parentNsps.keys();
var namespaceAllowed = false; // Deny unknown namespaces by default
const run = () => {
let nextFn = keysIterator.next();
if (nextFn.done) {
return fn(false);
}
nextFn.value(name, query, (err, allow) => {
if (err || !allow) {
run();
} else {
fn(this.parentNsps.get(nextFn.value).createChild(name));
}
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();
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
*/
@@ -210,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
*/
@@ -229,7 +221,7 @@ Server.prototype.adapter = function(v){
/**
* Sets the allowed origins for requests.
*
* @param {String|String[]} v origins
* @param {String} origins
* @return {Server|Adapter} self when setting or value when getting
* @api public
*/
@@ -278,33 +270,8 @@ 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);
if (this.sockets.fns.length > 0) {
this.initEngine(srv, opts);
return this;
}
var self = this;
var connectPacket = { type: parser.CONNECT, nsp: '/' };
this.encoder.encode(connectPacket, function (encodedPacket){
// the CONNECT packet will be merged with Engine.IO handshake,
// to reduce the number of round trips
opts.initialPacket = encodedPacket;
self.initEngine(srv, opts);
});
return this;
};
/**
* Initialize engine
*
* @param {Object} options passed to engine.io
* @api private
*/
Server.prototype.initEngine = function(srv, opts){
// initialize engine
debug('creating engine.io instance with opts %j', opts);
this.eio = engine.attach(srv, opts);
@@ -317,26 +284,25 @@ Server.prototype.initEngine = function(srv, opts){
// bind to engine events
this.bind(this.eio);
return this;
};
/**
* 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++) {
@@ -355,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();
@@ -370,47 +332,16 @@ Server.prototype.serve = function(req, res){
}
debug('serve client source');
res.setHeader("Cache-Control", "public, max-age=0");
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
*/
@@ -424,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
*/
@@ -439,56 +370,38 @@ Server.prototype.onconnection = function(conn){
/**
* Looks up a namespace.
*
* @param {String|RegExp|Function} 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 (typeof name === 'function' || name instanceof RegExp) {
const parentNsp = new ParentNamespace(this);
debug('initializing parent namespace %s', parentNsp.name);
if (typeof name === 'function') {
this.parentNsps.set(name, parentNsp);
} else {
this.parentNsps.set((nsp, conn, next) => next(null, name.test(nsp)), parentNsp);
}
if (fn) parentNsp.on('connect', fn);
return parentNsp;
}
if (String(name)[0] !== '/') name = '/' + name;
var nsp = this.nsps[name];
if (!nsp) {
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();
}
};
@@ -496,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', 'binary']).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;
});
});

View File

@@ -6,8 +6,8 @@
var Socket = require('./socket');
var Emitter = require('events').EventEmitter;
var parser = require('socket.io-parser');
var hasBin = require('has-binary2');
var debug = require('debug')('socket.io:namespace');
var hasBin = require('has-binary');
/**
* Module exports.
@@ -29,11 +29,7 @@ exports.events = [
* Flags.
*/
exports.flags = [
'json',
'volatile',
'local'
];
exports.flags = ['json'];
/**
* `EventEmitter#emit` reference.
@@ -52,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();
}
@@ -72,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;
});
});
@@ -100,10 +94,6 @@ Namespace.prototype.initAdapter = function(){
*/
Namespace.prototype.use = function(fn){
if (this.server.eio && this.name === '/') {
debug('removing initial packet');
delete this.server.eio.initialPacket;
}
this.fns.push(fn);
return this;
};
@@ -112,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
*/
@@ -145,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;
};
@@ -157,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(){
@@ -167,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
@@ -194,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);
}
@@ -211,31 +203,26 @@ Namespace.prototype.remove = function(socket){
Namespace.prototype.emit = function(ev){
if (~exports.events.indexOf(ev)) {
emit.apply(this, arguments);
return this;
} else {
// set up packet object
var args = Array.prototype.slice.call(arguments);
var parserType = parser.EVENT; // default
if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary
var packet = { type: parserType, data: args };
if ('function' == typeof args[args.length - 1]) {
throw new Error('Callbacks are not supported when broadcasting');
}
this.adapter.broadcast(packet, {
rooms: this.rooms,
flags: this.flags
});
delete this.rooms;
delete this.flags;
}
// set up packet object
var args = Array.prototype.slice.call(arguments);
var packet = {
type: (this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT : parser.EVENT,
data: args
};
if ('function' == typeof args[args.length - 1]) {
throw new Error('Callbacks are not supported when broadcasting');
}
var rooms = this.rooms.slice(0);
var flags = Object.assign({}, this.flags);
// reset flags
this.rooms = [];
this.flags = {};
this.adapter.broadcast(packet, {
rooms: rooms,
flags: flags
});
return this;
};
@@ -262,38 +249,6 @@ Namespace.prototype.write = function(){
*/
Namespace.prototype.clients = function(fn){
if(!this.adapter){
throw new Error('No adapter for this namespace, are you trying to get the list of clients of a dynamic namespace?')
}
this.adapter.clients(this.rooms, fn);
// reset rooms for scenario:
// .in('room').clients() (GH-1978)
this.rooms = [];
return this;
};
/**
* Sets the compress flag.
*
* @param {Boolean} compress if `true`, compresses the sending data
* @return {Socket} self
* @api public
*/
Namespace.prototype.compress = function(compress){
this.flags.compress = compress;
return this;
};
/**
* Sets the binary flag
*
* @param {Boolean} Encode as if it has binary data if `true`, Encode as if it doesnt have binary data if `false`
* @return {Socket} self
* @api public
*/
Namespace.prototype.binary = function (binary) {
this.flags.binary = binary;
return this;
};

View File

@@ -1,39 +0,0 @@
'use strict';
const Namespace = require('./namespace');
let count = 0;
class ParentNamespace extends Namespace {
constructor(server) {
super(server, '/_' + (count++));
this.children = new Set();
}
initAdapter() {}
emit() {
const args = Array.prototype.slice.call(arguments);
this.children.forEach(nsp => {
nsp.rooms = this.rooms;
nsp.flags = this.flags;
nsp.emit.apply(nsp, args);
});
this.rooms = [];
this.flags = {};
}
createChild(name) {
const namespace = new Namespace(this.server, name);
namespace.fns = this.fns.slice(0);
this.listeners('connect').forEach(listener => namespace.on('connect', listener));
this.listeners('connection').forEach(listener => namespace.on('connection', listener));
this.children.add(namespace);
this.server.nsps[name] = namespace;
return namespace;
}
}
module.exports = ParentNamespace;

View File

@@ -5,9 +5,9 @@
var Emitter = require('events').EventEmitter;
var parser = require('socket.io-parser');
var hasBin = require('has-binary2');
var url = require('url');
var debug = require('debug')('socket.io:socket');
var hasBin = require('has-binary');
/**
* Module exports.
@@ -25,7 +25,6 @@ exports.events = [
'error',
'connect',
'disconnect',
'disconnecting',
'newListener',
'removeListener'
];
@@ -39,8 +38,7 @@ exports.events = [
var flags = [
'json',
'volatile',
'broadcast',
'local'
'broadcast'
];
/**
@@ -57,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();
}
/**
@@ -85,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;
});
/**
@@ -111,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 Object.assign({}, query, requestQuery);
}
Socket.prototype.buildHandshake = function(){
return {
headers: this.request.headers,
time: (new Date) + '',
@@ -126,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 || {}
};
};
@@ -140,42 +127,37 @@ Socket.prototype.buildHandshake = function(query){
Socket.prototype.emit = function(ev){
if (~exports.events.indexOf(ev)) {
emit.apply(this, arguments);
return this;
}
} else {
var args = Array.prototype.slice.call(arguments);
var packet = {};
packet.type = hasBin(args) ? parser.BINARY_EVENT : parser.EVENT;
packet.data = args;
var args = Array.prototype.slice.call(arguments);
var packet = {
type: (this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT : parser.EVENT,
data: args
};
// access last argument to see if it's an ACK callback
if ('function' == typeof args[args.length - 1]) {
if (this._rooms || (this.flags && this.flags.broadcast)) {
throw new Error('Callbacks are not supported when broadcasting');
}
// access last argument to see if it's an ACK callback
if (typeof args[args.length - 1] === 'function') {
if (this._rooms.length || this.flags.broadcast) {
throw new Error('Callbacks are not supported when broadcasting');
debug('emitting packet with ack id %d', this.nsp.ids);
this.acks[this.nsp.ids] = args.pop();
packet.id = this.nsp.ids++;
}
debug('emitting packet with ack id %d', this.nsp.ids);
this.acks[this.nsp.ids] = args.pop();
packet.id = this.nsp.ids++;
}
if (this._rooms || (this.flags && this.flags.broadcast)) {
this.adapter.broadcast(packet, {
except: [this.id],
rooms: this._rooms,
flags: this.flags
});
} else {
// dispatch packet
this.packet(packet);
}
var rooms = this._rooms.slice(0);
var flags = Object.assign({}, this.flags);
// reset flags
this._rooms = [];
this.flags = {};
if (rooms.length || flags.broadcast) {
this.adapter.broadcast(packet, {
except: [this.id],
rooms: rooms,
flags: flags
});
} else {
// dispatch packet
this.packet(packet, flags);
// reset flags
delete this._rooms;
delete this.flags;
}
return this;
};
@@ -190,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;
};
@@ -213,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;
@@ -261,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
*/
@@ -272,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;
@@ -286,28 +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);
var skip = this.nsp.name === '/' && this.nsp.fns.length === 0;
if (skip) {
debug('packet already sent in initial handshake');
} else {
this.packet({ type: parser.CONNECT });
}
this.packet({ type: parser.CONNECT });
this.nsp.connected[this.id] = this;
};
/**
@@ -341,7 +308,7 @@ Socket.prototype.onpacket = function(packet){
break;
case parser.ERROR:
this.onerror(new Error(packet.data));
this.emit('error', packet.data);
}
};
@@ -361,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
*/
@@ -380,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: hasBin(args) ? parser.BINARY_ACK : parser.ACK,
type: type,
data: args
});
sent = true;
};
};
@@ -437,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);
@@ -457,7 +422,7 @@ Socket.prototype.onclose = function(reason){
/**
* Produces an `error` packet.
*
* @param {Object} err error object
* @param {Object} error object
* @api private
*/
@@ -468,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
*/
@@ -487,86 +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;
};
/**
* Sets the binary flag
*
* @param {Boolean} Encode as if it has binary data if `true`, Encode as if it doesnt have binary data if `false`
* @return {Socket} self
* @api public
*/
Socket.prototype.binary = function (binary) {
this.flags.binary = binary;
return this;
};
/**
* Dispatch incoming event to socket listeners.
*
* @param {Array} event that will get emitted
* @api private
*/
Socket.prototype.dispatch = function(event){
debug('dispatching an event %j', event);
var self = this;
function dispatchSocket(err) {
process.nextTick(function(){
if (err) {
return self.error(err.data || err.message);
}
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);
};

View File

@@ -1,6 +1,6 @@
{
"name": "socket.io",
"version": "2.3.0",
"version": "1.3.2",
"description": "node.js realtime framework server",
"keywords": [
"realtime",
@@ -11,32 +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": "nyc mocha --reporter spec --slow 200 --bail --timeout 10000 test/socket.io.js"
"test": "mocha --reporter dot --slow 200ms --bail"
},
"dependencies": {
"debug": "~4.1.0",
"engine.io": "~3.4.0",
"has-binary2": "~1.0.2",
"socket.io-adapter": "~1.1.0",
"socket.io-client": "2.3.0",
"socket.io-parser": "~3.4.0"
"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": {
"mocha": "1.16.2",
"expect.js": "0.3.1",
"mocha": "^3.5.3",
"nyc": "^11.2.1",
"superagent": "^3.8.2",
"supertest": "^3.0.0"
"supertest": "0.8.2",
"superagent": "0.17.0",
"istanbul": "0.2.3"
},
"contributors": [
{

View File

@@ -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();
});
});

View File

@@ -1,10 +1,8 @@
'use strict';
var http = require('http').Server;
var 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');
@@ -23,7 +21,7 @@ function client(srv, nsp, opts){
describe('socket.io', function(){
it.skip('should be the same version as client', function(){
it('should be the same version as client', function(){
var version = require('../package').version;
expect(version).to.be(require('socket.io-client/package').version);
});
@@ -162,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();
@@ -174,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);
@@ -246,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();
});
});
@@ -257,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();
});
});
@@ -300,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();
});
});
@@ -330,43 +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();
});
});
it('should allow request when using an array of origins', function(done) {
io({ origins: [ 'http://foo.example:54024' ] }).listen('54024');
request.get('http://localhost:54024/socket.io/default/')
.set('origin', 'http://foo.example:54024')
.query({ transport: 'polling' })
.end(function (err, res) {
expect(res.status).to.be(200);
done();
});
});
});
describe('close', function(){
@@ -381,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();
});
@@ -405,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();
});
@@ -425,16 +386,6 @@ 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(){
@@ -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,138 +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);
});
});
});
describe('dynamic namespaces', function () {
it('should allow connections to dynamic namespaces with a regex', function(done){
const srv = http();
const sio = io(srv);
let count = 0;
srv.listen(function(){
const socket = client(srv, '/dynamic-101');
let dynamicNsp = sio.of(/^\/dynamic-\d+$/).on('connect', (socket) => {
expect(socket.nsp.name).to.be('/dynamic-101');
dynamicNsp.emit('hello', 1, '2', { 3: '4'});
if (++count === 4) done();
}).use((socket, next) => {
next();
if (++count === 4) done();
});
socket.on('error', function(err) {
sio.on('connect', function(socket) {
if (socket.nsp.name === namespace) {
expect().fail();
});
socket.on('connect', () => {
if (++count === 4) done();
});
socket.on('hello', (a, b, c) => {
expect(a).to.eql(1);
expect(b).to.eql('2');
expect(c).to.eql({ 3: '4' });
if (++count === 4) done();
});
}
});
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 allow connections to dynamic namespaces with a function', function(done){
const srv = http();
const sio = io(srv);
srv.listen(function(){
const socket = client(srv, '/dynamic-101');
sio.of((name, query, next) => next(null, '/dynamic-101' === name));
socket.on('connect', 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();
}
});
});
it('should disallow connections when no dynamic namespace matches', function(done){
const srv = http();
const sio = io(srv);
srv.listen(function(){
const socket = client(srv, '/abc');
sio.of(/^\/dynamic-\d+$/);
sio.of((name, query, next) => next(null, '/dynamic-101' === name));
socket.on('error', (err) => {
expect(err).to.be('Invalid namespace');
done();
});
var dynamic = client(srv,namespace);
dynamic.on('connect', function(){
expect().fail();
});
dynamic.on('error', function(err) {
expect(err).to.be("Invalid namespace");
done();
});
});
});
@@ -1033,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();
});
});
@@ -1126,7 +982,7 @@ describe('socket.io', function(){
sio.on('connection', function(s){
fs.readFile(join(__dirname, 'support', 'doge.jpg'), function(err, data){
if (err) return done(err);
var buf = Buffer.from('asdfasdf', 'utf8');
var buf = new Buffer('asdfasdf', 'utf8');
s.emit('multiple', 1, data, '3', [4], buf, [data, 'swag', buf]);
});
});
@@ -1143,7 +999,7 @@ describe('socket.io', function(){
expect(Buffer.isBuffer(a)).to.be(true);
done();
});
var buf = Buffer.from('abcdefg', 'utf8');
var buf = new Buffer('abcdefg', 'utf8');
socket.emit('buff', buf);
});
});
@@ -1168,7 +1024,7 @@ describe('socket.io', function(){
});
fs.readFile(join(__dirname, 'support', 'doge.jpg'), function(err, data){
if (err) return done(err);
var buf = Buffer.from('asdfasdf', 'utf8');
var buf = new Buffer('asdfasdf', 'utf8');
socket.emit('multiple', 1, data, '3', [4], buf, [data, 'swag', buf]);
});
});
@@ -1231,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'] });
@@ -1243,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) {
@@ -1282,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'] });
@@ -1294,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) {
@@ -1496,7 +1352,7 @@ describe('socket.io', function(){
expect(Buffer.isBuffer(buf)).to.be(true);
fn(1, 2);
});
socket.emit('woot', Buffer.alloc(3), function(a, b){
socket.emit('woot', new Buffer(3), function(a, b){
expect(a).to.be(1);
expect(b).to.be(2);
done();
@@ -1515,7 +1371,7 @@ describe('socket.io', function(){
expect(Buffer.isBuffer(a)).to.be(true);
fn();
});
s.emit('hi', Buffer.alloc(4), function(){
s.emit('hi', new Buffer(4), function(){
done();
});
});
@@ -1529,7 +1385,7 @@ describe('socket.io', function(){
var socket = client(srv);
sio.on('connection', function(s){
socket.on('hi', function(fn){
fn(Buffer.alloc(1));
fn(new Buffer(1));
});
s.emit('hi', function(a){
expect(Buffer.isBuffer(a)).to.be(true);
@@ -1546,7 +1402,7 @@ describe('socket.io', function(){
var socket = client(srv);
sio.on('connection', function(s){
s.on('woot', function(fn){
fn(Buffer.alloc(2));
fn(new Buffer(2));
});
socket.emit('woot', function(a){
expect(Buffer.isBuffer(a)).to.be(true);
@@ -1598,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);
@@ -1608,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);
@@ -1653,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);
@@ -1695,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);
});
});
});
@@ -1708,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');
});
});
});
@@ -1723,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');
});
});
});
@@ -1738,10 +1582,10 @@ 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('\u001b[96mNote: warning expected and normal in test.\u001b[39m');
console.log('\033[96mNote: warning expected and normal in test.\033[39m');
socket.io.engine.write('5woooot');
setTimeout(function(){
done();
@@ -1751,28 +1595,11 @@ describe('socket.io', function(){
});
});
it('should not crash when receiving an error packet without handler', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
var socket = client(srv, { reconnection: false });
sio.on('connection', function(s){
s.conn.on('upgrade', function(){
console.log('\u001b[96mNote: warning expected and normal in test.\u001b[39m');
socket.io.engine.write('44["handle me please"]');
setTimeout(function(){
done();
}, 100);
});
});
});
});
it('should not crash with raw binary', function(done){
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/);
@@ -1784,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(){
@@ -1899,7 +1675,7 @@ describe('socket.io', function(){
});
function emit(){
sio.emit('bin', Buffer.alloc(10));
sio.emit('bin', new Buffer(10));
}
});
});
@@ -2083,8 +1859,8 @@ describe('socket.io', function(){
socket.join(room, fn);
});
socket.on('broadcast', function(){
socket.broadcast.to('test').emit('bin', Buffer.alloc(5));
socket.emit('bin2', Buffer.alloc(5));
socket.broadcast.to('test').emit('bin', new Buffer(5));
socket.emit('bin2', new Buffer(5));
});
});
});
@@ -2099,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();
});
});
@@ -2143,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();
});
});
@@ -2157,21 +1933,6 @@ 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){
@@ -2259,26 +2020,6 @@ describe('socket.io', function(){
});
});
it('should only call connection after (lengthy) fns', function(done){
var srv = http();
var sio = io(srv);
var authenticated = false;
sio.use(function(socket, next){
setTimeout(function () {
authenticated = true;
next();
}, 300);
});
srv.listen(function(){
var socket = client(srv);
socket.on('connect', function(){
expect(authenticated).to.be(true);
done();
});
});
});
it('should be ignored if socket gets closed', function(done){
var srv = http();
var sio = io(srv);
@@ -2332,128 +2073,5 @@ describe('socket.io', function(){
});
});
});
it('should disable the merge of handshake packets', function(done){
var srv = http();
var sio = io();
sio.use(function(socket, next){
next();
});
sio.listen(srv);
var socket = client(srv);
socket.on('connect', function(){
done();
});
});
it('should work with a custom namespace', (done) => {
var srv = http();
var sio = io();
sio.listen(srv);
sio.of('/chat').use(function(socket, next){
next();
});
var count = 0;
client(srv, '/').on('connect', () => {
if (++count === 2) done();
});
client(srv, '/chat').on('connect', () => {
if (++count === 2) done();
});
});
});
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'));
});
});
});
});
});
});