Compare commits

...

14 Commits
2.0.4 ... 2.1.0

Author SHA1 Message Date
Damien Arrachequesne
db831a3de4 [chore] Release 2.1.0 2018-03-29 23:30:03 +02:00
Damien Arrachequesne
ac945d1eba [feat] Add support for dynamic namespaces (#3195)
This follows #3187, with a slightly different API.

A dynamic namespace can be created with:

```js
io.of(/^\/dynamic-\d+$/).on('connect', (socket) => { /* ... */ });
```
2018-03-29 23:08:08 +02:00
Scott Gress
ad0c052eff [docs] Add note in docs for origins(fn) about error needing to be a string. (#2895) 2018-03-10 09:03:28 +01:00
Damien Arrachequesne
1f1d64bab6 [fix] Include the protocol in the origins check (#3198)
Previously, the protocol was not taken in account, which caused the following behaviour:

```js
io.origins('https://foo.example.com:443'); // ok as a string
io.origins(['https://foo.example.com:443'); // not ok as an array
```

Fixes #3190
2018-03-10 08:56:42 +01:00
Damien Arrachequesne
f4fc517e0f [fix] Properly emit 'connect' when using a custom namespace (#3197)
When using a custom namespace with a middleware, the client did not receive the 'connect' event.

Fixes #3082
2018-03-10 08:51:22 +01:00
Jumper Chen
be61ba0a20 [docs] Add link to a Dart client implementation (#2940) 2018-03-01 00:23:45 +01:00
Damien Arrachequesne
c0c79f019e [feat] Add support for dynamic namespaces (#3187) 2018-03-01 00:22:16 +01:00
Damien Arrachequesne
dea5214f21 [chore] Bump superagent and supertest versions (#3186) 2018-02-28 23:19:19 +01:00
Damien Arrachequesne
b1941d5dfe [chore] Bump engine.io to version 3.2.0 2018-02-28 23:10:40 +01:00
Miguel Piedrafita
a23007a635 [docs] Update license year (#3153) 2018-02-28 23:03:02 +01:00
Damien Arrachequesne
f48a06c040 [feat] Add a 'binary' flag (#3185)
So that the call to the `has-binary` method can be skipped. Usage:

```
// with binary data
socket.binary(true).emit("binary", obj);

// without binary data
socket.binary(false).emit("string", obj);

// call to hasBin
socket.emit("guess", obj);
```
2018-02-28 23:00:16 +01:00
Damien Arrachequesne
0539a2c4fd [test] Update travis configuration 2018-02-28 22:56:28 +01:00
Devlin Pajaron
c06ac071d0 [docs] Fix typo (#3157) 2018-02-25 09:26:24 +01:00
Damien Arrachequesne
52b09609db [chore] Bump debug to version 3.1.0 2018-02-25 09:22:40 +01:00
12 changed files with 316 additions and 36 deletions

View File

@@ -1,12 +1,14 @@
sudo: false
language: node_js
sudo: false
node_js:
- "4"
- "6"
- "7"
git:
depth: 1
- '4'
- '6'
- '8'
- node
notifications:
irc: "irc.freenode.org#socket.io"
git:
depth: 1
cache:
directories:
- node_modules

View File

@@ -1,6 +1,6 @@
(The MIT License)
Copyright (c) 2014-2017 Automattic <dev@cloudup.com>
Copyright (c) 2014-2018 Automattic <dev@cloudup.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@@ -21,6 +21,7 @@ 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:
@@ -38,7 +39,7 @@ Unless instructed otherwise a disconnected client will try to reconnect forever,
#### Disconnection detection
An heartbeat mechanism is implemented at the Engine.IO level, allowing both the server and the client to know when the other one is not responding anymore.
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.

View File

@@ -33,6 +33,7 @@
- [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)
@@ -57,6 +58,7 @@
- [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)
@@ -222,13 +224,13 @@ io.adapter(redis({ host: 'localhost', port: 6379 }));
#### server.origins([value])
- `value` _(String)_
- `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(['foo.example.com:443']);
io.origins(['https://foo.example.com:443']);
```
#### server.origins(fn)
@@ -236,7 +238,7 @@ io.origins(['foo.example.com:443']);
- `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.
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 `*`
@@ -290,7 +292,7 @@ Advanced use only. Creates a new `socket.io` client from the incoming engine.io
#### server.of(nsp)
- `nsp` _(String)_
- `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.
@@ -299,6 +301,34 @@ Initializes and retrieves the given `Namespace` by its pathname identifier `nsp`
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)_
@@ -470,6 +500,14 @@ Sets a modifier for a subsequent event emission that the event data may be lost
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).
@@ -769,6 +807,17 @@ io.on('connection', (socket) => {
});
```
#### 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)

View File

@@ -40,9 +40,12 @@ function onConnect(socket){
// 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');

View File

@@ -56,17 +56,38 @@ Client.prototype.setup = function(){
* Connects a client to a namespace.
*
* @param {String} name namespace
* @param {Object} query the query parameters
* @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 (this.server.nsps[name]) {
debug('connecting to namespace %s', name);
return this.doConnect(name, query);
}
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);
return;

View File

@@ -1,3 +1,4 @@
'use strict';
/**
* Module dependencies.
@@ -12,6 +13,7 @@ var clientVersion = require('socket.io-client/package.json').version;
var Client = require('./client');
var Emitter = require('events').EventEmitter;
var Namespace = require('./namespace');
var ParentNamespace = require('./parent-namespace');
var Adapter = require('socket.io-adapter');
var parser = require('socket.io-parser');
var debug = require('debug')('socket.io:server');
@@ -46,6 +48,7 @@ function Server(srv, opts){
}
opts = opts || {};
this.nsps = {};
this.parentNsps = new Map();
this.path(opts.path || '/socket.io');
this.serveClient(false !== opts.serveClient);
this.parser = opts.parser || parser;
@@ -79,9 +82,11 @@ 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) {
}
@@ -157,6 +162,37 @@ Server.prototype.set = function(key, val){
return this;
};
/**
* Executes the middleware for an incoming namespace not already created on the server.
*
* @param {String} name name of incoming namespace
* @param {Object} query the query parameters
* @param {Function} fn callback
* @api private
*/
Server.prototype.checkNamespace = function(name, query, fn){
if (this.parentNsps.size === 0) return fn(false);
const keysIterator = this.parentNsps.keys();
const run = () => {
let nextFn = keysIterator.next();
if (nextFn.done) {
return fn(false);
}
nextFn.value(name, query, (err, allow) => {
if (err || !allow) {
run();
} else {
fn(this.parentNsps.get(nextFn.value).createChild(name));
}
});
};
run();
};
/**
* Sets the client serving path.
*
@@ -193,7 +229,7 @@ Server.prototype.adapter = function(v){
/**
* Sets the allowed origins for requests.
*
* @param {String} v origins
* @param {String|String[]} v origins
* @return {Server|Adapter} self when setting or value when getting
* @api public
*/
@@ -402,12 +438,24 @@ Server.prototype.onconnection = function(conn){
/**
* Looks up a namespace.
*
* @param {String} name nsp name
* @param {String|RegExp|Function} name nsp name
* @param {Function} [fn] optional, nsp `connection` ev handler
* @api public
*/
Server.prototype.of = function(name, fn){
if (typeof name === 'function' || name instanceof RegExp) {
const parentNsp = new ParentNamespace(this);
debug('initializing parent namespace %s', parentNsp.name);
if (typeof name === 'function') {
this.parentNsps.set(name, parentNsp);
} else {
this.parentNsps.set((nsp, conn, next) => next(null, name.test(nsp)), parentNsp);
}
if (fn) parentNsp.on('connect', fn);
return parentNsp;
}
if (String(name)[0] !== '/') name = '/' + name;
var nsp = this.nsps[name];
@@ -451,7 +499,7 @@ 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){
emitterMethods.concat(['to', 'in', 'use', 'send', 'write', 'clients', 'compress', 'binary']).forEach(function(fn){
Server.prototype[fn] = function(){
return this.sockets[fn].apply(this.sockets, arguments);
};

View File

@@ -6,6 +6,7 @@
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');
/**
@@ -99,7 +100,7 @@ Namespace.prototype.initAdapter = function(){
*/
Namespace.prototype.use = function(fn){
if (this.server.eio) {
if (this.server.eio && this.name === '/') {
debug('removing initial packet');
delete this.server.eio.initialPacket;
}
@@ -214,7 +215,10 @@ Namespace.prototype.emit = function(ev){
}
// set up packet object
var args = Array.prototype.slice.call(arguments);
var packet = { type: parser.EVENT, data: args };
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');
@@ -277,3 +281,16 @@ Namespace.prototype.compress = function(compress){
this.flags.compress = compress;
return this;
};
/**
* Sets the binary flag
*
* @param {Boolean} Encode as if it has binary data if `true`, Encode as if it doesnt have binary data if `false`
* @return {Socket} self
* @api public
*/
Namespace.prototype.binary = function (binary) {
this.flags.binary = binary;
return this;
};

39
lib/parent-namespace.js Normal file
View File

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

View File

@@ -5,6 +5,7 @@
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');
@@ -143,7 +144,7 @@ Socket.prototype.emit = function(ev){
var args = Array.prototype.slice.call(arguments);
var packet = {
type: parser.EVENT,
type: (this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT : parser.EVENT,
data: args
};
@@ -380,7 +381,7 @@ Socket.prototype.ack = function(id){
self.packet({
id: id,
type: parser.ACK,
type: hasBin(args) ? parser.BINARY_ACK : parser.ACK,
data: args
});
@@ -495,6 +496,19 @@ Socket.prototype.compress = function(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.
*

View File

@@ -1,6 +1,6 @@
{
"name": "socket.io",
"version": "2.0.4",
"version": "2.1.0",
"description": "node.js realtime framework server",
"keywords": [
"realtime",
@@ -24,18 +24,19 @@
"test": "nyc mocha --reporter spec --slow 200 --bail --timeout 10000 test/socket.io.js"
},
"dependencies": {
"debug": "~2.6.6",
"engine.io": "~3.1.0",
"debug": "~3.1.0",
"engine.io": "~3.2.0",
"has-binary2": "~1.0.2",
"socket.io-adapter": "~1.1.0",
"socket.io-client": "2.0.4",
"socket.io-parser": "~3.1.1"
"socket.io-client": "2.1.0",
"socket.io-parser": "~3.2.0"
},
"devDependencies": {
"expect.js": "0.3.1",
"mocha": "^3.5.3",
"nyc": "^11.2.1",
"superagent": "1.6.1",
"supertest": "1.1.0"
"superagent": "^3.8.2",
"supertest": "^3.0.0"
},
"contributors": [
{

View File

@@ -1,3 +1,5 @@
'use strict';
var http = require('http').Server;
var io = require('../lib');
var fs = require('fs');
@@ -354,6 +356,17 @@ describe('socket.io', function(){
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(){
@@ -878,6 +891,61 @@ describe('socket.io', function(){
});
});
});
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) {
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();
});
});
});
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 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();
});
});
});
});
});
describe('socket', function(){
@@ -1673,7 +1741,7 @@ describe('socket.io', function(){
var socket = client(srv, { reconnection: false });
sio.on('connection', function(s){
s.conn.on('upgrade', function(){
console.log('\033[96mNote: warning expected and normal in test.\033[39m');
console.log('\u001b[96mNote: warning expected and normal in test.\u001b[39m');
socket.io.engine.write('5woooot');
setTimeout(function(){
done();
@@ -1690,7 +1758,7 @@ describe('socket.io', function(){
var socket = client(srv, { reconnection: false });
sio.on('connection', function(s){
s.conn.on('upgrade', function(){
console.log('\033[96mNote: warning expected and normal in test.\033[39m');
console.log('\u001b[96mNote: warning expected and normal in test.\u001b[39m');
socket.io.engine.write('44["handle me please"]');
setTimeout(function(){
done();
@@ -2277,6 +2345,23 @@ describe('socket.io', 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){