Skip to content

Commit

Permalink
[feat] Add support for dynamic namespaces (socketio#3187)
Browse files Browse the repository at this point in the history
  • Loading branch information
darrachequesne committed Feb 28, 2018
1 parent dea5214 commit c0c79f0
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 5 deletions.
17 changes: 17 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- [server.onconnection(socket)](#serveronconnectionsocket)
- [server.of(nsp)](#serverofnsp)
- [server.close([callback])](#serverclosecallback)
- [server.useNamespaceValidator(fn)](#serverusenamespacevalidatorfn)
- [Class: Namespace](#namespace)
- [namespace.name](#namespacename)
- [namespace.connected](#namespaceconnected)
Expand Down Expand Up @@ -321,6 +322,22 @@ server.listen(PORT); // PORT is free to use
io = Server(server);
```

#### server.useNamespaceValidator(fn)

- `fn` _(Function)_

Sets up server middleware to validate whether a new namespace should be created.

```js
io.useNamespaceValidator((nsp, next) => {
if (nsp === 'dynamic') {
next(null, true);
} else {
next(new Error('Invalid namespace'));
}
});
```

#### server.engine.generateId

Overwrites the default method to generate your custom socket id.
Expand Down
31 changes: 26 additions & 5 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,38 @@ Client.prototype.setup = function(){
* Connects a client to a namespace.
*
* @param {String} name namespace
* @param {String} 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, (allow) => {
if (allow) {
debug('creating namespace %s', 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;
Expand Down
48 changes: 48 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function Server(srv, opts){
}
opts = opts || {};
this.nsps = {};
this.nspValidators = [];
this.path(opts.path || '/socket.io');
this.serveClient(false !== opts.serveClient);
this.parser = opts.parser || parser;
Expand Down Expand Up @@ -157,6 +158,53 @@ 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.
*
Expand Down
75 changes: 75 additions & 0 deletions test/socket.io.js
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,81 @@ describe('socket.io', function(){
});
});
});

describe('dynamic', function () {
it('should allow connections to dynamic namespaces', function(done){
var srv = http();
var sio = io(srv);
srv.listen(function(){
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(Object.keys(sio.nsps[namespace].sockets).length).to.be(1);
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 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();
});
});
});
});
});

describe('socket', function(){
Expand Down

0 comments on commit c0c79f0

Please sign in to comment.