Skip to content

Commit c0c79f0

Browse files
authoredFeb 28, 2018
[feat] Add support for dynamic namespaces (#3187)
1 parent dea5214 commit c0c79f0

File tree

4 files changed

+166
-5
lines changed

4 files changed

+166
-5
lines changed
 

‎docs/API.md

+17
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- [server.onconnection(socket)](#serveronconnectionsocket)
2121
- [server.of(nsp)](#serverofnsp)
2222
- [server.close([callback])](#serverclosecallback)
23+
- [server.useNamespaceValidator(fn)](#serverusenamespacevalidatorfn)
2324
- [Class: Namespace](#namespace)
2425
- [namespace.name](#namespacename)
2526
- [namespace.connected](#namespaceconnected)
@@ -321,6 +322,22 @@ server.listen(PORT); // PORT is free to use
321322
io = Server(server);
322323
```
323324

325+
#### server.useNamespaceValidator(fn)
326+
327+
- `fn` _(Function)_
328+
329+
Sets up server middleware to validate whether a new namespace should be created.
330+
331+
```js
332+
io.useNamespaceValidator((nsp, next) => {
333+
if (nsp === 'dynamic') {
334+
next(null, true);
335+
} else {
336+
next(new Error('Invalid namespace'));
337+
}
338+
});
339+
```
340+
324341
#### server.engine.generateId
325342

326343
Overwrites the default method to generate your custom socket id.

‎lib/client.js

+26-5
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,38 @@ Client.prototype.setup = function(){
5656
* Connects a client to a namespace.
5757
*
5858
* @param {String} name namespace
59+
* @param {String} query the query parameters
5960
* @api private
6061
*/
6162

6263
Client.prototype.connect = function(name, query){
63-
debug('connecting to namespace %s', name);
64-
var nsp = this.server.nsps[name];
65-
if (!nsp) {
66-
this.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'});
67-
return;
64+
if (this.server.nsps[name]) {
65+
debug('connecting to namespace %s', name);
66+
return this.doConnect(name, query);
6867
}
6968

69+
this.server.checkNamespace(name, (allow) => {
70+
if (allow) {
71+
debug('creating namespace %s', name);
72+
this.doConnect(name, query);
73+
} else {
74+
debug('creation of namespace %s was denied', name);
75+
this.packet({ type: parser.ERROR, nsp: name, data: 'Invalid namespace' });
76+
}
77+
});
78+
};
79+
80+
/**
81+
* Connects a client to a namespace.
82+
*
83+
* @param {String} name namespace
84+
* @param {String} query the query parameters
85+
* @api private
86+
*/
87+
88+
Client.prototype.doConnect = function(name, query){
89+
var nsp = this.server.of(name);
90+
7091
if ('/' != name && !this.nsps['/']) {
7192
this.connectBuffer.push(name);
7293
return;

‎lib/index.js

+48
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ function Server(srv, opts){
4646
}
4747
opts = opts || {};
4848
this.nsps = {};
49+
this.nspValidators = [];
4950
this.path(opts.path || '/socket.io');
5051
this.serveClient(false !== opts.serveClient);
5152
this.parser = opts.parser || parser;
@@ -157,6 +158,53 @@ Server.prototype.set = function(key, val){
157158
return this;
158159
};
159160

161+
/**
162+
* Sets up server middleware to validate incoming namespaces not already created on the server.
163+
*
164+
* @return {Server} self
165+
* @api public
166+
*/
167+
168+
Server.prototype.useNamespaceValidator = function(fn){
169+
this.nspValidators.push(fn);
170+
return this;
171+
};
172+
173+
/**
174+
* Executes the middleware for an incoming namespace not already created on the server.
175+
*
176+
* @param name of incomming namespace
177+
* @param {Function} last fn call in the middleware
178+
* @api private
179+
*/
180+
181+
Server.prototype.checkNamespace = function(name, fn){
182+
var fns = this.nspValidators.slice(0);
183+
if (!fns.length) return fn(false);
184+
185+
var namespaceAllowed = false; // Deny unknown namespaces by default
186+
187+
function run(i){
188+
fns[i](name, function(err, allow){
189+
// upon error, short-circuit
190+
if (err) return fn(false);
191+
192+
// if one piece of middleware explicitly denies namespace, short-circuit
193+
if (allow === false) return fn(false);
194+
195+
namespaceAllowed = namespaceAllowed || allow === true;
196+
197+
// if no middleware left, summon callback
198+
if (!fns[i + 1]) return fn(namespaceAllowed);
199+
200+
// go on to next
201+
run(i + 1);
202+
});
203+
}
204+
205+
run(0);
206+
};
207+
160208
/**
161209
* Sets the client serving path.
162210
*

‎test/socket.io.js

+75
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,81 @@ describe('socket.io', function(){
878878
});
879879
});
880880
});
881+
882+
describe('dynamic', function () {
883+
it('should allow connections to dynamic namespaces', function(done){
884+
var srv = http();
885+
var sio = io(srv);
886+
srv.listen(function(){
887+
var namespace = '/dynamic';
888+
var dynamic = client(srv, namespace);
889+
sio.useNamespaceValidator(function(nsp, next) {
890+
expect(nsp).to.be(namespace);
891+
next(null, true);
892+
});
893+
dynamic.on('error', function(err) {
894+
expect().fail();
895+
});
896+
dynamic.on('connect', function() {
897+
expect(sio.nsps[namespace]).to.be.a(Namespace);
898+
expect(Object.keys(sio.nsps[namespace].sockets).length).to.be(1);
899+
done();
900+
});
901+
});
902+
});
903+
904+
it('should not allow connections to dynamic namespaces if not supported', function(done){
905+
var srv = http();
906+
var sio = io(srv);
907+
srv.listen(function(){
908+
var namespace = '/dynamic';
909+
sio.useNamespaceValidator(function(nsp, next) {
910+
expect(nsp).to.be(namespace);
911+
next(null, false);
912+
});
913+
sio.on('connect', function(socket) {
914+
if (socket.nsp.name === namespace) {
915+
expect().fail();
916+
}
917+
});
918+
919+
var dynamic = client(srv,namespace);
920+
dynamic.on('connect', function(){
921+
expect().fail();
922+
});
923+
dynamic.on('error', function(err) {
924+
expect(err).to.be("Invalid namespace");
925+
done();
926+
});
927+
});
928+
});
929+
930+
it('should not allow connections to dynamic namespaces if there is an error', function(done){
931+
var srv = http();
932+
var sio = io(srv);
933+
srv.listen(function(){
934+
var namespace = '/dynamic';
935+
sio.useNamespaceValidator(function(nsp, next) {
936+
expect(nsp).to.be(namespace);
937+
next(new Error(), true);
938+
});
939+
sio.on('connect', function(socket) {
940+
if (socket.nsp.name === namespace) {
941+
expect().fail();
942+
}
943+
});
944+
945+
var dynamic = client(srv,namespace);
946+
dynamic.on('connect', function(){
947+
expect().fail();
948+
});
949+
dynamic.on('error', function(err) {
950+
expect(err).to.be("Invalid namespace");
951+
done();
952+
});
953+
});
954+
});
955+
});
881956
});
882957

883958
describe('socket', function(){

0 commit comments

Comments
 (0)
Please sign in to comment.