Skip to content

Commit 0c16257

Browse files
feat: add support for dynamic namespaces (WIP)
1 parent e241fd0 commit 0c16257

File tree

4 files changed

+136
-6
lines changed

4 files changed

+136
-6
lines changed

lib/client.js

Lines changed: 26 additions & 5 deletions
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 {Object} 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, query, (dynamicNsp) => {
70+
if (dynamicNsp) {
71+
debug('dynamic namespace %s was created', dynamicNsp.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

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ var clientVersion = require('socket.io-client/package.json').version;
1212
var Client = require('./client');
1313
var Emitter = require('events').EventEmitter;
1414
var Namespace = require('./namespace');
15+
var ParentNamespace = require('./parent-namespace');
1516
var Adapter = require('socket.io-adapter');
1617
var parser = require('socket.io-parser');
1718
var debug = require('debug')('socket.io:server');
@@ -46,6 +47,7 @@ function Server(srv, opts){
4647
}
4748
opts = opts || {};
4849
this.nsps = {};
50+
this.parentNsps = new Map();
4951
this.path(opts.path || '/socket.io');
5052
this.serveClient(false !== opts.serveClient);
5153
this.parser = opts.parser || parser;
@@ -159,6 +161,37 @@ Server.prototype.set = function(key, val){
159161
return this;
160162
};
161163

164+
/**
165+
* Executes the middleware for an incoming namespace not already created on the server.
166+
*
167+
* @param {String} name name of incoming namespace
168+
* @param {Object} query the query parameters
169+
* @param {Function} fn callback
170+
* @api private
171+
*/
172+
173+
Server.prototype.checkNamespace = function(name, query, fn){
174+
if (this.parentNsps.size === 0) return fn(false);
175+
176+
const keysIterator = this.parentNsps.keys();
177+
178+
const run = () => {
179+
let nextFn = keysIterator.next();
180+
if (nextFn.done) {
181+
return fn(false);
182+
}
183+
nextFn.value(name, query, (err, allow) => {
184+
if (err || !allow) {
185+
run();
186+
} else {
187+
fn(this.parentNsps.get(nextFn.value).createChild(name));
188+
}
189+
});
190+
};
191+
192+
run();
193+
};
194+
162195
/**
163196
* Sets the client serving path.
164197
*
@@ -404,12 +437,24 @@ Server.prototype.onconnection = function(conn){
404437
/**
405438
* Looks up a namespace.
406439
*
407-
* @param {String} name nsp name
440+
* @param {String|RegExp|Function} name nsp name
408441
* @param {Function} [fn] optional, nsp `connection` ev handler
409442
* @api public
410443
*/
411444

412445
Server.prototype.of = function(name, fn){
446+
if (typeof name === 'function' || name instanceof RegExp) {
447+
const parentNsp = new ParentNamespace(this);
448+
debug('initializing parent namespace %s', parentNsp.name);
449+
if (typeof name === 'function') {
450+
this.parentNsps.set(name, parentNsp);
451+
} else {
452+
this.parentNsps.set((nsp, conn, next) => next(null, name.test(nsp)), parentNsp);
453+
}
454+
if (fn) parentNsp.on('connect', fn);
455+
return parentNsp;
456+
}
457+
413458
if (String(name)[0] !== '/') name = '/' + name;
414459

415460
var nsp = this.nsps[name];

lib/parent-namespace.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const Namespace = require('./namespace');
2+
3+
let count = 0;
4+
5+
class ParentNamespace extends Namespace {
6+
7+
constructor(server) {
8+
super(server, '/_' + (count++));
9+
this.children = new Set();
10+
}
11+
12+
initAdapter() {}
13+
14+
emit(...args) {
15+
this.children.forEach(nsp => {
16+
nsp.rooms = this.rooms;
17+
nsp.flags = this.flags;
18+
nsp.emit.apply(nsp, args);
19+
});
20+
this.rooms = [];
21+
this.flags = {};
22+
}
23+
24+
createChild(name) {
25+
const namespace = new Namespace(this.server, name);
26+
namespace.fns = this.fns.slice(0);
27+
this.listeners('connect').forEach(listener => namespace.on('connect', listener));
28+
this.listeners('connection').forEach(listener => namespace.on('connection', listener));
29+
this.children.add(namespace);
30+
this.server.nsps[name] = namespace;
31+
return namespace;
32+
}
33+
}
34+
35+
module.exports = ParentNamespace;

test/socket.io.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,35 @@ describe('socket.io', function(){
889889
});
890890
});
891891
});
892+
893+
describe('dynamic namespaces', function () {
894+
it('should allow connections to dynamic namespaces with regex', function(done){
895+
const srv = http();
896+
const sio = io(srv);
897+
let count = 0;
898+
srv.listen(function(){
899+
const socket = client(srv, '/dynamic-101');
900+
let dynamicNsp = sio.of(/^\/dynamic-\d+$/).on('connect', (socket) => {
901+
expect(socket.nsp.name).to.be('/dynamic-101');
902+
dynamicNsp.emit('hello', 1, '2', { 3 : '4'});
903+
if (++count === 4) done();
904+
}).use((socket, next) => {
905+
next();
906+
if (++count === 4) done();
907+
});
908+
socket.on('error', function(err) {
909+
expect().fail();
910+
});
911+
socket.on('connect', () => {
912+
if (++count === 4) done();
913+
});
914+
socket.on('hello', (...args) => {
915+
expect(args).to.eql([ 1, '2', { 3 : '4'} ]);
916+
if (++count === 4) done();
917+
});
918+
});
919+
});
920+
});
892921
});
893922

894923
describe('socket', function(){

0 commit comments

Comments
 (0)