├── .gitignore ├── index.js ├── .npmignore ├── spec ├── helpers │ └── sandboxed-module.coffee └── lib │ └── router-spec.coffee ├── .travis.yml ├── examples ├── run.sh ├── bau.js ├── sibilings.js ├── recover.js ├── router.js ├── consume.js └── multiple.js ├── Gruntfile.coffee ├── test.js ├── package.json ├── LICENSE ├── spec-e2e └── routing-spec.coffee ├── lib └── router.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | *.log 3 | *.swo 4 | *.swp 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/router'); 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | demo 2 | test 3 | spec 4 | spec-e2e 5 | examples 6 | .travis.yml 7 | Gruntfile.coffee 8 | -------------------------------------------------------------------------------- /spec/helpers/sandboxed-module.coffee: -------------------------------------------------------------------------------- 1 | sm = require 'sandboxed-module' 2 | global.requireSubject = (path,requires) -> 3 | sm.require "./../../#{path}", {requires} 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: "0.10" 3 | services: 4 | - redis 5 | install: 6 | - 'npm install grunt-cli -g' 7 | - 'npm install' 8 | script: 9 | - 'grunt' 10 | -------------------------------------------------------------------------------- /examples/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo 'testing router' 3 | node router 4 | echo 'testing bau' 5 | node bau 6 | echo 'testing recover' 7 | node recover 8 | echo 'testing routers attached to routers' 9 | node multiple 10 | echo 'testing routers next to eachother' 11 | node sibilings 12 | echo 'testing event consumption' 13 | node consume 14 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (g) -> 2 | 3 | g.loadNpmTasks 'grunt-jasmine-bundle' 4 | 5 | g.initConfig 6 | spec: 7 | unit: 8 | options: 9 | helpers: 'spec/helpers/**/*.{js,coffee}' 10 | specs: 'spec/**/*.{js,coffee}' 11 | e2e: 12 | options: 13 | helpers: 'spec-e2e/helpers/**/*.{js,coffee}' 14 | specs: 'spec-e2e/**/*.{js,coffee}' 15 | 16 | g.registerTask 'default', ['spec:unit', 'spec:e2e'] 17 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var router = require('./')(); 2 | router.on(function (sock, args, next) { 3 | console.log(args); 4 | next(); 5 | }); 6 | var io = require('socket.io')(3001); 7 | io.use(router); 8 | io.on('connect', function (sock) { 9 | sock.on('say', function () { 10 | sock.emit('say'); 11 | }); 12 | }); 13 | setTimeout(function () { 14 | var sock = require('socket.io-client').connect('ws://localhost:3001'); 15 | sock.on('connect', function () { 16 | sock.emit('say'); 17 | }); 18 | sock.on('say', function () { 19 | console.log('got say'); 20 | process.exit(0); 21 | }); 22 | }, 1000); 23 | -------------------------------------------------------------------------------- /examples/bau.js: -------------------------------------------------------------------------------- 1 | var ok = require('assert').equal; 2 | 3 | var router = require('./..')(); 4 | 5 | router.on(function (socket, args, next) { 6 | //do something! 7 | next(); 8 | }); 9 | 10 | var io = require('socket.io')(3000); 11 | io.use(router); 12 | io.on('connection', function (socket) { 13 | socket.on('echo', function (data) { 14 | socket.emit('echo', data); 15 | }); 16 | }); 17 | 18 | setTimeout(function () { 19 | 20 | var client = require('socket.io-client').connect('ws://localhost:3000'); 21 | client.on('connect', function () { 22 | client.emit('echo', 'data'); 23 | }); 24 | client.on('echo', function(data) { 25 | ok(data,'data'); 26 | console.log('we good'); 27 | process.exit(); 28 | }); 29 | 30 | },1000); 31 | -------------------------------------------------------------------------------- /examples/sibilings.js: -------------------------------------------------------------------------------- 1 | // this is to show that routers next to eachother place nice 2 | var ok = require('assert').equal; 3 | var Router = require('../.'); 4 | var a = Router().use(function (sock, args, next) { args.push('Hello'); next(); }); 5 | var b = Router().use(function (sock, args, next) { args.push('World'); next(); }); 6 | var io = require('socket.io')(3000); 7 | io.use(a); 8 | io.use(b); 9 | io.on('connection', function (sock) { 10 | sock.on('hi', function (hello, world) { 11 | sock.emit('hi', hello, world); 12 | }); 13 | }); 14 | 15 | setTimeout(function () { 16 | var sock = require('socket.io-client')('ws://localhost:3000'); 17 | sock.on('connect', function () { 18 | sock.emit('hi'); 19 | }); 20 | sock.on('hi', function (hello, world) { 21 | ok('Hello', hello); 22 | ok('World', world); 23 | console.log('we good'); 24 | process.exit(0); 25 | }); 26 | }, 1000); 27 | -------------------------------------------------------------------------------- /examples/recover.js: -------------------------------------------------------------------------------- 1 | var ok = require('assert').equal; 2 | 3 | var router = require('./..')(); 4 | 5 | // handle all events here 6 | router.on(function (socket, args, next) { 7 | next(); 8 | }); 9 | 10 | // gets 'some event' 11 | router.on('some event', function (socket, args, next) { 12 | next(new Error('something wrong')); 13 | }); 14 | 15 | // handle the error 16 | router.on(function (err, socket, args, next) { 17 | //handled the error! 18 | next(); 19 | }); 20 | 21 | // handle all events 22 | router.on(function (socket, args) { 23 | //emits back to the client 24 | socket.emit(args.shift(), args); 25 | }); 26 | 27 | var io = require('socket.io')(3000); 28 | io.use(router); 29 | 30 | setTimeout(function () { 31 | var client = require('socket.io-client').connect('ws://localhost:3000'); 32 | client.on('connect', function () { 33 | client.emit('some event', 'data'); 34 | }); 35 | client.on('some event', function(data) { 36 | ok(data,'data'); 37 | console.log('we good'); 38 | process.exit(); 39 | }); 40 | },1000); 41 | -------------------------------------------------------------------------------- /examples/router.js: -------------------------------------------------------------------------------- 1 | var ok = require('assert').equal; 2 | 3 | var router = require('./..')(); 4 | 5 | // handle all events here 6 | router.on(function (socket, args, next) { 7 | next(); 8 | }); 9 | 10 | // handle events named 'some event' 11 | router.on('some event', function (socket, args, next) { 12 | ok(args[0],'some event'); 13 | next(); 14 | }); 15 | 16 | // handle all events 17 | router.on(function (socket, args) { 18 | //emits back to the client 19 | socket.emit(args.shift(), args); 20 | }); 21 | 22 | router.on(function (socket, args) { 23 | //this wont fire because socket.emit() has been called which is like `res.end()` in express. 24 | }); 25 | 26 | var io = require('socket.io')(3000); 27 | io.use(router); 28 | 29 | setTimeout(function () { 30 | var client = require('socket.io-client').connect('ws://localhost:3000'); 31 | client.on('connect', function () { 32 | client.emit('some event', 'data'); 33 | }); 34 | client.on('some event', function(data) { 35 | ok(data,'data'); 36 | console.log('we good'); 37 | process.exit(); 38 | }); 39 | },1000); 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket.io-events", 3 | "version": "0.4.6", 4 | "description": "Power your socket.io apps with express like event routing.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "grunt" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:turbonetix/socket.io-events.git" 12 | }, 13 | "keywords": [ 14 | "socket.io", 15 | "event", 16 | "router", 17 | "middleware" 18 | ], 19 | "author": "Nathan G. Romano ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/turbonetix/socket.io-event/issues" 23 | }, 24 | "homepage": "https://github.com/turbonetix/socket.io-events", 25 | "devDependencies": { 26 | "sandboxed-module": "~0.3.0", 27 | "grunt-jasmine-bundle": "~0.2.0", 28 | "coffee-script": "~1.7.1", 29 | "grunt-cli": "~0.1.13", 30 | "grunt": "~0.4.5", 31 | "supertest": "~0.13.0", 32 | "request": "~2.36.0", 33 | "socket.io": "~1.0.4", 34 | "socket.io-client": "~1.0.4" 35 | }, 36 | "dependencies": { 37 | "debug": "^2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 turbonetix 4 | Copyright (c) 2014 Nathan G. Romano 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /examples/consume.js: -------------------------------------------------------------------------------- 1 | var ok = require('assert').equal; 2 | 3 | var router = require('./..')(); 4 | router.on(function (socket, args, next) { 5 | //do something! 6 | next(); 7 | }); 8 | router.on(function (sock, args, next) { 9 | sock.emit('what', new Date); 10 | // this next won't do us any good the emit was already called. 11 | next(); 12 | setTimeout(function () { 13 | sock.emit('what', new Date); 14 | }, 1000); 15 | sock.emit('what', new Date); 16 | }); 17 | 18 | var io = require('socket.io')(3000); 19 | io.use(router); 20 | io.on('connection', function (socket) { 21 | // this won't get called because the "echo" event will be consumed! 22 | socket.on('echo', function (data) { 23 | socket.emit('echo', data); 24 | }); 25 | }); 26 | 27 | setTimeout(function () { 28 | var client = require('socket.io-client').connect('ws://localhost:3000'); 29 | client.on('connect', function () { 30 | client.emit('echo', 'data'); 31 | }); 32 | client.on('echo', function (data) { 33 | ok(data,'data'); 34 | console.log('we good'); 35 | process.exit(); 36 | }); 37 | var c = 0; 38 | client.on('what', function (data) { 39 | if (++c >= 3) { 40 | console.log('we good'); 41 | process.exit(); 42 | } 43 | }); 44 | },1000); 45 | -------------------------------------------------------------------------------- /examples/multiple.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('router'); 2 | var ok = require('assert').equal; 3 | var Router = require('./..'); 4 | 5 | var a = Router(); 6 | a.on('say', function (sock, args, next) { 7 | debug('World'); 8 | args.push('World'); 9 | next(); 10 | }); 11 | 12 | var b = Router(); 13 | b.use('say', function (sock, args, next) { 14 | debug('Good'); 15 | args.push('Good'); 16 | next(); 17 | }); 18 | 19 | var c = Router(); 20 | c.use('say', function (sock, args, next) { 21 | debug('Bye'); 22 | args.push('Bye'); 23 | next(); 24 | }); 25 | 26 | var d = Router(); 27 | d.use(function (sock, args, next) { 28 | debug('!!!'); 29 | args.push('!!!'); 30 | next(); 31 | }); 32 | 33 | a.use(b) 34 | b.use(c); 35 | c.use(d); 36 | 37 | var io = require('socket.io')(3000); 38 | io.use(a); 39 | io.on('connection', function (sock) { 40 | sock.on('say', function (hello, world, good, bye, exclamation) { 41 | debug('the socket emit function????', sock.emit.toString()); 42 | sock.emit('say', hello, world, good, bye, exclamation); 43 | }); 44 | }); 45 | 46 | setTimeout(function () { 47 | var sock = require('socket.io-client').connect('ws://localhost:3000'); 48 | sock.on('connect', function () { 49 | sock.emit('say', 'Hello'); 50 | }); 51 | sock.on('say', function (hello, world, good, bye, exclamation) { 52 | ok(hello, 'Hello'); 53 | ok(world, 'World'); 54 | ok(good, 'Good'); 55 | ok(bye, 'Bye'); 56 | ok(exclamation, '!!!'); 57 | console.log('we good'); 58 | process.exit(0); 59 | }); 60 | 61 | }, 1000); 62 | -------------------------------------------------------------------------------- /spec-e2e/routing-spec.coffee: -------------------------------------------------------------------------------- 1 | debug = require('debug')('router') 2 | 3 | describe 'routing events', -> 4 | 5 | describe 'when a router receives some event it should route it to a handler', -> 6 | 7 | Given -> @foo = jasmine.createSpy 'foo' 8 | Given -> @bar = jasmine.createSpy 'bar' 9 | Given -> @baz = jasmine.createSpy 'baz' 10 | Given -> @no = jasmine.createSpy 'no' 11 | Given -> 12 | @router = require('./..')() 13 | @router.on (socket, args, next) => 14 | @foo() 15 | next() 16 | @router.on 'some event', (socket, args, next) => 17 | @bar() 18 | next() 19 | @router.on '*', (socket, args, next) => 20 | @baz() 21 | socket.emit.apply(socket, args) 22 | @router.on 'some event', (socket, args, next) => 23 | @no() 24 | next() 25 | 26 | Given -> 27 | @io = require('socket.io')(5000) 28 | @io.use @router.middleware 29 | 30 | Given -> @message = 'Hello, World' 31 | 32 | When (done) -> 33 | @socket = require('socket.io-client').connect('ws://localhost:5000') 34 | @socket.on 'connect', => 35 | @socket.emit 'some event', @message 36 | @socket.on 'some event', (message) => 37 | @res = message 38 | done() 39 | 40 | Then -> expect(@res).toEqual @message 41 | And -> expect(@baz).toHaveBeenCalled() 42 | And -> expect(@bar).toHaveBeenCalled() 43 | And -> expect(@foo).toHaveBeenCalled() 44 | And -> expect(@no).not.toHaveBeenCalled() 45 | 46 | describe 'when a router receives some event and it is not handle business as usual', -> 47 | 48 | Given -> 49 | @router = require('./..')() 50 | @router.on (socket, args, next) -> next() 51 | 52 | Given -> 53 | @io = require('socket.io')(5001) 54 | @io.use @router.middleware 55 | @io.on 'connect', (socket) -> 56 | socket.on 'echo', (data) -> 57 | socket.emit 'echo', data 58 | 59 | Given -> @message = 'Hello, World' 60 | 61 | When (done) -> 62 | @socket = require('socket.io-client').connect('ws://localhost:5001') 63 | @socket.on 'connect', => 64 | @socket.emit 'echo', @message 65 | @socket.on 'echo', (message) => 66 | @res = message 67 | done() 68 | Then -> expect(@res).toEqual @message 69 | 70 | describe 'two routers connected to each other', -> 71 | 72 | Given -> @hit = 0 73 | 74 | Given -> 75 | @a = require('./..')() 76 | @a.on (socket, args, next) => 77 | debug('handler "a" socket.id %s args %s typeof next', socket.id, args, typeof next) 78 | @hit++ 79 | next() 80 | 81 | Given -> 82 | @b = require('./..')() 83 | @b.on (socket, args, next) => 84 | debug('handler "b" socket.id %s args %s typeof next', socket.id, args, typeof next) 85 | @hit++ 86 | next() 87 | 88 | Given -> @a.use @b 89 | 90 | Given -> 91 | @io = require('socket.io')(5002) 92 | @io.use @a 93 | @io.on 'connect', (socket) -> 94 | socket.on 'echo', (data) -> 95 | socket.emit 'echo', data 96 | 97 | Given -> @message = 'Hello, World' 98 | 99 | When (done) -> 100 | @socket = require('socket.io-client').connect('ws://localhost:5002') 101 | @socket.on 'connect', => 102 | @socket.emit 'echo', @message 103 | @socket.on 'echo', (message) => 104 | @res = message 105 | done() 106 | Then -> expect(@res).toEqual @message 107 | And -> expect(@hit).toBe 2 108 | 109 | describe 'two routers side by side should dwork together', -> 110 | 111 | Given -> 112 | @a = require('./..')() 113 | @a.on (socket, args, next) => 114 | args.pop() 115 | debug('handler "a" socket.id %s args %s typeof next', socket.id, args, typeof next) 116 | args.push('play') 117 | next() 118 | 119 | Given -> 120 | @b = require('./..')() 121 | @b.on (socket, args, next) => 122 | debug('handler "b" socket.id %s args %s typeof next', socket.id, args, typeof next) 123 | args.push('nice') 124 | next() 125 | 126 | Given -> 127 | @io = require('socket.io')(5003) 128 | @io.use @a 129 | @io.use @b 130 | @io.on 'connect', (socket) -> 131 | socket.on 'echo', (play, nice) -> 132 | socket.emit 'echo', play, nice 133 | 134 | When (done) -> 135 | @socket = require('socket.io-client').connect('ws://localhost:5003') 136 | @socket.on 'connect', => 137 | @socket.emit 'echo', @message 138 | @socket.on 'echo', (play, nice) => 139 | @res = play + ' ' + nice 140 | done() 141 | 142 | Then -> expect(@res).toEqual 'play nice' 143 | 144 | describe 'should support wild cards and regex', -> 145 | 146 | Given -> @hit = 0 147 | 148 | Given -> 149 | @a = require('./..')() 150 | @a.on 'some*', (sock, args, next) => 151 | @hit++ 152 | next() 153 | @a.on '*event', (sock, args, next) => 154 | @hit++ 155 | next() 156 | @a.on /^\w+\s/, (sock, args, next) => 157 | @hit++ 158 | next() 159 | 160 | Given -> 161 | @io = require('socket.io')(5004) 162 | @io.use @a 163 | @io.on 'connect', (socket) -> 164 | socket.on 'some event', -> 165 | socket.emit 'some event', new Date 166 | 167 | When (done) -> 168 | @socket = require('socket.io-client').connect('ws://localhost:5004') 169 | @socket.on 'connect', => 170 | @socket.emit 'some event' 171 | @socket.on 'some event', -> 172 | done() 173 | 174 | Then -> expect(@hit).toBe 3 175 | 176 | describe 'should handle multiple events', -> 177 | 178 | Given -> @hit = 0 179 | 180 | Given -> 181 | @a = require('./..')() 182 | @a.on 'some*', (sock, args, next) => 183 | @hit++ 184 | next() 185 | @a.on '*event', (sock, args, next) => 186 | @hit++ 187 | next() 188 | @a.on /^\w+\s/, (sock, args, next) => 189 | @hit++ 190 | next() 191 | 192 | Given -> 193 | @io = require('socket.io')(5005) 194 | @io.use @a 195 | @io.on 'connect', (socket) -> 196 | socket.on 'some event', -> 197 | socket.emit 'some event', new Date 198 | 199 | When (done) -> 200 | @socket = require('socket.io-client').connect('ws://localhost:5005') 201 | @socket.on 'connect', => 202 | @a = 0 203 | @socket.emit 'some event' 204 | @socket.emit 'some event' 205 | @socket.emit 'some event' 206 | @socket.on 'some event', => 207 | if ++@a == 3 208 | done() 209 | 210 | Then -> expect(@hit).toBe 9 211 | -------------------------------------------------------------------------------- /lib/router.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('socket.io-events:router'); 2 | var util = require('util'); 3 | var EventEmitter = require('events').EventEmitter; 4 | var emit = EventEmitter.prototype.emit; 5 | var slice = Array.prototype.slice; 6 | 7 | module.exports = Router; 8 | 9 | /** 10 | * Binds a function to the socket.io socket #onevent method and pushes the 11 | * event through middleware. 12 | * 13 | * @return Router 14 | */ 15 | 16 | function Router () { 17 | if (!(this instanceof Router)) return new Router(); 18 | 19 | function router (socket, cb) { 20 | debug('router socket.id %s typeof cb %s', socket ? socket.id : null, typeof cb); 21 | router.middleware(socket, cb); 22 | } 23 | 24 | router.__proto__ = Router.prototype; 25 | 26 | var index = 0; 27 | 28 | /** 29 | * Inc the index 30 | * 31 | * @return Number 32 | */ 33 | 34 | router.index = function () { 35 | return index++; 36 | }; 37 | 38 | /** 39 | * The middleware function 40 | * 41 | * @api private 42 | * @param {Socket} socket 43 | * @param {function} cb 44 | */ 45 | 46 | router.middleware = function (socket, cb) { 47 | debug('middleware socket.id %s typeof cb %s', (socket ? socket.id : null), typeof cb); 48 | if ('function' != typeof socket.onevent || socket.onevent !== router.onEvent) { 49 | if (socket.onevent && socket.onevent.router) { 50 | socket.onevent.router.use(router); 51 | } 52 | else { 53 | socket.onevent = router.onEvent; 54 | } 55 | } 56 | cb(); 57 | }; 58 | 59 | /** 60 | * Handles the packet and pushes it through the middleware. 61 | * When invoked "this" will be the socket. 62 | * 63 | * @api private 64 | * @param {Object} packet 65 | */ 66 | 67 | router.onEvent = function (packet) { 68 | var args = packet.data || []; 69 | if (null != packet.id) { 70 | args.push(this.ack(packet.id)); 71 | } 72 | router.onRoute(null, Socket(this), args); 73 | }; 74 | 75 | /* 76 | * This will allow the onEvent method to chain it self when attaching 77 | * multiple routers side by side. e.g. 78 | * 79 | * var io = require('socket.io')(3000); 80 | * io.use(Router()); 81 | * io.use(Router()); 82 | */ 83 | router.onEvent.router = router; 84 | 85 | /** 86 | * Pushes the socket and arguments through the middleware 87 | * 88 | * @api private 89 | * @param {Error} err *optional 90 | * @param {Socket} socket 91 | * @param {Array} args 92 | * @param {Function} cb 93 | */ 94 | 95 | router.onRoute = function (err, socket, args, cb) { 96 | 97 | debug('onRoute err? %s socket.id %s args?', util.isError(err), (socket ? socket.id : null), args); 98 | 99 | socket = router.decorate(socket, function (emit, args) { 100 | debug('done callled'); 101 | 102 | // set back to the old function 103 | if (socket.emit && socket.emit.emit) { 104 | debug('unwrapping the socket'); 105 | socket.emit = socket.emit.emit; 106 | } 107 | 108 | socket.emit.done = true; 109 | 110 | emit.apply(socket, args); 111 | }); 112 | 113 | var i = 0, path = router.getPath(args.length ? args[0] : '*'), len = path.length; 114 | 115 | debug('got path the length is %s', len); 116 | 117 | (function step (err) { 118 | 119 | if (socket.emit.done) return; 120 | 121 | debug('current step %s of %s', i+1, len); 122 | 123 | function next (err) { 124 | if (++i >= len) { 125 | if (err) { 126 | if ('function' === typeof cb) { 127 | cb(err, socket, args); 128 | } 129 | else { 130 | emit.apply(socket, ['error', args]); 131 | } 132 | } 133 | else { 134 | if (socket.emit.done) return; 135 | if ('function' === typeof cb) { 136 | cb(null, socket, args); 137 | } 138 | else { 139 | emit.apply(socket, args); 140 | } 141 | } 142 | } 143 | else { 144 | step(err); 145 | } 146 | } 147 | 148 | var fn = path[i]; 149 | if (!fn) return next(); 150 | 151 | try { 152 | if (err) { 153 | if (fn.length >= 4) { 154 | if (fn instanceof Router) { 155 | fn.onRoute(err, socket, args, next); 156 | } 157 | else { 158 | fn(err, socket, args, next); 159 | } 160 | } 161 | else { 162 | next(err); 163 | } 164 | } 165 | else { 166 | if (fn.length >= 4) { 167 | if (fn instanceof Router) { 168 | fn.onRoute(null, socket, args, next); 169 | } 170 | else { 171 | next(); 172 | } 173 | } 174 | else { 175 | if (fn instanceof Router) { 176 | fn.onRoute(null, socket, args, next); 177 | } 178 | else { 179 | fn(socket, args, next); 180 | } 181 | } 182 | } 183 | } 184 | catch(e) { 185 | debug('caught error %s', e); 186 | debug('the fn causing problems', fn); 187 | console.error(e.stack); 188 | next(e); 189 | } 190 | 191 | })(err); 192 | 193 | }; 194 | 195 | return router; 196 | 197 | }; 198 | 199 | 200 | /** 201 | * Decorates the socket's emit function to call the passed done method 202 | * 203 | * @api private 204 | * @param {Socket} socket 205 | * @param {function} done 206 | * @return Socket 207 | */ 208 | 209 | Router.prototype.decorate = function (socket, done) { 210 | debug('decorate socket.id %s typeof done %s', socket.id, typeof done); 211 | var emit = socket.emit; 212 | 213 | // prevent double wrapping 214 | if (emit.wrapped) return socket; 215 | 216 | socket.emit = function () { 217 | done(emit, slice.call(arguments)); 218 | }; 219 | 220 | socket.emit.emit = emit; 221 | socket.emit.wrapped = true; 222 | 223 | return socket; 224 | }; 225 | 226 | /** 227 | * Gets the routing path given the name 228 | * 229 | * @api private 230 | * @param {String} name 231 | * @return Array 232 | */ 233 | 234 | Router.prototype.getPath = function (name) { 235 | debug('get path %s', name); 236 | name = name || '*'; 237 | var fns = this._fns(), points = [], path = []; 238 | if (name === '*') { 239 | points = points.concat(fns[name]); 240 | } 241 | else { 242 | var keys = Object.keys(fns), i, key, match, regexp; 243 | for (i=0; i 128 ? args[1].slice(0, 125) + '...' : args[1]; 220 | next(); 221 | }); 222 | ``` 223 | 224 | You can also pass in multiple `function`s for handling the `event`. 225 | 226 | ```javascript 227 | var chop = function (sock, args, next) { next() }; 228 | var clean = function (sock, args, next) { next() }; 229 | var pretty = function (sock, args, next) { next() }; 230 | 231 | router.use('chat', chop, clean, pretty); 232 | ``` 233 | 234 | ### Router#use(event:RegExp, fn:Function, ...) 235 | 236 | Bind the `function` using a `RegExp` pattern to match the `event`. 237 | 238 | ```javascript 239 | router.use(/\w+/, function (sock, args, next) { 240 | assert.equal(args[0], 'chat'); 241 | args[1] = args[1].length > 128 ? args[1].slice(0, 125) + '...' : args[1]; 242 | next(); 243 | }); 244 | ``` 245 | 246 | You can also pass in multiple `function`s for handling the `event`. 247 | 248 | ```javascript 249 | var chop = function (sock, args, next) { next() }; 250 | var clean = function (sock, args, next) { next() }; 251 | var pretty = function (sock, args, next) { next() }; 252 | 253 | router.use(/\w+/, chop, clean, pretty); 254 | ``` 255 | 256 | ### Router#use(router:Router, ...) 257 | 258 | You can attach another `Router` instance to your `Router` instance. 259 | 260 | ```javascript 261 | var another = Router(); 262 | another.use(function (sock, args, next) { next(); }); 263 | 264 | router.use(another); 265 | ``` 266 | 267 | Attach multiple routers in a single call. 268 | 269 | ```javascript 270 | var foo = Router(); 271 | foo.use(function (sock, args, next) { next(); }); 272 | 273 | var bar = Router(); 274 | bar.use(function (sock, args, next) { next(); }); 275 | 276 | var baz = Router(); 277 | baz.use(function (sock, args, next) { next(); }); 278 | 279 | router.use(foo, bar, baz); 280 | ``` 281 | 282 | ### Router#use(name:String, router:Router, ...) 283 | 284 | Just like attaching a `function` to the router given the `event`. You can attach `Router` 285 | instance as well to the `event`. 286 | 287 | ```javascript 288 | var foo = Router(); 289 | foo.use(function (sock, args, next) { next(); }); 290 | 291 | router.use('some event', foo); 292 | ``` 293 | 294 | Attach multiple routers in a single call to the `event` too. 295 | 296 | ```javascript 297 | var foo = Router(); 298 | foo.use(function (sock, args, next) { next(); }); 299 | 300 | var bar = Router(); 301 | bar.use(function (sock, args, next) { next(); }); 302 | 303 | var baz = Router(); 304 | baz.use(function (sock, args, next) { next(); }); 305 | 306 | router.use('some event', foo, bar, baz); 307 | ``` 308 | 309 | ### Router#use(fns:Array, ...) 310 | 311 | Attach an `Array` of `Fuction`'s or `Router` instances, or an `Array` or `Array`s . 312 | 313 | ```javascript 314 | var middleware = [ 315 | function (sock, args, next) { next(); }, 316 | [ 317 | function (sock, args, next) { next(); }, 318 | Router().use(function (sock, args, next) { next(); }), 319 | function (sock, args, next) { next(); }, 320 | ], 321 | Router().use(function (sock, args, next) { next(); }) 322 | ]; 323 | 324 | var errHandler = function (err, sock, args, next) { next(err); } 325 | 326 | router.use(middleware, errHandler); 327 | ``` 328 | 329 | ### Router#use(name:String, fns:Array, ...) 330 | 331 | Attach everything to an event. 332 | 333 | ```javascript 334 | var middleware = [ 335 | function (sock, args, next) { next(); }, 336 | [ 337 | function (sock, args, next) { next(); }, 338 | Router().use(function (sock, args, next) { next(); }), 339 | function (sock, args, next) { next(); }, 340 | ], 341 | Router().use(function (sock, args, next) { next(); }) 342 | ]; 343 | 344 | var errHandler = function (err, sock, args, next) { next(err); } 345 | 346 | router.use('only this event', middleware, errHandler); 347 | ``` 348 | 349 | ### Router#on(...) 350 | 351 | This is an alias to to the `use` method. It does the same thing. 352 | 353 | ```javascript 354 | router.on(function (sock, args, next) { next() }); 355 | ``` 356 | 357 | # Installation and Environment Setup 358 | 359 | Install node.js (See download and install instructions here: http://nodejs.org/). 360 | 361 | Clone this repository 362 | 363 | > git clone git@github.com:turbonetix/socket.io-events.git 364 | 365 | cd into the directory and install the dependencies 366 | 367 | > cd socket.io-eventst 368 | > npm install && npm shrinkwrap --dev 369 | 370 | # Running Tests 371 | 372 | Install coffee-script 373 | 374 | > npm install coffee-script -g 375 | 376 | Tests are run using grunt. You must first globally install the grunt-cli with npm. 377 | 378 | > sudo npm install -g grunt-cli 379 | 380 | ## Unit Tests 381 | 382 | To run the tests, just run grunt 383 | 384 | > grunt spec 385 | -------------------------------------------------------------------------------- /spec/lib/router-spec.coffee: -------------------------------------------------------------------------------- 1 | EventEmitter = require('events').EventEmitter 2 | 3 | describe 'Router', -> 4 | 5 | Given -> @Router = requireSubject 'lib/router', {} 6 | 7 | describe '#', -> 8 | Given -> @res = @Router() 9 | Given -> spyOn(@res,'middleware') 10 | When -> @res() 11 | Then -> expect(@res instanceof @Router).toBe true 12 | And -> expect(@res.middleware).toHaveBeenCalled() 13 | 14 | describe 'prototype', -> 15 | 16 | Given -> @router = @Router() 17 | 18 | Given -> @socket = new EventEmitter 19 | Given -> @socket.ack = (id) -> return -> 20 | 21 | describe '#middleware', -> 22 | 23 | Given -> @cb = jasmine.createSpy 'cb' 24 | Given -> @router.middleware @socket, @cb 25 | When -> @router.middleware @socket, @cb 26 | Then -> expect(@socket.onevent).toEqual @router.onEvent 27 | And -> expect(@cb).toHaveBeenCalled() 28 | 29 | describe '#onEvent', -> 30 | 31 | Given -> spyOn(@router,['onRoute']) 32 | Given -> spyOn(EventEmitter.prototype.emit,['apply']).andCallThrough() 33 | Given -> @fn = -> 34 | Given -> spyOn(@socket,['ack']).andReturn(@fn) 35 | Given -> @packet = id:1, data: ['message', 'hello'] 36 | When -> @router.onEvent.call @socket, @packet 37 | Then -> expect(@socket.ack).toHaveBeenCalledWith @packet.id 38 | And -> expect(@router.onRoute).toHaveBeenCalledWith null, jasmine.any(Object), ['message', 'hello', @fn] 39 | And -> expect(@router.onRoute.mostRecentCall.args[1].sock).toBe @socket 40 | 41 | describe '#onRoute (err:Error=null, sock:Object, args:Array)', -> 42 | 43 | Given -> @order = [] 44 | Given -> @a = jasmine.createSpy 'a' 45 | Given -> @b = jasmine.createSpy 'b' 46 | Given -> @c = jasmine.createSpy 'c' 47 | Given -> @d = jasmine.createSpy 'd' 48 | Given -> @e = jasmine.createSpy 'e' 49 | Given -> @f = jasmine.createSpy 'f' 50 | Given -> @error = new Error 'something wrong' 51 | Given -> @foo = (socket, args, next) => @a(); @order.push('a'); next() 52 | Given -> @bar = (socket, args, next) => @b(); @order.push('b'); next() 53 | Given -> @baz = (socket, args, next) => @c(); @order.push('c'); next @error 54 | Given -> @err = (err, socket, args, next) => @d err; @order.push('d'); next err 55 | Given -> @err1 = (err, socket, args, next) => @e err; @order.push('e'); next() 56 | Given -> @cra = (socket, args, next) => @f(); @order.push('f'); next() 57 | Given -> @path = [@foo, @bar, @err, @baz, @err1, @cra] 58 | Given -> @router.use @path 59 | Given -> spyOn(@router,['getPath']).andCallThrough() 60 | Given -> spyOn(@router,['decorate']).andCallThrough() 61 | Given -> @args = ['message', 'hello', @fn] 62 | When -> @router.onRoute null, @socket, @args, null 63 | Then -> expect(@router.getPath).toHaveBeenCalledWith @args[0] 64 | And -> expect(@router.decorate).toHaveBeenCalledWith @socket, jasmine.any(Function) 65 | And -> expect(@a).toHaveBeenCalled() 66 | And -> expect(@b).toHaveBeenCalled() 67 | And -> expect(@c).toHaveBeenCalled() 68 | And -> expect(@d).not.toHaveBeenCalled() 69 | And -> expect(@e).toHaveBeenCalledWith @error 70 | And -> expect(@f).toHaveBeenCalled() 71 | And -> expect(@order).toEqual ['a', 'b', 'c', 'e', 'f'] 72 | 73 | describe '#onRoute (err:Error=null, sock:Object, args:Array, cb:Function)', -> 74 | 75 | Given -> @order = [] 76 | Given -> @cb = jasmine.createSpy 'cb' 77 | Given -> @a = jasmine.createSpy 'a' 78 | Given -> @b = jasmine.createSpy 'b' 79 | Given -> @c = jasmine.createSpy 'c' 80 | Given -> @d = jasmine.createSpy 'd' 81 | Given -> @e = jasmine.createSpy 'e' 82 | Given -> @f = jasmine.createSpy 'f' 83 | Given -> @error = new Error 'something wrong' 84 | Given -> @foo = (socket, args, next) => @a(); @order.push('a'); next() 85 | Given -> @bar = (socket, args, next) => @b(); @order.push('b'); next() 86 | Given -> @baz = (socket, args, next) => @c(); @order.push('c'); next @error 87 | Given -> @err = (err, socket, args, next) => @d err; @order.push('d'); next err 88 | Given -> @err1 = (err, socket, args, next) => @e err; @order.push('e'); next() 89 | Given -> @cra = (socket, args, next) => @f(); @order.push('f'); next() 90 | Given -> @path = [@foo, @bar, @err, @baz, @err1, @cra] 91 | Given -> @router.use @path 92 | Given -> spyOn(@router,['getPath']).andCallThrough() 93 | Given -> spyOn(@router,['decorate']).andCallThrough() 94 | Given -> @args = ['message', 'hello', @fn] 95 | When -> @router.onRoute null, @socket, @args, @cb 96 | Then -> expect(@router.getPath).toHaveBeenCalledWith @args[0] 97 | And -> expect(@router.decorate).toHaveBeenCalledWith @socket, jasmine.any(Function) 98 | And -> expect(@a).toHaveBeenCalled() 99 | And -> expect(@b).toHaveBeenCalled() 100 | And -> expect(@c).toHaveBeenCalled() 101 | And -> expect(@d).not.toHaveBeenCalled() 102 | And -> expect(@e).toHaveBeenCalledWith @error 103 | And -> expect(@f).toHaveBeenCalled() 104 | And -> expect(@cb).toHaveBeenCalled() 105 | And -> expect(@order).toEqual ['a', 'b', 'c', 'e', 'f'] 106 | 107 | describe '#onRoute (err:Error, sock:Object, args:Array)', -> 108 | 109 | Given -> @order = [] 110 | Given -> @cb = jasmine.createSpy 'cb' 111 | Given -> @a = jasmine.createSpy 'a' 112 | Given -> @b = jasmine.createSpy 'b' 113 | Given -> @c = jasmine.createSpy 'c' 114 | Given -> @d = jasmine.createSpy 'd' 115 | Given -> @e = jasmine.createSpy 'e' 116 | Given -> @f = jasmine.createSpy 'f' 117 | Given -> @error = new Error 'something wrong' 118 | Given -> @foo = (socket, args, next) => @a(); @order.push('a'); next() 119 | Given -> @bar = (socket, args, next) => @b(); @order.push('b'); next() 120 | Given -> @baz = (socket, args, next) => @c(); @order.push('c'); next @error 121 | Given -> @err = (err, socket, args, next) => @d err; @order.push('d'); next err 122 | Given -> @err1 = (err, socket, args, next) => @e err; @order.push('e'); next() 123 | Given -> @cra = (socket, args, next) => @f(); @order.push('f'); next() 124 | Given -> @path = [@foo, @bar, @err, @baz, @err1, @cra] 125 | Given -> @router.use @path 126 | Given -> spyOn(@router,['getPath']).andCallThrough() 127 | Given -> spyOn(@router,['decorate']).andCallThrough() 128 | Given -> @args = ['message', 'hello', @fn] 129 | When -> @router.onRoute @error, @socket, @args, @cb 130 | Then -> expect(@router.getPath).toHaveBeenCalledWith @args[0] 131 | And -> expect(@router.decorate).toHaveBeenCalledWith @socket, jasmine.any(Function) 132 | And -> expect(@a).not.toHaveBeenCalled() 133 | And -> expect(@b).not.toHaveBeenCalled() 134 | And -> expect(@c).not.toHaveBeenCalled() 135 | And -> expect(@d).toHaveBeenCalled() 136 | And -> expect(@e).toHaveBeenCalledWith @error 137 | And -> expect(@f).toHaveBeenCalled() 138 | And -> expect(@cb).toHaveBeenCalled() 139 | And -> expect(@order).toEqual ['d', 'e', 'f'] 140 | 141 | describe '#decorate', -> 142 | 143 | Given -> @old = emit: @socket.emit 144 | Given -> spyOn(@old.emit, ['apply']).andCallThrough() 145 | Given -> @done = (emit, args) => emit.apply(@socket, args) 146 | When -> 147 | @router.decorate @socket, @done 148 | @socket.emit 'hello' 149 | Then -> expect(@old.emit.apply).toHaveBeenCalledWith @socket, ['hello'] 150 | 151 | describe '#use', -> 152 | 153 | Given -> @test = => @router.use() 154 | Then -> expect(@test).toThrow new Error 'expecting at least one parameter' 155 | 156 | describe '#use (name:Sring)', -> 157 | 158 | Given -> @test = => @router.use 'some event' 159 | Then -> expect(@test).toThrow new Error 'we have the name, but need a handler' 160 | 161 | describe '#use (regexp:RegExp)', -> 162 | 163 | Given -> @test = => @router.use /^w+/ 164 | Then -> expect(@test).toThrow new Error 'we have the name, but need a handler' 165 | 166 | describe '#use (fn:Function)', -> 167 | 168 | Given -> @fn = (socket, args, next) -> 169 | When -> @router.use @fn 170 | Then -> expect(@router.fns().length).toBe 1 171 | 172 | describe '#use (router:Router)', -> 173 | 174 | Given -> @a = @Router() 175 | When -> @router.use @a 176 | Then -> expect(@router.fns().length).toBe 1 177 | And -> expect(@router.fns()[0][1]).toEqual @a 178 | 179 | describe '#use (name:String,fn:Function)', -> 180 | 181 | Given -> @name = 'name' 182 | Given -> @fn = (socket, args, next) -> 183 | When -> @router.use @name, @fn 184 | Then -> expect(@router.fns(@name).length).toBe 1 185 | 186 | describe '#use (regexp:RegExp,fn:Function)', -> 187 | 188 | Given -> @name = /\w+/ 189 | Given -> @fn = (socket, args, next) -> 190 | When -> @router.use @name, @fn 191 | Then -> expect(@router.fns(@name).length).toBe 1 192 | 193 | describe '#use (name:Array)', -> 194 | 195 | Given -> @a = -> 196 | Given -> @b = -> 197 | Given -> @c = -> 198 | Given -> @name = [@a, @b, @c] 199 | When -> @router.use @name 200 | Then -> expect(@router.fns()).toEqual [[0,@a], [1,@b], [2,@c]] 201 | 202 | describe '#on', -> 203 | 204 | Then -> expect(@router.on).toEqual @router.use 205 | 206 | describe '#getPath (name:String)', -> 207 | 208 | Given -> @a = jasmine.createSpy 'a' 209 | Given -> @b = jasmine.createSpy 'b' 210 | Given -> @c = jasmine.createSpy 'c' 211 | Given -> @d = jasmine.createSpy 'd' 212 | Given -> @router.use 'test*', @a 213 | Given -> @router.use 'tes*', @b 214 | Given -> @router.use 't*r', @c 215 | Given -> @router.use /^\w+/, @d 216 | 217 | describe 'name matches', -> 218 | 219 | Given -> @name = 'tester' 220 | When -> @res = @router.getPath @name 221 | Then -> expect(@res).toEqual [@a, @b, @c, @d] 222 | 223 | describe 'name does not matches', -> 224 | 225 | Given -> @name = '!sleeper' 226 | When -> @res = @router.getPath @name 227 | Then -> expect(@res).toEqual [] 228 | 229 | describe '#index', -> 230 | 231 | When -> expect(@router.index()).toBe 0 232 | When -> expect(@router.index()).toBe 1 233 | When -> expect(@router.index()).toBe 2 234 | 235 | describe '#fns', -> 236 | 237 | When -> @res = @router.fns() 238 | Then -> expect(@res).toEqual [] 239 | 240 | describe '#fns (name:String="test")', -> 241 | 242 | Given -> @name = 'test' 243 | When -> @res = @router.fns @name 244 | Then -> expect(@res).toEqual [] 245 | 246 | describe '#_fns', -> 247 | 248 | When -> @res = @router._fns() 249 | Then -> expect(@res).toEqual {} 250 | --------------------------------------------------------------------------------