From 7026d16711dd1ec3dcd73eef402a272ad63105de Mon Sep 17 00:00:00 2001 From: NathanGRomano Date: Fri, 27 Jun 2014 02:41:11 -0400 Subject: [PATCH 01/16] added support for an err in the onRoute method --- lib/router.js | 5 +++-- spec/lib/router-spec.coffee | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/router.js b/lib/router.js index e3a7adc..9f61f28 100644 --- a/lib/router.js +++ b/lib/router.js @@ -63,7 +63,7 @@ function Router () { args.push(this.ack(packet.id)); } - router.onRoute(this, args); + router.onRoute(null, this, args); }; @@ -71,11 +71,12 @@ function Router () { * Pushes the socket and arguments through the middleware * * @api private + * @param {Error} err * @param {Socket} socket * @param {Array} args */ - router.onRoute = function (socket, args) { + router.onRoute = function (err, socket, args) { var done = function (emit, args) { debug('done callled'); diff --git a/spec/lib/router-spec.coffee b/spec/lib/router-spec.coffee index ec10fbc..1c5de19 100644 --- a/spec/lib/router-spec.coffee +++ b/spec/lib/router-spec.coffee @@ -35,7 +35,7 @@ describe 'Router', -> Given -> @packet = id:1, data: ['message', 'hello'] When -> @router.onEvent.call @socket, @packet Then -> expect(@socket.ack).toHaveBeenCalledWith @packet.id - And -> expect(@router.onRoute).toHaveBeenCalledWith @socket, ['message', 'hello', @fn] + And -> expect(@router.onRoute).toHaveBeenCalledWith null, @socket, ['message', 'hello', @fn] describe '#onRoute', -> @@ -58,7 +58,7 @@ describe 'Router', -> Given -> spyOn(@router,['getPath']).andCallThrough() Given -> spyOn(@router,['decorate']).andCallThrough() Given -> @args = ['message', 'hello', @fn] - When -> @router.onRoute @socket, @args + When -> @router.onRoute null, @socket, @args Then -> expect(@router.getPath).toHaveBeenCalledWith @args[0] And -> expect(@router.decorate).toHaveBeenCalledWith @socket, jasmine.any(Function) And -> expect(@a).toHaveBeenCalled() From 97b8ec9d81dc63f2b9af803be5dc2dd7fec93063 Mon Sep 17 00:00:00 2001 From: NathanGRomano Date: Fri, 27 Jun 2014 02:41:40 -0400 Subject: [PATCH 02/16] passed the error to the step method when given to the onRoute method --- lib/router.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/router.js b/lib/router.js index 9f61f28..a812939 100644 --- a/lib/router.js +++ b/lib/router.js @@ -128,7 +128,7 @@ function Router () { next(e); } - })(); + })(err); }; From c734893b49ae14d8f7cde83e81d24ebed22fd79d Mon Sep 17 00:00:00 2001 From: NathanGRomano Date: Fri, 27 Jun 2014 02:58:04 -0400 Subject: [PATCH 03/16] added the use method, which simplifies the old 'on' method --- README.md | 2 ++ lib/router.js | 52 +++++++++++++++++++++++---------------------------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 5298919..84962c8 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ io.use(router); # Examples +The method `on` is an alias to `use`. + ```javascript var assert = require('assert'); var router = require('socket.io-events')(); diff --git a/lib/router.js b/lib/router.js index a812939..aef655b 100644 --- a/lib/router.js +++ b/lib/router.js @@ -1,5 +1,7 @@ var debug = require('debug')('router'); +var util = require('util'); var emit = require('events').EventEmitter.prototype.emit; +var slice = Array.prototype.slice; module.exports = Router; @@ -154,7 +156,7 @@ Router.prototype.decorate = function (socket, done) { if (emit.wrapped) return socket; socket.emit = function () { - done(emit, Array.prototype.slice.call(arguments)); + done(emit, slice.call(arguments)); }; socket.emit.wrapped = true; @@ -198,45 +200,37 @@ Router.prototype.getPath = function (name) { }; /** - * Used to bind functions + * Use this method to attach handlers, and other routers * * @api public - * @param {mixed} Either a string, function, or array of functions + * @param {mixed} Either a string and one of either a [function, or array of functions, or a Router] * @return Router */ -Router.prototype.on = function () { +Router.prototype.use = function () { if (!arguments.length) throw new Error('expecting at least one parameter'); - + var args = slice.call(arguments); + var name = typeof args[0] === 'string' ? args.shift() : '*'; + var fns = this.fns(name); var self = this; - var args = Array.prototype.slice.call(arguments); - var first = args.shift(); - var type = typeof first; - var name = '*'; - var fn; - - if (type === 'string') { - name = first; - if (!args.length) throw new Error('need a function'); - } - else if (type === 'function') { - fn = first; - this.fns(name).push([this.index(), first]); - } - else if (type === 'object' && first instanceof Array) { - this.on.apply(this, first); - } - debug('on %s %s', name, args.length ); - args.forEach(function (item) { - if (typeof item !== 'function') return; - var length = item.length; - debug('fn length %s', length); - self.fns(name).push([self.index(), item]); + args.forEach(function (arg) { + if (util.isArray(arg)) return self.use.apply(self, arg); + if (typeof arg !== 'function') return; + self.fns(name).push([self.index(), arg]); }); - return this; }; +/** + * An alias to the `use` method. + * + * @api public + * @param {mixed} Either a string, function, or array of functions + * @return Router + */ + +Router.prototype.on = Router.prototype.use; + /** * Gets the functions given the name. Name will default to '*' * From 9841c08cf6b4788b44776a2906e239dc491b35ee Mon Sep 17 00:00:00 2001 From: NathanGRomano Date: Fri, 27 Jun 2014 02:58:31 -0400 Subject: [PATCH 04/16] modified to not call fns() again, and again --- lib/router.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/router.js b/lib/router.js index aef655b..380553b 100644 --- a/lib/router.js +++ b/lib/router.js @@ -216,7 +216,7 @@ Router.prototype.use = function () { args.forEach(function (arg) { if (util.isArray(arg)) return self.use.apply(self, arg); if (typeof arg !== 'function') return; - self.fns(name).push([self.index(), arg]); + fns.push([self.index(), arg]); }); return this; }; From 5de2911c62211d83ebede5180fbffd73aec91c3a Mon Sep 17 00:00:00 2001 From: NathanGRomano Date: Fri, 27 Jun 2014 02:59:12 -0400 Subject: [PATCH 05/16] replaced the push call with an index into the array --- lib/router.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/router.js b/lib/router.js index 380553b..ede753b 100644 --- a/lib/router.js +++ b/lib/router.js @@ -216,7 +216,7 @@ Router.prototype.use = function () { args.forEach(function (arg) { if (util.isArray(arg)) return self.use.apply(self, arg); if (typeof arg !== 'function') return; - fns.push([self.index(), arg]); + fns[fns.length] = [self.index(), arg]; }); return this; }; From ba9795ea975e303828d4fcf8c93d80a937f531e4 Mon Sep 17 00:00:00 2001 From: NathanGRomano Date: Fri, 27 Jun 2014 02:59:58 -0400 Subject: [PATCH 06/16] removed the for each --- lib/router.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/router.js b/lib/router.js index ede753b..ef142df 100644 --- a/lib/router.js +++ b/lib/router.js @@ -213,11 +213,13 @@ Router.prototype.use = function () { var name = typeof args[0] === 'string' ? args.shift() : '*'; var fns = this.fns(name); var self = this; - args.forEach(function (arg) { + var i, arg; + for (i=0; i Date: Fri, 27 Jun 2014 03:04:55 -0400 Subject: [PATCH 07/16] modified the test for "on" to be "use" --- lib/router.js | 1 + spec/lib/router-spec.coffee | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/router.js b/lib/router.js index ef142df..b8e065e 100644 --- a/lib/router.js +++ b/lib/router.js @@ -211,6 +211,7 @@ Router.prototype.use = function () { if (!arguments.length) throw new Error('expecting at least one parameter'); var args = slice.call(arguments); var name = typeof args[0] === 'string' ? args.shift() : '*'; + if (!args.length) throw new Error('we have the name, but need a handler'); var fns = this.fns(name); var self = this; var i, arg; diff --git a/spec/lib/router-spec.coffee b/spec/lib/router-spec.coffee index 1c5de19..245e431 100644 --- a/spec/lib/router-spec.coffee +++ b/spec/lib/router-spec.coffee @@ -54,7 +54,7 @@ describe 'Router', -> Given -> @err1 = (err, socket, args, next) => @e err; @order.push('e'); next() Given -> @cra = (socket, args, next) => @f(); @order.push('f'); next() Given -> @path = [@foo, @bar, @err, @baz, @err1, @cra] - Given -> @router.on @path + Given -> @router.use @path Given -> spyOn(@router,['getPath']).andCallThrough() Given -> spyOn(@router,['decorate']).andCallThrough() Given -> @args = ['message', 'hello', @fn] @@ -79,26 +79,31 @@ describe 'Router', -> @socket.emit 'hello' Then -> expect(@old.emit.apply).toHaveBeenCalledWith @socket, ['hello'] - describe '#on (fn:Function)', -> + describe '#use', -> + + Given -> @test = => @router.use() + Then -> expect(@test).toThrow new Error 'expecting at least one parameter' + + describe '#use (fn:Function)', -> Given -> @fn = (socket, args, next) -> - When -> @router.on @fn + When -> @router.use @fn Then -> expect(@router.fns().length).toBe 1 - describe '#on (name:String,fn:Function)', -> + describe '#use (name:String,fn:Function)', -> Given -> @name = 'name' Given -> @fn = (socket, args, next) -> - When -> @router.on @name, @fn + When -> @router.use @name, @fn Then -> expect(@router.fns(@name).length).toBe 1 - describe '#on (name:Array)', -> + describe '#use (name:Array)', -> Given -> @a = -> Given -> @b = -> Given -> @c = -> Given -> @name = [@a, @b, @c] - When -> @router.on @name + When -> @router.use @name Then -> expect(@router.fns()).toEqual [[0,@a], [1,@b], [2,@c]] describe '#getPath (name:String)', -> @@ -106,9 +111,9 @@ describe 'Router', -> Given -> @a = -> Given -> @b = -> Given -> @c = -> - Given -> @router.on @a - Given -> @router.on 'event', @b - Given -> @router.on '*', @c + Given -> @router.use @a + Given -> @router.use 'event', @b + Given -> @router.use '*', @c When -> @path = @router.getPath ['event'] Then -> expect(@path).toEqual [@a, @b, @c] From 15721bc48d988ec6353ef6e9ecb6c41f2ca01b51 Mon Sep 17 00:00:00 2001 From: NathanGRomano Date: Fri, 27 Jun 2014 03:05:39 -0400 Subject: [PATCH 08/16] wrote a test for just passing in a name --- spec/lib/router-spec.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/lib/router-spec.coffee b/spec/lib/router-spec.coffee index 245e431..13c9b50 100644 --- a/spec/lib/router-spec.coffee +++ b/spec/lib/router-spec.coffee @@ -84,6 +84,11 @@ describe 'Router', -> Given -> @test = => @router.use() Then -> expect(@test).toThrow new Error 'expecting at least one parameter' + describe '#use (name:Sring)', -> + + Given -> @test = => @router.use 'name' + Then -> expect(@test).toThrow new Error 'we have the name, but need a handler' + describe '#use (fn:Function)', -> Given -> @fn = (socket, args, next) -> From 6224a8ba30a1ddf509a83af7337be7014e232449 Mon Sep 17 00:00:00 2001 From: NathanGRomano Date: Fri, 27 Jun 2014 03:07:47 -0400 Subject: [PATCH 09/16] wrote a test for the alias "on" to "use" --- spec/lib/router-spec.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/lib/router-spec.coffee b/spec/lib/router-spec.coffee index 13c9b50..f8a6f6b 100644 --- a/spec/lib/router-spec.coffee +++ b/spec/lib/router-spec.coffee @@ -86,7 +86,7 @@ describe 'Router', -> describe '#use (name:Sring)', -> - Given -> @test = => @router.use 'name' + Given -> @test = => @router.use 'some event' Then -> expect(@test).toThrow new Error 'we have the name, but need a handler' describe '#use (fn:Function)', -> @@ -111,6 +111,10 @@ describe 'Router', -> When -> @router.use @name Then -> expect(@router.fns()).toEqual [[0,@a], [1,@b], [2,@c]] + describe '#on', -> + + Then -> expect(@router.on).toEqual @router.use + describe '#getPath (name:String)', -> Given -> @a = -> From afe7287bcf7fbac6466504acf26c9e443bcf22e0 Mon Sep 17 00:00:00 2001 From: NathanGRomano Date: Fri, 27 Jun 2014 03:10:40 -0400 Subject: [PATCH 10/16] wrote a test for passing a router into the "use" method --- lib/router.js | 2 +- spec/lib/router-spec.coffee | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/router.js b/lib/router.js index b8e065e..13eac68 100644 --- a/lib/router.js +++ b/lib/router.js @@ -219,7 +219,7 @@ Router.prototype.use = function () { arg = args[i]; if (util.isArray(arg)) return self.use.apply(self, arg); if (typeof arg !== 'function') return; - fns[fns.length] = [self.index(), arg]; + fns[fns.length] = [self.index(), (arg instanceof Router ? (arg.onRoute) : (arg))]; } return this; }; diff --git a/spec/lib/router-spec.coffee b/spec/lib/router-spec.coffee index f8a6f6b..891a928 100644 --- a/spec/lib/router-spec.coffee +++ b/spec/lib/router-spec.coffee @@ -95,6 +95,13 @@ describe 'Router', -> When -> @router.use @fn Then -> expect(@router.fns().length).toBe 1 + describe '#use (router:Router)', -> + + Given -> @a = @Router() + When -> @router.use @a + Then -> expect(@router.fns().length).toBe 1 + And -> expect(@router.fns()[0][1]).toEqual @a.onRoute + describe '#use (name:String,fn:Function)', -> Given -> @name = 'name' From adc94d87feb6f1587e42c359d9348c0d5edc4a8c Mon Sep 17 00:00:00 2001 From: NathanGRomano Date: Fri, 27 Jun 2014 03:35:46 -0400 Subject: [PATCH 11/16] added new example and documentation, however test is not passing --- README.md | 18 ++++++++++++++++++ examples/multiple.js | 27 +++++++++++++++++++++++++++ examples/run.sh | 2 ++ lib/router.js | 24 ++++++++++++++++++++---- 4 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 examples/multiple.js diff --git a/README.md b/README.md index 84962c8..f8a7c72 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ io.use(router); * Easy to use interface for manipulating socket.io events. * Express-like routing capabilties for socket.io events. * Gives you more control over how events are handled. +* Attach `Router` instances to other `Router` instances. # Examples @@ -113,6 +114,23 @@ router.on(function (socket, args, next) { io.use(router); ``` +You can even attach a `Router' intance to another `Router` intance. + +```javascript +var Router = require('socket.io-events')(); + +var a = Router(); +a.use(function (sock, args, next) { next() }); + +var b = Router(); +b.use(function (sock, args, next) { next() }); + +a.use(b) + +var io = require('socket.io')(3000); +io.use(a); +``` + # Installation and Environment Setup Install node.js (See download and install instructions here: http://nodejs.org/). diff --git a/examples/multiple.js b/examples/multiple.js new file mode 100644 index 0000000..c5e12e8 --- /dev/null +++ b/examples/multiple.js @@ -0,0 +1,27 @@ +var ok = require('assert').equal; +var Router = require('./..'); + +var a = Router(); +a.on('say', function (sock, args, next) { args.push('World'); next() }); + +var b = Router(); +b.use(function (sock, args, next) { sock.emit('done', args.pop(), args.pop()); }); + +a.use(b) + +var io = require('socket.io')(3000); +io.use(a); + +setTimeout(function () { + var sock = require('socket.io-client').connect('ws://localhost:3000'); + sock.on('connect', function () { + sock.emit('say', 'Hello'); + }); + sock.on('done', function (hello, world) { + ok(hello, 'Hello'); + ok(world, 'World'); + console.log('we good'); + process.exit(0); + }); + +}, 1000); diff --git a/examples/run.sh b/examples/run.sh index 03685c7..371bd93 100755 --- a/examples/run.sh +++ b/examples/run.sh @@ -5,3 +5,5 @@ echo 'testing bau' node bau echo 'testing recover' node recover +echo 'testing routers attached to routers' +node multiple diff --git a/lib/router.js b/lib/router.js index 13eac68..93919de 100644 --- a/lib/router.js +++ b/lib/router.js @@ -16,6 +16,8 @@ function Router () { if (!(this instanceof Router)) return new Router(); function router (socket, cb) { + console.trace('calling router?'); + debug('calling router socket? %s, cb %s', typeof socket, typeof cb); router.middleware(socket, cb); } @@ -42,7 +44,7 @@ function Router () { */ router.middleware = function (socket, cb) { - debug('middleware'); + debug('middleware socket? %s, cb? %s', typeof socket, typeof cb); if ('function' != typeof socket.onevent || socket.onevent !== router.onEvent) { socket.onevent = router.onEvent; } @@ -73,20 +75,32 @@ function Router () { * Pushes the socket and arguments through the middleware * * @api private - * @param {Error} err + * @param {Error} err *optional * @param {Socket} socket * @param {Array} args */ router.onRoute = function (err, socket, args) { + // just checking if our first parameter is actually an Error or not + if (err != null && !(err instanceof Error)) { + var tmp = socket; + socket = args; + err = tmp; + } + + // LOOKS LIKE ERR ARE THE ARGS, SOCKET, AND ARGS ARE THE NEXT FUNCTION + + debug('onRoute %s, %s, %s', typeof err, typeof socket, typeof args); + debug('onRoute', err, socket, args); + var done = function (emit, args) { debug('done callled'); emit.apply(socket, args); }; - var path = this.getPath(args.length ? args[0] : '*'); - socket = this.decorate(socket, done); + var path = router.getPath(args.length ? args[0] : '*'); + socket = router.decorate(socket, done); var i = 0, len = path.length; @@ -133,6 +147,8 @@ function Router () { })(err); }; + + debug('router instanceof Router? %s', router instanceof Router); return router; From 13c51d8090ff7c151e77717860a3a0bf51a739f7 Mon Sep 17 00:00:00 2001 From: "Nathan G. Romano" Date: Fri, 27 Jun 2014 14:21:17 -0400 Subject: [PATCH 12/16] added support for combining routers --- lib/router.js | 54 +++++++++++--------------- spec/lib/router-spec.coffee | 76 +++++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 35 deletions(-) diff --git a/lib/router.js b/lib/router.js index 93919de..4831eef 100644 --- a/lib/router.js +++ b/lib/router.js @@ -16,7 +16,6 @@ function Router () { if (!(this instanceof Router)) return new Router(); function router (socket, cb) { - console.trace('calling router?'); debug('calling router socket? %s, cb %s', typeof socket, typeof cb); router.middleware(socket, cb); } @@ -78,31 +77,17 @@ function Router () { * @param {Error} err *optional * @param {Socket} socket * @param {Array} args + * @param {Function} cb */ - router.onRoute = function (err, socket, args) { - - // just checking if our first parameter is actually an Error or not - if (err != null && !(err instanceof Error)) { - var tmp = socket; - socket = args; - err = tmp; - } - - // LOOKS LIKE ERR ARE THE ARGS, SOCKET, AND ARGS ARE THE NEXT FUNCTION + router.onRoute = function (err, socket, args, cb) { debug('onRoute %s, %s, %s', typeof err, typeof socket, typeof args); debug('onRoute', err, socket, args); - var done = function (emit, args) { - debug('done callled'); - emit.apply(socket, args); - }; + socket = router.decorate(socket, function (emit, args) { debug('done callled'); emit.apply(socket, args); }); - var path = router.getPath(args.length ? args[0] : '*'); - socket = router.decorate(socket, done); - - var i = 0, len = path.length; + var i = 0, path = router.getPath(args.length ? args[0] : '*'), len = path.length; (function step (err) { @@ -111,7 +96,11 @@ function Router () { function next (err) { if (++i >= len) { debug('last step'); - if (err) return emit.apply(socket, ['error', args]); + if (err) { + if ('function' === typeof cb) return cb(err, socket, args); + return emit.apply(socket, ['error', args]); + } + if ('function' === typeof cb) return cb(null, socket, args); return emit.apply(socket, args); } step(err); @@ -122,7 +111,12 @@ function Router () { try { if (err) { if (fn.length >= 4) { - fn(err, socket, args, next); + if (fn instanceof Router) { + fn.onRoute(err, socket, args, next); + } + else { + fn(err, socket, args, next); + } } else { next(err); @@ -130,7 +124,12 @@ function Router () { } else { if (fn.length >= 4) { - next(); + if (fn instanceof Router) { + fn.onRoute(null, socket, args, next); + } + else { + next(); + } } else { fn(socket, args, next); @@ -152,7 +151,7 @@ function Router () { return router; -} +}; /** @@ -202,13 +201,6 @@ Router.prototype.getPath = function (name) { debug('sort index a %s, b %s = %s', a, b, res); return res; }) - /* - .sort(function (a, b) { - var res = (a[1].length >= 4 ? 1 : 0) - (b[1].length >= 4 ? 1 : 0); - debug('sort index a %s, b %s = %s', a, b, res); - return res; - }) - */ .forEach(function (point) { fns.push(point[1]); }); @@ -235,7 +227,7 @@ Router.prototype.use = function () { arg = args[i]; if (util.isArray(arg)) return self.use.apply(self, arg); if (typeof arg !== 'function') return; - fns[fns.length] = [self.index(), (arg instanceof Router ? (arg.onRoute) : (arg))]; + fns[fns.length] = [self.index(), arg]; } return this; }; diff --git a/spec/lib/router-spec.coffee b/spec/lib/router-spec.coffee index 891a928..cd36eda 100644 --- a/spec/lib/router-spec.coffee +++ b/spec/lib/router-spec.coffee @@ -37,7 +37,7 @@ describe 'Router', -> Then -> expect(@socket.ack).toHaveBeenCalledWith @packet.id And -> expect(@router.onRoute).toHaveBeenCalledWith null, @socket, ['message', 'hello', @fn] - describe '#onRoute', -> + describe '#onRoute (err:Error=null, sock:Object, args:Array)', -> Given -> @order = [] Given -> @a = jasmine.createSpy 'a' @@ -49,7 +49,7 @@ describe 'Router', -> Given -> @error = new Error 'something wrong' Given -> @foo = (socket, args, next) => @a(); @order.push('a'); next() Given -> @bar = (socket, args, next) => @b(); @order.push('b'); next() - Given -> @baz = (socket, args, next) => @c(); @order.push('c'); next @error + Given -> @baz = (socket, args, next) => @c(); @order.push('c'); next @error Given -> @err = (err, socket, args, next) => @d err; @order.push('d'); next err Given -> @err1 = (err, socket, args, next) => @e err; @order.push('e'); next() Given -> @cra = (socket, args, next) => @f(); @order.push('f'); next() @@ -58,7 +58,7 @@ describe 'Router', -> Given -> spyOn(@router,['getPath']).andCallThrough() Given -> spyOn(@router,['decorate']).andCallThrough() Given -> @args = ['message', 'hello', @fn] - When -> @router.onRoute null, @socket, @args + When -> @router.onRoute null, @socket, @args, null Then -> expect(@router.getPath).toHaveBeenCalledWith @args[0] And -> expect(@router.decorate).toHaveBeenCalledWith @socket, jasmine.any(Function) And -> expect(@a).toHaveBeenCalled() @@ -69,6 +69,74 @@ describe 'Router', -> And -> expect(@f).toHaveBeenCalled() And -> expect(@order).toEqual ['a', 'b', 'c', 'e', 'f'] + describe '#onRoute (err:Error=null, sock:Object, args:Array, cb:Function)', -> + + Given -> @order = [] + Given -> @cb = jasmine.createSpy 'cb' + Given -> @a = jasmine.createSpy 'a' + Given -> @b = jasmine.createSpy 'b' + Given -> @c = jasmine.createSpy 'c' + Given -> @d = jasmine.createSpy 'd' + Given -> @e = jasmine.createSpy 'e' + Given -> @f = jasmine.createSpy 'f' + Given -> @error = new Error 'something wrong' + Given -> @foo = (socket, args, next) => @a(); @order.push('a'); next() + Given -> @bar = (socket, args, next) => @b(); @order.push('b'); next() + Given -> @baz = (socket, args, next) => @c(); @order.push('c'); next @error + Given -> @err = (err, socket, args, next) => @d err; @order.push('d'); next err + Given -> @err1 = (err, socket, args, next) => @e err; @order.push('e'); next() + Given -> @cra = (socket, args, next) => @f(); @order.push('f'); next() + Given -> @path = [@foo, @bar, @err, @baz, @err1, @cra] + Given -> @router.use @path + Given -> spyOn(@router,['getPath']).andCallThrough() + Given -> spyOn(@router,['decorate']).andCallThrough() + Given -> @args = ['message', 'hello', @fn] + When -> @router.onRoute null, @socket, @args, @cb + Then -> expect(@router.getPath).toHaveBeenCalledWith @args[0] + And -> expect(@router.decorate).toHaveBeenCalledWith @socket, jasmine.any(Function) + And -> expect(@a).toHaveBeenCalled() + And -> expect(@b).toHaveBeenCalled() + And -> expect(@c).toHaveBeenCalled() + And -> expect(@d).not.toHaveBeenCalled() + And -> expect(@e).toHaveBeenCalledWith @error + And -> expect(@f).toHaveBeenCalled() + And -> expect(@cb).toHaveBeenCalled() + And -> expect(@order).toEqual ['a', 'b', 'c', 'e', 'f'] + + describe '#onRoute (err:Error, sock:Object, args:Array)', -> + + Given -> @order = [] + Given -> @cb = jasmine.createSpy 'cb' + Given -> @a = jasmine.createSpy 'a' + Given -> @b = jasmine.createSpy 'b' + Given -> @c = jasmine.createSpy 'c' + Given -> @d = jasmine.createSpy 'd' + Given -> @e = jasmine.createSpy 'e' + Given -> @f = jasmine.createSpy 'f' + Given -> @error = new Error 'something wrong' + Given -> @foo = (socket, args, next) => @a(); @order.push('a'); next() + Given -> @bar = (socket, args, next) => @b(); @order.push('b'); next() + Given -> @baz = (socket, args, next) => @c(); @order.push('c'); next @error + Given -> @err = (err, socket, args, next) => @d err; @order.push('d'); next err + Given -> @err1 = (err, socket, args, next) => @e err; @order.push('e'); next() + Given -> @cra = (socket, args, next) => @f(); @order.push('f'); next() + Given -> @path = [@foo, @bar, @err, @baz, @err1, @cra] + Given -> @router.use @path + Given -> spyOn(@router,['getPath']).andCallThrough() + Given -> spyOn(@router,['decorate']).andCallThrough() + Given -> @args = ['message', 'hello', @fn] + When -> @router.onRoute @error, @socket, @args, @cb + Then -> expect(@router.getPath).toHaveBeenCalledWith @args[0] + And -> expect(@router.decorate).toHaveBeenCalledWith @socket, jasmine.any(Function) + And -> expect(@a).not.toHaveBeenCalled() + And -> expect(@b).not.toHaveBeenCalled() + And -> expect(@c).not.toHaveBeenCalled() + And -> expect(@d).toHaveBeenCalled() + And -> expect(@e).toHaveBeenCalledWith @error + And -> expect(@f).toHaveBeenCalled() + And -> expect(@cb).toHaveBeenCalled() + And -> expect(@order).toEqual ['d', 'e', 'f'] + describe '#decorate', -> Given -> @old = emit: @socket.emit @@ -100,7 +168,7 @@ describe 'Router', -> Given -> @a = @Router() When -> @router.use @a Then -> expect(@router.fns().length).toBe 1 - And -> expect(@router.fns()[0][1]).toEqual @a.onRoute + And -> expect(@router.fns()[0][1]).toEqual @a describe '#use (name:String,fn:Function)', -> From b175af3ad40d6fb67db087a3758214d1787660b0 Mon Sep 17 00:00:00 2001 From: "Nathan G. Romano" Date: Fri, 27 Jun 2014 17:39:10 -0400 Subject: [PATCH 13/16] lots of debug and got the two routers to connect and work with eachother --- lib/router.js | 82 +++++++++++++---- spec-e2e/routing-spec.coffee | 171 ++++++++++++++++++++++------------- 2 files changed, 172 insertions(+), 81 deletions(-) diff --git a/lib/router.js b/lib/router.js index 4831eef..34fe3ed 100644 --- a/lib/router.js +++ b/lib/router.js @@ -16,7 +16,7 @@ function Router () { if (!(this instanceof Router)) return new Router(); function router (socket, cb) { - debug('calling router socket? %s, cb %s', typeof socket, typeof cb); + debug('router socket.id %s typeof cb %s', socket ? socket.id : null, typeof cb); router.middleware(socket, cb); } @@ -43,7 +43,7 @@ function Router () { */ router.middleware = function (socket, cb) { - debug('middleware socket? %s, cb? %s', typeof socket, typeof cb); + debug('middleware socket.id %s typeof cb %s', socket.id, typeof cb); if ('function' != typeof socket.onevent || socket.onevent !== router.onEvent) { socket.onevent = router.onEvent; } @@ -82,57 +82,99 @@ function Router () { router.onRoute = function (err, socket, args, cb) { - debug('onRoute %s, %s, %s', typeof err, typeof socket, typeof args); - debug('onRoute', err, socket, args); + debug('onRoute err? %s socket.id %s args?', util.isError(err), (socket ? socket.id : null), args); socket = router.decorate(socket, function (emit, args) { debug('done callled'); emit.apply(socket, args); }); var i = 0, path = router.getPath(args.length ? args[0] : '*'), len = path.length; + debug('got path the length is %s', len); + + debug('about to call "step" for the first time passing %s for err', err); + (function step (err) { - debug('current step %s of %s', i, len); + debug('current step %s of %s', i+1, len); function next (err) { + debug('next called err? %s', util.isError(err)); if (++i >= len) { debug('last step'); if (err) { - if ('function' === typeof cb) return cb(err, socket, args); - return emit.apply(socket, ['error', args]); + debug('has err'); + if ('function' === typeof cb) { + debug('cb is function'); + cb(err, socket, args); + } + else { + debug('default which is to call emit on the socket'); + emit.apply(socket, ['error', args]); + } + } + else { + debug('no err'); + if ('function' === typeof cb) { + debug('cb is function'); + cb(null, socket, args); + } + else { + debug('default which is to call emit on the socket'); + emit.apply(socket, args); + } } - if ('function' === typeof cb) return cb(null, socket, args); - return emit.apply(socket, args); } - step(err); + else { + debug('calling step'); + step(err); + } } var fn = path[i]; + debug('typeof fn? %s', typeof fn); + try { if (err) { + debug('we have an error'); if (fn.length >= 4) { + debug('the fn.length is >= 4'); if (fn instanceof Router) { + debug('we have a router instance'); fn.onRoute(err, socket, args, next); } else { + debug('we have a regular function'); fn(err, socket, args, next); } } else { + debug('fn is not an erorr handler call next %s', typeof next); next(err); } } else { + debug('we do not have an error'); + debug('fn %s', fn); if (fn.length >= 4) { + debug('the fn.length is >= 4'); if (fn instanceof Router) { + debug('we have a router instance'); fn.onRoute(null, socket, args, next); } else { + debug('we have a regular function call next %s', typeof next); next(); } } else { - fn(socket, args, next); + if (fn instanceof Router) { + debug('we have a router instance'); + fn.onRoute(null, socket, args, next); + } + else { + debug('fn is normal invoke it socket.id %s args %s typeof next %s', (socket ? socket.id : null), args, typeof next); + fn(socket, args, next); + } } } } @@ -164,8 +206,10 @@ function Router () { */ Router.prototype.decorate = function (socket, done) { - debug('decorate'); + debug('decorate socket.id %s typeof done %s', socket.id, typeof done); var emit = socket.emit; + + debug('is the "emit" on socket.id %s wrapped? %s', socket.id, (emit.wrapped ? true : false)); // prevent double wrapping if (emit.wrapped) return socket; @@ -174,6 +218,7 @@ Router.prototype.decorate = function (socket, done) { done(emit, slice.call(arguments)); }; + debug('"emit" on socket.id %s is now wrapped', socket.id); socket.emit.wrapped = true; return socket; @@ -197,9 +242,7 @@ Router.prototype.getPath = function (name) { var fns = []; points .sort(function (a, b) { - var res = a[0] - b[0]; - debug('sort index a %s, b %s = %s', a, b, res); - return res; + return a[0] - b[0]; }) .forEach(function (point) { fns.push(point[1]); @@ -218,15 +261,20 @@ Router.prototype.getPath = function (name) { Router.prototype.use = function () { if (!arguments.length) throw new Error('expecting at least one parameter'); var args = slice.call(arguments); + debug('use called %s', args); var name = typeof args[0] === 'string' ? args.shift() : '*'; + debug('the name %s', name); if (!args.length) throw new Error('we have the name, but need a handler'); var fns = this.fns(name); var self = this; - var i, arg; + var i, arg, type; for (i=0; i - - Given -> @foo = jasmine.createSpy 'foo' - Given -> @bar = jasmine.createSpy 'bar' - Given -> @baz = jasmine.createSpy 'baz' - Given -> @no = jasmine.createSpy 'no' - Given -> - @router = require('./..')() - @router.on (socket, args, next) => - @foo() - next() - @router.on 'some event', (socket, args, next) => - @bar() - next() - @router.on '*', (socket, args, next) => - @baz() - socket.emit.apply(socket, args) - @router.on 'some event', (socket, args, next) => - @no() - next() - - Given -> - @io = require('socket.io')(3000) - @io.use @router.middleware - - Given -> @message = 'Hello, World' - - When (done) -> - @socket = require('socket.io-client').connect('ws://localhost:3000') - @socket.on 'connect', => - @socket.emit 'some event', @message - @socket.on 'some event', (message) => - @res = message - done() - - Then -> expect(@res).toEqual @message - And -> expect(@baz).toHaveBeenCalled() - And -> expect(@bar).toHaveBeenCalled() - And -> expect(@foo).toHaveBeenCalled() - And -> expect(@no).not.toHaveBeenCalled() - -describe 'when a router receives some event and it is not handle business as usual', -> - - Given -> - @router = require('./..')() - @router.on (socket, args, next) -> next() - - Given -> - @io = require('socket.io')(3001) - @io.use @router.middleware - @io.on 'connect', (socket) -> - socket.on 'echo', (data) -> - socket.emit 'echo', data - - Given -> @message = 'Hello, World' - - When (done) -> - @socket = require('socket.io-client').connect('ws://localhost:3001') - @socket.on 'connect', => - @socket.emit 'echo', @message - @socket.on 'echo', (message) => - @res = message - done() - Then -> expect(@res).toEqual @message +debug = require('debug')('router') + +describe 'routing events', -> + + describe 'when a router receives some event it should route it to a handler', -> + + Given -> @foo = jasmine.createSpy 'foo' + Given -> @bar = jasmine.createSpy 'bar' + Given -> @baz = jasmine.createSpy 'baz' + Given -> @no = jasmine.createSpy 'no' + Given -> + @router = require('./..')() + @router.on (socket, args, next) => + @foo() + next() + @router.on 'some event', (socket, args, next) => + @bar() + next() + @router.on '*', (socket, args, next) => + @baz() + socket.emit.apply(socket, args) + @router.on 'some event', (socket, args, next) => + @no() + next() + + Given -> + @io = require('socket.io')(3000) + @io.use @router.middleware + + Given -> @message = 'Hello, World' + + When (done) -> + @socket = require('socket.io-client').connect('ws://localhost:3000') + @socket.on 'connect', => + @socket.emit 'some event', @message + @socket.on 'some event', (message) => + @res = message + done() + + Then -> expect(@res).toEqual @message + And -> expect(@baz).toHaveBeenCalled() + And -> expect(@bar).toHaveBeenCalled() + And -> expect(@foo).toHaveBeenCalled() + And -> expect(@no).not.toHaveBeenCalled() + + describe 'when a router receives some event and it is not handle business as usual', -> + + Given -> + @router = require('./..')() + @router.on (socket, args, next) -> next() + + Given -> + @io = require('socket.io')(3001) + @io.use @router.middleware + @io.on 'connect', (socket) -> + socket.on 'echo', (data) -> + socket.emit 'echo', data + + Given -> @message = 'Hello, World' + + When (done) -> + @socket = require('socket.io-client').connect('ws://localhost:3001') + @socket.on 'connect', => + @socket.emit 'echo', @message + @socket.on 'echo', (message) => + @res = message + done() + Then -> expect(@res).toEqual @message + + describe 'two routers connected to each other', -> + + Given -> @hit = 0 + + Given -> + @a = require('./..')() + @a.on (socket, args, next) => + debug('handler "a" socket.id %s args %s typeof next', socket.id, args, typeof next) + @hit++ + next() + + Given -> + @b = require('./..')() + @b.on (socket, args, next) => + debug('handler "b" socket.id %s args %s typeof next', socket.id, args, typeof next) + @hit++ + next() + + Given -> @a.use @b + + Given -> + @io = require('socket.io')(3002) + @io.use @a + @io.on 'connect', (socket) -> + socket.on 'echo', (data) -> + socket.emit 'echo', data + + Given -> @message = 'Hello, World' + + When (done) -> + @socket = require('socket.io-client').connect('ws://localhost:3002') + @socket.on 'connect', => + @socket.emit 'echo', @message + @socket.on 'echo', (message) => + @res = message + done() + Then -> expect(@res).toEqual @message + And -> expect(@hit).toBe 2 From 698714239cb23058aa8e753043a0aaf86c4c7d01 Mon Sep 17 00:00:00 2001 From: "Nathan G. Romano" Date: Fri, 27 Jun 2014 17:50:20 -0400 Subject: [PATCH 14/16] modified example to demonstrate the use of chaining multiple routers together --- examples/multiple.js | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/examples/multiple.js b/examples/multiple.js index c5e12e8..3dc13ba 100644 --- a/examples/multiple.js +++ b/examples/multiple.js @@ -2,24 +2,52 @@ var ok = require('assert').equal; var Router = require('./..'); var a = Router(); -a.on('say', function (sock, args, next) { args.push('World'); next() }); +a.on('say', function (sock, args, next) { + args.push('World'); + next(); +}); var b = Router(); -b.use(function (sock, args, next) { sock.emit('done', args.pop(), args.pop()); }); +b.use('say', function (sock, args, next) { + args.push('Good'); + next(); +}); + +var c = Router(); +c.use('say', function (sock, args, next) { + args.push('Bye'); + next(); +}); + +var d = Router(); +d.use(function (sock, args, next) { + args.push('!!!'); + next(); +}); a.use(b) +b.use(c); +c.use(d); var io = require('socket.io')(3000); io.use(a); +io.on('connection', function (sock) { + sock.on('say', function (hello, world, good, bye, exclamation) { + sock.emit('say', hello, world, good, bye, exclamation); + }); +}); setTimeout(function () { var sock = require('socket.io-client').connect('ws://localhost:3000'); sock.on('connect', function () { sock.emit('say', 'Hello'); }); - sock.on('done', function (hello, world) { + sock.on('say', function (hello, world, good, bye, exclamation) { ok(hello, 'Hello'); ok(world, 'World'); + ok(good, 'Good'); + ok(bye, 'Bye'); + ok(exclamation, '!!!'); console.log('we good'); process.exit(0); }); From bf38a048d09c5c86928df3ce0308befa50a62e48 Mon Sep 17 00:00:00 2001 From: "Nathan G. Romano" Date: Fri, 27 Jun 2014 17:51:00 -0400 Subject: [PATCH 15/16] 0.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b73d63d..d7fd690 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socket.io-events", - "version": "0.2.1", + "version": "0.3.0", "description": "Power your socket.io apps with express like event routing.", "main": "index.js", "scripts": { From 34eb696a5cd6863227f5331109844e7778c5f8e7 Mon Sep 17 00:00:00 2001 From: "Nathan G. Romano" Date: Fri, 27 Jun 2014 18:17:06 -0400 Subject: [PATCH 16/16] updatd readme --- README.md | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/README.md b/README.md index f8a7c72..f27e8da 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,189 @@ var io = require('socket.io')(3000); io.use(a); ``` +# API + +## Router + +Get the `Router` class. + +```javascript +var Router = require('socket.io-events'); +``` + +The `use` and `on` methods are equivalent. They also can be chained. + +```javascript +var router = Router() + .use(function (sock, args, next) { }) + .use(function (sock, args, next) { }) + .use(function (sock, args, next) { }); +``` + +### Router#() + +Make a `Router` instance + +```javascript +var router = Router(); +``` + +### Router#use(fn:Function, ...) + +Attach a `function` to the router. + +```javascript +router.use(function (sock, args, next) { + //do something! + next(); +}); +``` + +You can pass in multiple `function`s. + +```javascript +var a = function (sock, args, next) { next() }; +var b = function (sock, args, next) { next() }; +var c = function (sock, args, next) { next() }; + +router.use(a,b,c); +``` + +You can pass in a function that accepts an `Error` object. + +```javascript +router.use(function (err, sock, args, next) { + console.error(err); + + //calling next(err) will invoke the next error handler. + //to resume operation just call next() + next(err); +}); +``` + +### Router#use(event:String, fn:Function, ...) + +Bind the `function` to the `event`. + +```javascript +router.use('chat', function (sock, args, next) { + assert.equal(args[0], 'chat'); + args[1] = args[1].length > 128 ? args[1].slice(0, 125) + '...' : args[1]; + next(); +}); +``` + +You can also pass in multiple `function`s for handling the `event`. + +```javascript +var chop = function (sock, args, next) { next() }; +var clean = function (sock, args, next) { next() }; +var pretty = function (sock, args, next) { next() }; + +router.use('chat', chop, clean, pretty); +``` + +### Router#use(router:Router, ...) + +You can attach another `Router` instance to your `Router` instance. + +```javascript +var another = Router(); +another.use(function (sock, args, next) { next(); }); + +router.use(another); +``` + +Attach multiple routers in a single call. + +```javascript +var foo = Router(); +foo.use(function (sock, args, next) { next(); }); + +var bar = Router(); +bar.use(function (sock, args, next) { next(); }); + +var baz = Router(); +baz.use(function (sock, args, next) { next(); }); + +router.use(foo, bar, baz); +``` + +### Router#use(name:String, router:Router, ...) + +Just like attaching a `function` to the router given the `event`. You can attach `Router` +instance as well to the `event`. + +```javascript +var foo = Router(); +foo.use(function (sock, args, next) { next(); }); + +router.use('some event', foo); +``` + +Attach multiple routers in a single call to the `event` too. + +```javascript +var foo = Router(); +foo.use(function (sock, args, next) { next(); }); + +var bar = Router(); +bar.use(function (sock, args, next) { next(); }); + +var baz = Router(); +baz.use(function (sock, args, next) { next(); }); + +router.use('some event', foo, bar, baz); +``` + +### Router#use(fns:Array, ...) + +Attach an `Array` of `Fuction`'s or `Router` instances, or an `Array` or `Array`s . + +```javascript +var middleware = [ + function (sock, args, next) { next(); }, + [ + function (sock, args, next) { next(); }, + Router().use(function (sock, args, next) { next(); }), + function (sock, args, next) { next(); }, + ], + Router().use(function (sock, args, next) { next(); }) +]; + +var errHandler = function (err, sock, args, next) { next(err); } + +router.use(middleware, errHandler); +``` + +### Router#use(name:String, fns:Array, ...) + +Attach everything to an event. + +```javascript +var middleware = [ + function (sock, args, next) { next(); }, + [ + function (sock, args, next) { next(); }, + Router().use(function (sock, args, next) { next(); }), + function (sock, args, next) { next(); }, + ], + Router().use(function (sock, args, next) { next(); }) +]; + +var errHandler = function (err, sock, args, next) { next(err); } + +router.use('only this event', middleware, errHandler); +``` + +### Router#on(...) + +This is an alias to to the `use` method. It does the same thing. + +```javascript +router.on(function (sock, args, next) { next() }); +``` + # Installation and Environment Setup Install node.js (See download and install instructions here: http://nodejs.org/).