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
23 changed files with 385 additions and 1137 deletions

View File

@@ -1,25 +0,0 @@
*Note*: for support questions, please use one of these channels: [stackoverflow](http://stackoverflow.com/questions/tagged/socket.io) or [slack](https://socketio.slack.com)
### You want to:
* [x] report a *bug*
* [ ] request a *feature*
### Current behaviour
### Steps to reproduce (if the current behaviour is a bug)
### Expected behaviour
### 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)

2
.gitignore vendored
View File

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

4
.npmignore Normal file
View File

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

View File

@@ -1,28 +1,16 @@
sudo: false
before_install:
- npm install -g npm@'>=1.4.3'
language: node_js
node_js:
- "0.8"
- "0.10"
- "0.12"
- "4"
- "node"
- "0.11"
git:
depth: 1
matrix:
include:
- node_js: '0.10'
env: TEST_VERSION=compat
- node_js: '0.12'
env: TEST_VERSION=compat
- node_js: '4'
env: TEST_VERSION=compat
#matrix:
#fast_finish: true
#allow_failures:
#- node_js: "0.11"
fast_finish: true
allow_failures:
- node_js: "0.11"
notifications:
irc: "irc.freenode.org#socket.io"

View File

@@ -1,177 +1,4 @@
1.7.4 / 2017-05-07
===================
* [chore] Bump engine.io to version 1.8.4
1.7.3 / 2017-02-17
===================
* [chore] Bump engine.io to version 1.8.3
1.7.2 / 2016-12-11
===================
* [chore] Bump engine.io to version 1.8.2 (#2782)
* [fix] Fixes socket.use error packet (#2772)
1.7.1 / 2016-11-28
===================
1.7.0 / 2016-11-27
===================
* [docs] Comment connected socket availability for adapters (#2081)
* [docs] Fixed grammar issues in the README.md (#2159)
* [feature] serve sourcemap for socket.io-client (#2482)
* [feature] Add a `local` flag (#2628)
* [chore] Bump engine.io to version 1.8.1 (#2765)
* [chore] Update client location and serve minified file (#2766)
1.6.0 / 2016-11-20
==================
* [fix] Make ETag header comply with standard. (#2603)
* [feature] Loading client script on demand. (#2567)
* [test] Fix leaking clientSocket (#2721)
* [feature] Add support for all event emitter methods (#2601)
* [chore] Update year to 2016 (#2456)
* [feature] Add support for socket middleware (#2306)
* [feature] add support for Server#close(callback) (#2748)
* [fix] Don't drop query variables on handshake (#2745)
* [example] Add disconnection/reconnection logs to the chat example (#2675)
* [perf] Minor code optimizations (#2219)
* [chore] Bump debug to version 2.3.3 (#2754)
* [chore] Bump engine.io to version 1.8.0 (#2755)
* [chore] Bump socket.io-adapter to version 0.5.0 (#2756)
1.5.1 / 2016-10-24
==================
* [fix] Avoid swallowing exceptions thrown by user event handlers (#2682)
* [test] Use client function to unify `client` in test script (#2731)
* [docs] Add link to LICENSE (#2221)
* [docs] Fix JSDoc of optional parameters (#2465)
* [docs] Fix typo (#2724)
* [docs] Link readme npm package badge to npm registry page (#2612)
* [docs] Minor fixes (#2526)
* [chore] Bump socket.io-parser to 2.3.0 (#2730)
* [chore] Add Github issue and PR templates (#2733)
* [chore] Bump engine.io to 1.7.2 (#2729)
* [chore] Bump socket.io-parser to 2.3.1 (#2734)
1.5.0 / 2016-10-06
==================
* [feature] stop append /# before id when no namespace (#2508)
* [feature] Add a 'disconnecting' event to access to socket.rooms upon disconnection (#2332)
* [fix] Fix query string management (#2422)
* [fix] add quote to exec paths, prevent error when spaces in path (#2508)
* [docs] Prevent mixup for new programmers (#2599)
* [example] Fix chat display in Firefox (#2477)
* [chore] Add gulp & babel in the build process (#2471)
* [chore] Bump engine.io to 1.7.0 (#2707)
* [chore] Remove unused zuul-ngrok dependency (#2708)
* [chore] Point towards current master of socket.io-client (#2710)
* [chore] Restrict files included in npm package (#2709)
* [chore] Link build badge to master branch (#2549)
1.4.8 / 2016-06-23
==================
* package: bump `engine.io`
1.4.7 / 2016-06-23
==================
* package: bump `engine.io`
1.4.6 / 2016-05-02
==================
* package: bump engine.io
1.4.5 / 2016-01-26
==================
* fix closing the underlying `http.Server`
1.4.4 / 2016-01-10
==================
* package: bump `engine.io`
1.4.3 / 2016-01-08
==================
* bump `socket.io-client`
1.4.2 / 2016-01-07
==================
* bump `engine.io`
1.4.1 / 2016-01-07
==================
* version bump
1.4.0 / 2015-11-28
==================
* socket.io: increase large binary data test timeout
* package: bump `engine.io` for release
* trigger callback even when joining an already joined room
* package: bump parser
* namespace: clear rooms flag after a clients call (fixes #1978)
* package: bump `socket.io-parser`
* fixed tests with large data
* fixed a typo in the example code
* package: bump mocha
* package: bump `has-binary` and `zuul-ngrok`
* package: bump `engine.io` and `socket.io-client`
* README: clarified documentation of Socket.in
* README: fixed up legacy repo links
* test: better timeout for stress test
* socket: don't set request property which has a getter
* removed proxy index file
* support flags on namespace
* improve Socket#packet and Client#packet
* socket: warn node_redis-style about missing `error`
* test: added failing test
* test: increase timeout for large binary data test
* package: bump `has-binary` to work with all objects (fixes #1955)
* fix origin verification default https port [evanlucas]
* support compression [nkzawa]
* changed type of `Client#sockets`, `Namespace#sockets` and `Socket#rooms` to maps (instead of arrays)
1.3.7 / 2015-09-21
==================
* package: bump `socket.io-client` for node4 compatibility
* package: bump `engine.io` for node4 compatibility
1.3.6 / 2015-07-14
==================
* package: bump `engine.io` to fix build on windows
1.3.5 / 2015-03-03
==================
* package: bump `socket.io-parser`
1.3.4 / 2015-02-14
==================
* package: bump `socket.io-client`
1.3.3 / 2015-02-03
==================
* socket: warn node_redis-style about missing `error`
* package: bump parser to better handle bad binary packets
1.3.2 / 2015-01-19
==================

View File

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

View File

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

141
Readme.md
View File

@@ -1,12 +1,9 @@
# socket.io
[![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)
[![](http://slack.socket.io/badge.svg?)](http://slack.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
@@ -16,9 +13,9 @@ HTTP server listening on port `3000`.
```js
var server = require('http').createServer();
var io = require('socket.io')(server);
io.on('connection', function(client){
client.on('event', function(data){});
client.on('disconnect', function(){});
io.on('connection', function(socket){
socket.on('event', function(data){});
socket.on('disconnect', function(){});
});
server.listen(3000);
```
@@ -27,7 +24,7 @@ server.listen(3000);
```js
var io = require('socket.io')();
io.on('connection', function(client){});
io.on('connection', function(socket){});
io.listen(3000);
```
@@ -88,7 +85,7 @@ server.listen(3000);
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)
[options](https://github.com/learnboost/engine.io#methods-1)
as reference.
### Server(srv:http#Server, opts:Object)
@@ -130,7 +127,7 @@ server.listen(3000);
Sets the adapter `v`. Defaults to an instance of the `Adapter` that
ships with socket.io which is memory based. See
[socket.io-adapter](https://github.com/socketio/socket.io-adapter).
[socket.io-adapter](https://github.com/Automattic/socket.io-adapter).
If no arguments are supplied this method returns the current value.
@@ -147,7 +144,7 @@ server.listen(3000);
__Potential drawbacks__:
* in some situations, when it is not possible to determine `origin` it may have value of `*`
* As this function will be executed for every request, it is advised to make this function work as fast as possible
* If `socket.io` is used together with `Express`, the CORS headers will be affected only for `socket.io` requests. For Express can use [cors](https://github.com/expressjs/cors).
* 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
@@ -183,7 +180,7 @@ server.listen(3000);
Initializes and retrieves the given `Namespace` by its pathname
identifier `nsp`.
If the namespace was already initialized it returns it immediately.
If the namespace was already initialized it returns it right away.
### Server#emit
@@ -198,14 +195,9 @@ server.listen(3000);
For other available methods, see `Namespace` below.
### Server#close([fn:Function])
### Server#close
Closes socket.io server.
The optional `fn` is passed to the `server.close([callback])` method of the
core `net` module and is called on error or when all connections are closed.
The callback is expected to implement the common single argument `err`
signature (if any).
Closes socket server
```js
var Server = require('socket.io');
@@ -285,7 +277,7 @@ server.listen(3000);
### Namespace#use(fn:Function):Namespace
Registers a middleware, which is a function that gets executed for
every incoming `Socket`, and receives as parameters the socket and a
every incoming `Socket` and receives as parameter the socket and a
function to optionally defer execution to the next registered
middleware.
@@ -305,33 +297,10 @@ server.listen(3000);
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.
### Socket#use(fn:Function):Socket
### Socket#rooms:Array
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.
```js
var io = require('socket.io')();
io.on('connection', function(socket){
socket.use(function(packet, next){
if (packet.doge === true) return next();
next(new Error('Not a doge error'));
});
```
Errors passed to middleware callbacks are sent as special `error`
packets to clients.
### Socket#rooms:Object
A hash of strings identifying the rooms this client is in, indexed by
room name.
A list of strings identifying the rooms this socket is in.
### Socket#client:Client
@@ -340,8 +309,7 @@ server.listen(3000);
### Socket#conn: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` object).
### Socket#request:Request
@@ -351,65 +319,62 @@ server.listen(3000);
### Socket#id:String
A unique identifier for the session, that comes from the
A unique identifier for the socket session, that comes from the
underlying `Client`.
### Socket#emit(name:String[, …]):Socket
Emits an event identified by the string `name` to the client.
Any other parameters can be included.
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(client){
client.emit('an event', { some: 'data' });
io.on('connection', function(socket){
socket.emit('an event', { some: 'data' });
});
```
### Socket#join(name:String[, fn:Function]):Socket
Adds the client to the `room`, and fires optionally a callback `fn`
Adds the socket to the `room`, and fires optionally a callback `fn`
with `err` signature (if any).
The client is automatically a member of a room identified with its
The socket is automatically a member of a room identified with its
session id (see `Socket#id`).
The mechanics of joining rooms are handled by the `Adapter`
that has been configured (see `Server#adapter` above), defaulting to
[socket.io-adapter](https://github.com/socketio/socket.io-adapter).
[socket.io-adapter](https://github.com/Automattic/socket.io-adapter).
### Socket#leave(name:String[, fn:Function]):Socket
Removes the client from `room`, and fires optionally a callback `fn`
Removes the socket from `room`, and fires optionally a callback `fn`
with `err` signature (if any).
**Rooms are left automatically upon disconnection**.
The mechanics of leaving rooms are handled by the `Adapter`
that has been configured (see `Server#adapter` above), defaulting to
[socket.io-adapter](https://github.com/socketio/socket.io-adapter).
[socket.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 clients that have joined the given `room`.
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(client){
client.to('others').emit('an event', { some: 'data' });
io.on('connection', function(socket){
socket.to('others').emit('an event', { some: 'data' });
});
```
### Socket#in(room:String):Socket
Same as `Socket#to`
### Socket#compress(v:Boolean):Socket
Sets a modifier for a subsequent event emission that the event data will
@@ -417,38 +382,15 @@ server.listen(3000);
```js
var io = require('socket.io')();
io.on('connection', function(client){
client.compress(false).emit('an event', { some: 'data' });
io.on('connection', function(socket){
socket.compress(false).emit('an event', { some: 'data' });
});
```
### Socket#disconnect(close:Boolean):Socket
Disconnects this client. If value of close is `true`, closes the underlying connection.
Otherwise, it just disconnects the namespace.
#### Events
- `disconnect`
- Fired upon disconnection.
- **Arguments**
- `String`: the reason of the disconnection (either client or server-side)
- `error`
- Fired when an error occurs.
- **Arguments**
- `Object`: error data
- `disconnecting`
- Fired when the client is going to be disconnected (but hasn't left its `rooms` yet).
- **Arguments**
- `String`: the reason of the disconnection (either client or server-side)
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
connection. A `Client` can be associated with many multiplexed `Socket`
that belong to different `Namespace`s.
### Client#conn
@@ -463,7 +405,7 @@ These are reserved events (along with `connect`, `newListener` and `removeListen
## 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.
@@ -473,17 +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.
## License
[MIT](LICENSE)
MIT

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

@@ -14,6 +14,8 @@ app.use(express.static(__dirname + '/public'));
// Chatroom
// usernames which are currently connected to the chat
var usernames = {};
var numUsers = 0;
io.on('connection', function (socket) {
@@ -30,10 +32,10 @@ io.on('connection', function (socket) {
// when the client emits 'add user', this listens and executes
socket.on('add user', function (username) {
if (addedUser) return;
// we store the username in the socket session for this client
socket.username = username;
// add the client's username to the global list
usernames[username] = username;
++numUsers;
addedUser = true;
socket.emit('login', {
@@ -62,7 +64,9 @@ io.on('connection', function (socket) {
// when the user disconnects.. perform this
socket.on('disconnect', function () {
// remove the username from global usernames list
if (addedUser) {
delete usernames[socket.username];
--numUsers;
// echo globally that this client has left

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
@@ -263,20 +263,4 @@ $(function() {
socket.on('stop typing', function (data) {
removeChatTyping(data);
});
socket.on('disconnect', function () {
log('you have been disconnected');
});
socket.on('reconnect', function () {
log('you have been reconnected');
if (username) {
socket.emit('add user', username);
}
});
socket.on('reconnect_error', function () {
log('attempt to reconnect has failed');
});
});

View File

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

View File

@@ -1,69 +0,0 @@
const gulp = require('gulp');
const mocha = require('gulp-mocha');
const babel = require("gulp-babel");
const istanbul = require('gulp-istanbul');
const help = require('gulp-task-listing');
const del = require('del');
gulp.task('help', help);
gulp.task('default', ['transpile']);
const TRANSPILE_DEST_DIR = './dist';
// By default, individual js files are transformed by babel and exported to /dist
gulp.task('transpile', function () {
return gulp.src("lib/*.js")
.pipe(babel({ "presets": ["es2015"] }))
.pipe(gulp.dest(TRANSPILE_DEST_DIR));
});
gulp.task('clean', function () {
return del([TRANSPILE_DEST_DIR]);
})
gulp.task('test', ['transpile'], function(){
return gulp.src('test/socket.io.js', {read: false})
.pipe(mocha({
slow: 200,
reporter: 'spec',
bail: true,
timeout: 10000
}))
.once('error', function (err) {
console.error(err.stack);
process.exit(1);
})
.once('end', function () {
process.exit();
});
});
gulp.task('set-compat-node-env', function() {
process.env.TEST_VERSION = 'compat';
});
gulp.task('test-compat', ['set-compat-node-env', 'test']);
gulp.task('istanbul-pre-test', function () {
return gulp.src(['lib/**/*.js'])
// Covering files
.pipe(istanbul())
// Force `require` to return covered files
.pipe(istanbul.hookRequire());
});
gulp.task('test-cov', ['istanbul-pre-test'], function(){
return gulp.src('test/socket.io.js', {read: false})
.pipe(mocha({
reporter: 'dot'
}))
.pipe(istanbul.writeReports())
.once('error', function (err){
console.error(err.stack);
process.exit(1);
})
.once('end', function (){
process.exit();
});
});

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,7 +16,7 @@ module.exports = Client;
* Client constructor.
*
* @param {Server} server instance
* @param {Socket} conn
* @param {Socket} connection
* @api private
*/
@@ -29,7 +28,7 @@ function Client(server, conn){
this.id = conn.id;
this.request = conn.request;
this.setup();
this.sockets = {};
this.sockets = [];
this.nsps = {};
this.connectBuffer = [];
}
@@ -55,32 +54,45 @@ Client.prototype.setup = function(){
/**
* Connects a client to a namespace.
*
* @param {String} name namespace
* @param {String} namespace name
* @api private
*/
Client.prototype.connect = function(name, query){
debug('connecting to namespace %s', name);
var nsp = this.server.nsps[name];
if (!nsp) {
this.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'});
return;
}
if ('/' != name && !this.nsps['/']) {
this.connectBuffer.push(name);
return;
}
Client.prototype.connect = function(name){
var self = this;
var socket = nsp.add(this, query, function(){
self.sockets[socket.id] = socket;
self.nsps[nsp.name] = socket;
debug('connecting to namespace %s', name);
if ('/' == nsp.name && self.connectBuffer.length > 0) {
self.connectBuffer.forEach(self.connect, self);
self.connectBuffer = [];
function connectNamespace() {
var nsp = self.server.of(name);
if ('/' != name && !self.nsps['/']) {
self.connectBuffer.push(name);
return;
}
var socket = nsp.add(self, function(){
self.sockets.push(socket);
self.nsps[nsp.name] = socket;
if ('/' == nsp.name && self.connectBuffer.length > 0) {
self.connectBuffer.forEach(self.connect, self);
self.connectBuffer = [];
}
});
}
if (self.server.nsps[name]) {
// Namespace already created, connect
connectNamespace();
return;
}
self.server.checkNamespace(name, function(allow) {
if (allow) {
connectNamespace();
return
}
self.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'});
});
};
@@ -91,12 +103,12 @@ Client.prototype.connect = function(name, query){
*/
Client.prototype.disconnect = function(){
for (var id in this.sockets) {
if (this.sockets.hasOwnProperty(id)) {
this.sockets[id].disconnect();
}
var socket;
// we don't use a for loop because the length of
// `sockets` changes upon each iteration
while (socket = this.sockets.shift()) {
socket.disconnect();
}
this.sockets = {};
this.close();
};
@@ -107,9 +119,10 @@ Client.prototype.disconnect = function(){
*/
Client.prototype.remove = function(socket){
if (this.sockets.hasOwnProperty(socket.id)) {
var nsp = this.sockets[socket.id].nsp.name;
delete this.sockets[socket.id];
var i = this.sockets.indexOf(socket);
if (~i) {
var nsp = this.sockets[i].nsp.name;
this.sockets.splice(i, 1);
delete this.nsps[nsp];
} else {
debug('ignoring remove for %s', socket.id);
@@ -134,25 +147,26 @@ 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
if(!preEncoded) { // not broadcasting, need to encode
this.encoder.encode(packet, function (encodedPackets) { // encode, then write results to engine
writeToEngine(encodedPackets);
});
@@ -187,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);
}
@@ -203,16 +215,14 @@ 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.sockets.forEach(function(socket){
socket.onerror(err);
});
this.onclose('client error');
};
@@ -230,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

@@ -5,11 +5,11 @@
var http = require('http');
var read = require('fs').readFileSync;
var parse = require('url').parse;
var engine = require('engine.io');
var client = require('socket.io-client');
var clientVersion = require('socket.io-client/package').version;
var Client = require('./client');
var Emitter = require('events').EventEmitter;
var Namespace = require('./namespace');
var Adapter = require('socket.io-adapter');
var debug = require('debug')('socket.io:server');
@@ -25,14 +25,13 @@ 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
*/
@@ -44,6 +43,7 @@ function Server(srv, opts){
}
opts = opts || {};
this.nsps = {};
this.nspValidators = [];
this.path(opts.path || '/socket.io');
this.serveClient(false !== opts.serveClient);
this.adapter(opts.adapter || Adapter);
@@ -55,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) {
@@ -88,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
*/
@@ -96,16 +96,6 @@ Server.prototype.checkRequest = function(req, fn) {
Server.prototype.serveClient = function(v){
if (!arguments.length) return this._serveClient;
this._serveClient = v;
if (v && !clientSource) {
clientSource = read(require.resolve('socket.io-client/dist/socket.io.min.js'), 'utf-8');
try {
clientSourceMap = read(require.resolve('socket.io-client/dist/socket.io.js.map'), 'utf-8');
} catch(err) {
debug('could not load sourcemap file');
}
}
return this;
};
@@ -121,7 +111,7 @@ var oldSettings = {
};
/**
* Backwards compatibility.
* Backwards compatiblity.
*
* @api public
*/
@@ -148,10 +138,57 @@ Server.prototype.set = function(key, val){
return this;
};
/**
* Sets up server middleware to validate incoming namespaces not already created on the server.
*
* @return {Server} self
* @api public
*/
Server.prototype.useNamespaceValidator = function(fn){
this.nspValidators.push(fn);
return this;
};
/**
* Executes the middleware for an incoming namespace not already created on the server.
*
* @param name of incomming namespace
* @param {Function} last fn call in the middleware
* @api private
*/
Server.prototype.checkNamespace = function(name, fn){
var fns = this.nspValidators.slice(0);
if (!fns.length) return fn(false);
var namespaceAllowed = false; // Deny unknown namespaces by default
function run(i){
fns[i](name, function(err, allow){
// upon error, short-circuit
if (err) return fn(false);
// if one piece of middleware explicitly denies namespace, short-circuit
if (allow === false) return fn(false);
namespaceAllowed = namespaceAllowed || allow === true;
// if no middleware left, summon callback
if (!fns[i + 1]) return fn(namespaceAllowed);
// go on to next
run(i + 1);
});
}
run(0);
};
/**
* Sets the client serving path.
*
* @param {String} v pathname
* @param {String} pathname
* @return {Server|String} self when setting or value when getting
* @api public
*/
@@ -165,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
*/
@@ -184,7 +221,7 @@ Server.prototype.adapter = function(v){
/**
* Sets the allowed origins for requests.
*
* @param {String} v origins
* @param {String} origins
* @return {Server|Adapter} self when setting or value when getting
* @api public
*/
@@ -233,7 +270,7 @@ 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);
// initialize engine
debug('creating engine.io instance with opts %j', opts);
@@ -254,21 +291,18 @@ Server.prototype.attach = function(srv, opts){
/**
* Attaches the static file serving.
*
* @param {Function|http.Server} srv http server
* @param {Function|http.Server} http server
* @api private
*/
Server.prototype.attachServe = function(srv){
debug('attaching client serving req handler');
var url = this._path + '/socket.io.js';
var urlMap = this._path + '/socket.io.js.map';
var evs = srv.listeners('request').slice(0);
var self = this;
srv.removeAllListeners('request');
srv.on('request', function(req, res) {
if (0 === req.url.indexOf(urlMap)) {
self.serveMap(req, res);
} else if (0 === req.url.indexOf(url)) {
if (0 === req.url.indexOf(url)) {
self.serve(req, res);
} else {
for (var i = 0; i < evs.length; i++) {
@@ -287,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();
@@ -303,46 +333,15 @@ Server.prototype.serve = function(req, res){
debug('serve client source');
res.setHeader('Content-Type', 'application/javascript');
res.setHeader('ETag', expectedEtag);
res.setHeader('X-SourceMap', 'socket.io.js.map');
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
*/
@@ -356,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
*/
@@ -371,44 +370,38 @@ Server.prototype.onconnection = function(conn){
/**
* Looks up a namespace.
*
* @param {String} name nsp name
* @param {Function} [fn] optional, nsp `connection` ev handler
* @param {String} nsp name
* @param {Function} optional, nsp `connection` ev handler
* @api public
*/
Server.prototype.of = function(name, fn){
if (String(name)[0] !== '/') name = '/' + name;
var nsp = this.nsps[name];
if (!nsp) {
if (!this.nsps[name]) {
debug('initializing namespace %s', name);
nsp = new Namespace(this, name);
var nsp = new Namespace(this, name);
this.nsps[name] = nsp;
}
if (fn) nsp.on('connect', fn);
return nsp;
if (fn) this.nsps[name].on('connect', fn);
return this.nsps[name];
};
/**
* Closes server connection
*
* @param {Function} [fn] optional, called as `fn([err])` on error OR all conns closed
* @api public
*/
Server.prototype.close = function(fn){
for (var id in this.nsps['/'].sockets) {
if (this.nsps['/'].sockets.hasOwnProperty(id)) {
this.nsps['/'].sockets[id].onclose();
}
}
Server.prototype.close = function(){
this.nsps['/'].sockets.forEach(function(socket){
socket.onclose();
});
this.engine.close();
if (this.httpServer) {
this.httpServer.close(fn);
} else {
fn && fn();
if(this.httpServer){
this.httpServer.close();
}
};
@@ -416,23 +409,17 @@ Server.prototype.close = function(fn){
* Expose main namespace (/).
*/
var emitterMethods = Object.keys(Emitter.prototype).filter(function(key){
return typeof Emitter.prototype[key] === 'function';
});
emitterMethods.concat(['to', 'in', 'use', 'send', 'write', 'clients', 'compress']).forEach(function(fn){
['on', 'to', 'in', 'use', 'emit', 'send', 'write', 'clients'].forEach(function(fn){
Server.prototype[fn] = function(){
return this.sockets[fn].apply(this.sockets, arguments);
var nsp = this.sockets[fn];
return nsp.apply(this.sockets, arguments);
};
});
Namespace.flags.forEach(function(flag){
Object.defineProperty(Server.prototype, flag, {
get: function() {
this.sockets.flags = this.sockets.flags || {};
this.sockets.flags[flag] = true;
return this;
}
Server.prototype.__defineGetter__(flag, function(name){
this.flags.push(name);
return this;
});
});

View File

@@ -29,11 +29,7 @@ exports.events = [
* Flags.
*/
exports.flags = [
'json',
'volatile',
'local'
];
exports.flags = ['json'];
/**
* `EventEmitter#emit` reference.
@@ -52,10 +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.acks = {};
this.initAdapter();
}
@@ -70,12 +67,10 @@ Namespace.prototype.__proto__ = Emitter.prototype;
*/
exports.flags.forEach(function(flag){
Object.defineProperty(Namespace.prototype, flag, {
get: function() {
this.flags = this.flags || {};
this.flags[flag] = true;
return this;
}
Namespace.prototype.__defineGetter__(flag, function(){
this.flags = this.flags || {};
this.flags[flag] = true;
return this;
});
});
@@ -107,7 +102,7 @@ Namespace.prototype.use = function(fn){
* Executes the middleware for an incoming client.
*
* @param {Socket} socket that will get added
* @param {Function} fn last fn call in the middleware
* @param {Function} last fn call in the middleware
* @api private
*/
@@ -140,7 +135,7 @@ 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;
@@ -153,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(){
@@ -163,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
@@ -190,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);
}
@@ -254,22 +250,5 @@ Namespace.prototype.write = function(){
Namespace.prototype.clients = function(fn){
this.adapter.clients(this.rooms, fn);
// delete rooms flag for scenario:
// .in('room').clients() (GH-1978)
delete 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 = this.flags || {};
this.flags.compress = compress;
return this;
};

View File

@@ -8,7 +8,6 @@ var parser = require('socket.io-parser');
var url = require('url');
var debug = require('debug')('socket.io:socket');
var hasBin = require('has-binary');
var assign = require('object-assign');
/**
* Module exports.
@@ -26,7 +25,6 @@ exports.events = [
'error',
'connect',
'disconnect',
'disconnecting',
'newListener',
'removeListener'
];
@@ -57,19 +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.handshake = this.buildHandshake();
}
/**
@@ -83,25 +81,21 @@ Socket.prototype.__proto__ = Emitter.prototype;
*/
flags.forEach(function(flag){
Object.defineProperty(Socket.prototype, flag, {
get: function() {
this.flags = this.flags || {};
this.flags[flag] = true;
return this;
}
Socket.prototype.__defineGetter__(flag, function(){
this.flags = this.flags || {};
this.flags[flag] = true;
return this;
});
});
/**
* `request` engine.io shortcut.
* `request` engine.io shorcut.
*
* @api public
*/
Object.defineProperty(Socket.prototype, 'request', {
get: function() {
return this.conn.request;
}
Socket.prototype.__defineGetter__('request', function(){
return this.conn.request;
});
/**
@@ -110,13 +104,7 @@ Object.defineProperty(Socket.prototype, 'request', {
* @api private
*/
Socket.prototype.buildHandshake = function(query){
var self = this;
function buildQuery(){
var requestQuery = url.parse(self.request.url, true).query;
//if socket-specific query exist, replace query strings in requestQuery
return assign({}, query, requestQuery);
}
Socket.prototype.buildHandshake = function(){
return {
headers: this.request.headers,
time: (new Date) + '',
@@ -125,7 +113,7 @@ Socket.prototype.buildHandshake = function(query){
secure: !!this.request.connection.encrypted,
issued: +(new Date),
url: this.request.url,
query: buildQuery()
query: url.parse(this.request.url, true).query || {}
};
};
@@ -144,11 +132,10 @@ Socket.prototype.emit = function(ev){
var packet = {};
packet.type = hasBin(args) ? parser.BINARY_EVENT : parser.EVENT;
packet.data = args;
var flags = this.flags || {};
// access last argument to see if it's an ACK callback
if ('function' == typeof args[args.length - 1]) {
if (this._rooms || flags.broadcast) {
if (this._rooms || (this.flags && this.flags.broadcast)) {
throw new Error('Callbacks are not supported when broadcasting');
}
@@ -157,18 +144,15 @@ Socket.prototype.emit = function(ev){
packet.id = this.nsp.ids++;
}
if (this._rooms || flags.broadcast) {
if (this._rooms || (this.flags && this.flags.broadcast)) {
this.adapter.broadcast(packet, {
except: [this.id],
rooms: this._rooms,
flags: flags
flags: this.flags
});
} else {
// dispatch packet
this.packet(packet, {
volatile: flags.volatile,
compress: flags.compress
});
this.packet(packet);
}
// reset flags
@@ -212,22 +196,21 @@ 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} room
* @param {Function} fn optional, callback
* @param {Function} optional, callback
* @return {Socket} self
* @api private
*/
@@ -235,14 +218,11 @@ Socket.prototype.packet = function(packet, opts){
Socket.prototype.join = function(room, fn){
debug('joining room %s', room);
var self = this;
if (this.rooms.hasOwnProperty(room)) {
fn && fn(null);
return this;
}
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', room);
self.rooms[room] = room;
self.rooms.push(room);
fn && fn(null);
});
return this;
@@ -252,7 +232,7 @@ Socket.prototype.join = function(room, fn){
* Leaves a room.
*
* @param {String} room
* @param {Function} fn optional, callback
* @param {Function} optional, callback
* @return {Socket} self
* @api private
*/
@@ -263,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;
@@ -277,23 +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);
this.packet({ type: parser.CONNECT });
this.nsp.connected[this.id] = this;
};
/**
@@ -347,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
*/
@@ -372,8 +353,6 @@ Socket.prototype.ack = function(id){
type: type,
data: args
});
sent = true;
};
};
@@ -424,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);
@@ -444,7 +422,7 @@ Socket.prototype.onclose = function(reason){
/**
* Produces an `error` packet.
*
* @param {Object} err error object
* @param {Object} error object
* @api private
*/
@@ -455,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
*/
@@ -474,7 +452,7 @@ 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
*/
@@ -484,63 +462,3 @@ Socket.prototype.compress = function(compress){
this.flags.compress = compress;
return this;
};
/**
* Dispatch incoming event to socket listeners.
*
* @param {Array} event that will get emitted
* @api private
*/
Socket.prototype.dispatch = function(event){
debug('dispatching an event %j', event);
var self = this;
this.run(event, function(err){
process.nextTick(function(){
if (err) {
return self.error(err.data || err.message);
}
emit.apply(self, event);
});
});
}
/**
* 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": "1.7.4",
"version": "1.3.2",
"description": "node.js realtime framework server",
"keywords": [
"realtime",
@@ -11,40 +11,27 @@
"socket",
"io"
],
"main": "./lib/index",
"files": [
"lib/"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/socketio/socket.io"
"url": "git://github.com/Automattic/socket.io"
},
"scripts": {
"test": "gulp test"
"test": "mocha --reporter dot --slow 200ms --bail"
},
"dependencies": {
"debug": "2.3.3",
"engine.io": "~1.8.4",
"has-binary": "0.1.7",
"object-assign": "4.1.0",
"socket.io-adapter": "0.5.0",
"socket.io-client": "1.7.4",
"socket.io-parser": "2.3.1"
"engine.io": "automattic/engine.io#ddc64a",
"socket.io-parser": "2.2.3",
"socket.io-client": "automattic/socket.io-client#210d65",
"socket.io-adapter": "automattic/socket.io-adapter#ae79d8",
"has-binary": "0.1.6",
"debug": "2.1.0"
},
"devDependencies": {
"babel-preset-es2015": "6.3.13",
"del": "2.2.0",
"mocha": "1.16.2",
"expect.js": "0.3.1",
"gulp": "3.9.0",
"gulp-babel": "6.1.1",
"gulp-istanbul": "0.10.3",
"gulp-mocha": "2.2.0",
"gulp-task-listing": "1.0.1",
"istanbul": "0.4.1",
"mocha": "2.3.4",
"superagent": "1.6.1",
"supertest": "1.1.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,15 +1,8 @@
var testVersion = process.env.TEST_VERSION;
var http = require('http').Server;
var io;
if (testVersion === 'compat') {
console.log('testing compat version');
io = require('../dist');
} else {
io = require('../lib');
}
var io = require('..');
var fs = require('fs');
var join = require('path').join;
var exec = require('child_process').exec;
var ioc = require('socket.io-client');
var request = require('supertest');
var expect = require('expect.js');
@@ -167,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();
@@ -179,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);
@@ -335,32 +328,6 @@ describe('socket.io', function(){
done();
});
});
it('should allow request if custom function in opts.allowRequest returns true', function(done){
var sockets = io(http().listen(54022), { allowRequest: function (req, callback) {
return callback(null, true);
}, origins: 'http://foo.example:*' });
request.get('http://localhost:54022/socket.io/default/')
.query({ transport: 'polling' })
.end(function (err, res) {
expect(res.status).to.be(200);
done();
});
});
it('should disallow request if custom function in opts.allowRequest returns false', function(done){
var sockets = io(http().listen(54023), { allowRequest: function (req, callback) {
return callback(null, false);
} });
request.get('http://localhost:54023/socket.io/default/')
.set('origin', 'http://foo.example')
.query({ transport: 'polling' })
.end(function (err, res) {
expect(res.status).to.be(400);
done();
});
});
});
describe('close', function(){
@@ -375,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();
});
@@ -399,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();
});
@@ -419,31 +386,12 @@ describe('socket.io', function(){
});
});
describe('graceful close', function(){
function fixture(filename) {
return '"' + process.execPath + '" "' +
join(__dirname, 'fixtures', filename) + '"';
}
it('should stop socket and timers', function(done){
exec(fixture('server-close.js'), done);
});
});
});
describe('namespaces', function(){
var Socket;
if (testVersion === 'compat') {
Socket = require('../dist/socket');
} else {
Socket = require('../lib/socket');
}
var Namespace;
if (testVersion === 'compat') {
Namespace = require('../dist/namespace');
} else {
Namespace = require('../lib/namespace');
}
var Socket = require('../lib/socket');
var Namespace = require('../lib/namespace');
it('should be accessible through .sockets', function(){
var sio = io();
expect(sio.sockets).to.be.a(Namespace);
@@ -457,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){
@@ -644,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);
@@ -680,7 +597,7 @@ describe('socket.io', function(){
});
});
});
it('should not reuse same-namespace connections', function(done){
var srv = http();
var sio = io(srv);
@@ -691,7 +608,7 @@ describe('socket.io', function(){
var clientSocket2 = client(srv);
sio.on('connection', function() {
connections++;
if (connections === 2) {
if(connections === 2) {
done();
}
});
@@ -719,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);
@@ -762,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);
@@ -805,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);
@@ -814,83 +731,74 @@ describe('socket.io', function(){
}
});
it('should not emit volatile event after regular event', function(done) {
var srv = http();
var sio = io(srv);
var counter = 0;
srv.listen(function(){
sio.of('/chat').on('connection', function(s){
// Wait to make sure there are no packets being sent for opening the connection
setTimeout(function() {
sio.of('/chat').emit('ev', 'data');
sio.of('/chat').volatile.emit('ev', 'data');
}, 50);
});
var socket = client(srv, '/chat');
socket.on('ev', function() {
counter++;
});
});
setTimeout(function() {
expect(counter).to.be(1);
done();
}, 500);
});
it('should emit volatile event', function(done) {
var srv = http();
var sio = io(srv);
var counter = 0;
srv.listen(function(){
sio.of('/chat').on('connection', function(s){
// Wait to make sure there are no packets being sent for opening the connection
setTimeout(function() {
sio.of('/chat').volatile.emit('ev', 'data');
}, 100);
});
var socket = client(srv, '/chat');
socket.on('ev', function() {
counter++;
});
});
setTimeout(function() {
expect(counter).to.be(1);
done();
}, 500);
});
it('should enable compression by default', function(done){
it('should allow connections to dynamic namespaces', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
var socket = client(srv, '/chat');
sio.of('/chat').on('connection', function(s){
s.conn.once('packetCreate', function(packet) {
expect(packet.options.compress).to.be(true);
done();
});
sio.of('/chat').emit('woot', 'hi');
var namespace = '/dynamic';
var dynamic = client(srv,namespace);
sio.useNamespaceValidator(function(nsp, next) {
expect(nsp).to.be(namespace);
next(null, true);
});
dynamic.on('error', function(err) {
expect().fail();
});
dynamic.on('connect', function() {
expect(sio.nsps[namespace]).to.be.a(Namespace);
expect(sio.nsps[namespace].sockets.length).to.be(1);
done();
});
});
});
it('should disable compression', function(done){
it('should not allow connections to dynamic namespaces if not supported', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
var socket = client(srv, '/chat');
sio.of('/chat').on('connection', function(s){
s.conn.once('packetCreate', function(packet) {
expect(packet.options.compress).to.be(false);
done();
});
sio.of('/chat').compress(false).emit('woot', 'hi');
var namespace = '/dynamic';
sio.useNamespaceValidator(function(nsp, next) {
expect(nsp).to.be(namespace);
next(null, false);
});
sio.on('connect', function(socket) {
if (socket.nsp.name === namespace) {
expect().fail();
}
});
var dynamic = client(srv,namespace);
dynamic.on('connect', function(){
expect().fail();
});
dynamic.on('error', function(err) {
expect(err).to.be("Invalid namespace");
done();
});
});
});
it('should not allow connections to dynamic namespaces if there is an error', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
var namespace = '/dynamic';
sio.useNamespaceValidator(function(nsp, next) {
expect(nsp).to.be(namespace);
next(new Error(), true);
});
sio.on('connect', function(socket) {
if (socket.nsp.name === namespace) {
expect().fail();
}
});
var dynamic = client(srv,namespace);
dynamic.on('connect', function(){
expect().fail();
});
dynamic.on('error', function(err) {
expect(err).to.be("Invalid namespace");
done();
});
});
});
@@ -1179,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'] });
@@ -1191,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) {
@@ -1230,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'] });
@@ -1242,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) {
@@ -1546,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);
@@ -1556,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);
@@ -1601,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);
@@ -1643,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);
});
});
});
@@ -1656,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');
});
});
});
@@ -1671,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');
});
});
});
@@ -1715,55 +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);
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', function(done){
Object.prototype.foo = 'bar';
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(){
@@ -2028,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();
});
});
@@ -2072,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();
});
});
@@ -2089,12 +1936,7 @@ describe('socket.io', function(){
});
describe('middleware', function(done){
var Socket;
if (testVersion === 'compat') {
Socket = require('../dist/socket');
} else {
Socket = require('../lib/socket');
}
var Socket = require('../lib/socket');
it('should call functions', function(done){
var srv = http();
@@ -2232,97 +2074,4 @@ describe('socket.io', function(){
});
});
});
describe('socket middleware', function(done){
var Socket = require('../lib/socket');
it('should call functions', function(done){
var srv = http();
var sio = io(srv);
var run = 0;
srv.listen(function(){
var socket = client(srv, { multiplex: false });
socket.emit('join', 'woot');
sio.on('connection', function(socket){
socket.use(function(event, next){
expect(event).to.eql(['join', 'woot']);
event.unshift('wrap');
run++;
next();
});
socket.use(function(event, next){
expect(event).to.eql(['wrap', 'join', 'woot']);
run++;
next();
});
socket.on('wrap', function(data1, data2){
expect(data1).to.be('join');
expect(data2).to.be('woot');
expect(run).to.be(2);
done();
});
});
});
});
it('should pass errors', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
var clientSocket = client(srv, { multiplex: false });
clientSocket.emit('join', 'woot');
clientSocket.on('error', function(err){
expect(err).to.be('Authentication error');
done();
});
sio.on('connection', function(socket){
socket.use(function(event, next){
next(new Error('Authentication error'));
});
socket.use(function(event, next){
done(new Error('nope'));
});
socket.on('join', function(){
done(new Error('nope'));
});
});
});
});
it('should pass `data` of error object', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
var clientSocket = client(srv, { multiplex: false });
clientSocket.emit('join', 'woot');
clientSocket.on('error', function(err){
expect(err).to.eql({ a: 'b', c: 3 });
done();
});
sio.on('connection', function(socket){
socket.use(function(event, next){
var err = new Error('Authentication error');
err.data = { a: 'b', c: 3 };
next(err);
});
socket.on('join', function(){
done(new Error('nope'));
});
});
});
});
});
});