├── .gitignore ├── .travis.yml ├── LICENSE ├── hub ├── eventHub.js └── hub.js ├── examples ├── browser │ ├── yui3 │ │ ├── server.js │ │ └── index.html │ └── jquery │ │ ├── server.js │ │ └── index.html └── nodejs │ └── client.js ├── package.json ├── spec ├── clientSpec.js ├── multiSpec.js └── hubSpec.js ├── clients ├── browser │ ├── yui3.js │ └── jquery.js └── server │ └── eventClient.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.8 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2013 Mark Ethan Trostler 2 | MIT License - http://opensource.org/licenses/mit-license.php 3 | -------------------------------------------------------------------------------- /hub/eventHub.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2013 Mark Ethan Trostler 2 | // MIT License - http://opensource.org/licenses/mit-license.php 3 | Hub = require('./hub'); 4 | Hub.start(); 5 | -------------------------------------------------------------------------------- /examples/browser/yui3/server.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2013 Mark Ethan Trostler 2 | // MIT License - http://opensource.org/licenses/mit-license.php 3 | var connect = require('connect') 4 | , server = connect.createServer( 5 | connect.logger() 6 | , connect.static(__dirname + '/../../..') 7 | , connect.static(__dirname) 8 | ) 9 | , io = require('socket.io').listen(server) 10 | , port = 8888 11 | ; 12 | 13 | server.listen(port); 14 | console.log('Listening on port ' + port); 15 | -------------------------------------------------------------------------------- /examples/browser/jquery/server.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2013 Mark Ethan Trostler 2 | // MIT License - http://opensource.org/licenses/mit-license.php 3 | var connect = require('connect') 4 | , server = connect.createServer( 5 | connect.logger() 6 | , connect.static(__dirname + '/../../..') 7 | , connect.static(__dirname) 8 | ) 9 | , io = require('socket.io').listen(server) 10 | , port = 8888 11 | ; 12 | 13 | server.listen(port); 14 | console.log('Listening on port ' + port); 15 | 16 | -------------------------------------------------------------------------------- /examples/nodejs/client.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2013 Mark Ethan Trostler 2 | // MIT License - http://opensource.org/licenses/mit-license.php 3 | var eventHub = require('../../clients/server/eventClient.js').getClientHub('zzo.eventhub.jit.su?token=ehrox'); 4 | 5 | var worker = function(data) { 6 | console.log("in dowork:"); 7 | console.log(data); 8 | //throw { this: "sux" }; 9 | return { data: data, mark: 'trostler', pid: process.pid }; 10 | } 11 | 12 | eventHub.on('eventHubReady', function() { 13 | console.log('event hub ready!'); 14 | eventHub.on('click', eventHub.makeListener(worker), { type: 'unicast' }); 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EventHub", 3 | "description": "Event Hub and clients for event-based applications", 4 | "keywords": [ 5 | "event", 6 | "socket.io", 7 | "hub", 8 | "server", 9 | "client", 10 | "yui", 11 | "jquery" 12 | ], 13 | "version": "1.2.4", 14 | "author": "Mark Ethan Trostler ", 15 | "preferGlobal": true, 16 | "bin": { 17 | "eventHub": "./hub/eventHub.js" 18 | }, 19 | "main": "./clients/server/eventClient.js", 20 | "scripts": { 21 | "start": "node ./hub/eventHub.js", 22 | "test": "jasmine-node spec" 23 | }, 24 | "engines": { 25 | "node": ">=0.4" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/zzo/EventHub.git" 30 | }, 31 | "homepage": "https://github.com/zzo/EventHub", 32 | "dependencies": { 33 | "socket.io": "~0.8.7", 34 | "node-uuid": "~1.3" 35 | }, 36 | "devDependencies": { 37 | "jasmine-node": "" 38 | }, 39 | "config": { 40 | "port": "5883" 41 | }, 42 | "subdomain": "zzo.EventHub" 43 | } 44 | -------------------------------------------------------------------------------- /examples/browser/jquery/index.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/browser/yui3/index.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /spec/clientSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2013 Mark Ethan Trostler 2 | // MIT License - http://opensource.org/licenses/mit-license.php 3 | var Hub = require("../clients/server/eventClient") 4 | , events = require("events") 5 | ; 6 | 7 | describe("NodeJS Client", function() { 8 | var hub; 9 | 10 | beforeEach(function() { hub = Hub.getHub(); }); 11 | 12 | it("has basic functionality", function() { 13 | expect(hub).toBeDefined(); 14 | expect(hub.on).toBeDefined(); 15 | expect(hub.emit).toBeDefined(); 16 | expect(hub.addEvent).toBeDefined(); 17 | expect(hub.makeListener).toBeDefined(); 18 | expect(hub instanceof events.EventEmitter).toBeTruthy(); 19 | }); 20 | it("Is ready when socket added", function(done) { 21 | hub.on("eventHubReady", done); 22 | hub.addSocket({}); 23 | }); 24 | it("Passes messages to Hub", function(done) { 25 | var type = { type: "unicast" }; 26 | var fakeSocket = { 27 | emit: function(action, eventName, what) { 28 | expect(action).toMatch(/newListener|eventHub:on/); 29 | expect(eventName).toMatch(/eventClient:unicast:0|foo/); 30 | if (typeof what == 'object') { 31 | expect(what).toEqual(type); 32 | } else { 33 | expect(typeof what).toEqual('function'); 34 | } 35 | done(); 36 | } 37 | }; 38 | hub.on("eventHubReady", function() { 39 | hub.on("foo", function() {}, type); 40 | }); 41 | hub.addSocket(fakeSocket); 42 | }); 43 | it("Receives messages from the Hub", function(done) { 44 | var obj = {}; 45 | var socket = { 46 | emit: function(action, eventName, func) { 47 | expect(action).toBe('newListener'); 48 | expect(eventName).toBe('foo'); 49 | expect(func).toBe(done); 50 | } 51 | }; 52 | hub.on("foo", function(data) { 53 | expect(data).toBe(obj); 54 | }); 55 | hub.on("eventHubReady", function() { 56 | socket.$emit("foo", obj); 57 | done(); 58 | }); 59 | hub.addSocket(socket); 60 | }); 61 | it("addEvent works", function(done) { 62 | var ev = hub.addEvent("foobie"); 63 | var obj = { grumble: "bumble" }; 64 | expect(typeof ev.on).toBe('function'); 65 | expect(typeof ev.emit).toBe('function'); 66 | ev.on(function(data) { expect(data).toBe(obj); done(); }); 67 | ev.emit(obj); 68 | }); 69 | it("makeListener no hub works", function() { 70 | var obj = {}; 71 | var f = function(data) { 72 | expect(data).toBe(obj); 73 | return 99; 74 | }; 75 | var fthrow = function(data) { 76 | throw 33; 77 | }; 78 | var f1 = hub.makeListener(f); 79 | var f2 = hub.makeListener(fthrow); 80 | expect(f1(obj)).toBe(99); 81 | expect(f2).toThrow(); 82 | }); 83 | it("makeListener WITH hub works", function() { 84 | var obj = { some: 'data' }; 85 | var f = function(data) { 86 | expect(data).toBe(obj); 87 | return 99; 88 | }; 89 | var fthrow = function(data) { 90 | throw 33; 91 | }; 92 | var f1 = hub.makeListener(f); 93 | var f2 = hub.makeListener(fthrow); 94 | f1(obj, function(error, data) { 95 | expect(error).toBeNull(); 96 | expect(data).toBe(99); 97 | }); 98 | f2(obj, function(error, data) { 99 | expect(data).toBeUndefined(); 100 | expect(error).toBe(33); 101 | }) 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /clients/browser/yui3.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2013 Mark Ethan Trostler 2 | // MIT License - http://opensource.org/licenses/mit-license.php 3 | YUI().add('EventHub', function(Y) { 4 | Y.EventHub = function(io, url) { 5 | var _this = this 6 | , socket = io.connect(url) 7 | , eid = 0 8 | ; 9 | 10 | // Bookkeeping 11 | this.events = {}; 12 | this._fire = this.fire; 13 | this._on = this.on; 14 | 15 | // Get the session & cookie-ize the session 16 | socket.on('ready', function(session) { 17 | Y.Cookie.set("eventHub", session, { expires: new Date("August 11, 2069"), path: '/' }); 18 | socket.$emit = function() { /* fire the event locally */ _this._fire.apply(_this, arguments); }; 19 | _this._fire('eventHubReady'); 20 | }); 21 | 22 | // Set our session if we have one 23 | socket.on('connect', function() { 24 | socket.emit('eventHub:session', Y.Cookie.get('eventHub')); 25 | Y.Global.Hub = _this; 26 | }); 27 | 28 | // Fire all events off to the hub 29 | this.fire = function() { 30 | socket.emit.apply(socket, arguments); 31 | }; 32 | 33 | /* Tell event switch we're listening for a unicast event... */ 34 | this.on = function(eventName, func, args) { 35 | this._on.call(this, eventName, func); 36 | if (typeof(args) !== 'undefined') { 37 | args.ts = eid++; 38 | this.on('eventClient:' + args.type + ':' + args.ts, function(err) { 39 | if (err) { 40 | // unhook this guy 41 | this.detach(eventName, func); 42 | } 43 | if (args.cb) { 44 | args.cb(err); 45 | } 46 | // don't need this anymore 47 | this.detach('eventClient:' + args.type + ':' + args.ts); 48 | }); 49 | this.fire('eventHub:on', eventName, args); 50 | } 51 | }; 52 | 53 | /* 54 | * An optional helper function to set up compiler-checkable event names 55 | * var clickEvent = hub.addEvent('click'); 56 | * clickEvent.on(function(...) { ... } ); 57 | * clickEvent.fire({ foo: 'goo' }, ... ); 58 | */ 59 | this.addEvent = function(eventName) { 60 | //var _this = this; 61 | 62 | // Dummy up an object that drop in the event name for listening & firing 63 | this.events[eventName] = { 64 | on: function(callback, opts) { 65 | // _this.on.call(_this, eventName, callback, opts); 66 | _this.on(eventName, callback, opts); 67 | } 68 | , fire: function() { 69 | // Shove event name back in argument list & fire it 70 | Array.prototype.unshift.call(arguments, eventName); 71 | _this.fire.apply(_this, arguments); 72 | } 73 | }; 74 | return this.events[eventName]; 75 | }; 76 | 77 | this.makeListener = function(func, thisp) { 78 | // if no callback assume a local call 79 | return function(data, callback) { 80 | try { 81 | var ret = func.call(thisp, data); 82 | return callback ? callback(null, ret) : ret; 83 | } catch(e) { 84 | if (callback) { 85 | callback(e) 86 | } else { 87 | throw e; 88 | } 89 | } 90 | } 91 | }; 92 | }; 93 | 94 | // We're an event target 95 | Y.augment(Y.EventHub, Y.EventTarget); 96 | 97 | }, '1.0', { requires: [ 'event-custom', 'cookie' ] }); 98 | 99 | -------------------------------------------------------------------------------- /clients/browser/jquery.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2013 Mark Ethan Trostler 2 | // MIT License - http://opensource.org/licenses/mit-license.php 3 | (function( $ ) { 4 | $.fn.eventHub = function(io, url) { 5 | var $this = $(this) 6 | , _this = $this 7 | , eid = 0 8 | ; 9 | 10 | if ($.fn.eventHub.EH) { 11 | return $.fn.eventHub.EH; 12 | } 13 | 14 | // Stash it 15 | $.fn.eventHub.EH = $this; 16 | 17 | $this.socket = io.connect(url); 18 | $this._trigger = $this.trigger; 19 | $this._bind = $this.bind; 20 | $this.events = {}; 21 | $this.session = document.cookie; 22 | if ($this.session) { 23 | var matches = $this.session.match(/eventHub=([^;]+)/); 24 | if (matches) { 25 | $this.session = matches[1]; 26 | } else { 27 | $this.session = null; 28 | } 29 | } 30 | 31 | $this.socket.on('ready', function(session) { 32 | document.cookie = 'eventHub=' + session + '; expires=Thu, 11 Aug 2069 20:47:11 UTC; path=/'; 33 | _this.session = session; 34 | _this.socket.$emit = function() { 35 | // skip over jquery event object 36 | var trig = arguments[1] || {}; 37 | if (trig.type) { 38 | if (console && console.error) { 39 | console.error("Your response should not use the key 'type' thanks jQuery!"); 40 | console.error("it will be clobbered - you can find it in obj._type now"); 41 | } 42 | trig._type = trig.type; 43 | } 44 | trig.type = arguments[0]; 45 | _this._trigger(trig, Array.prototype.splice.call(arguments, 2)); 46 | }; 47 | _this._trigger('eventHubReady'); 48 | }); 49 | 50 | $this.socket.on('connect', function() { 51 | $this.socket.emit('eventHub:session', $this.session); 52 | }); 53 | 54 | $this.trigger = $this.fire = function() { 55 | _this.socket.emit.apply(_this.socket, arguments); 56 | }; 57 | 58 | /* Tell event switch we're listening for a unicast/multicast event... */ 59 | $this.bind = function(eventName, func, args) { 60 | this._bind.call(this, eventName, func); 61 | if (typeof(args) !== 'undefined') { 62 | args.ts = eid++; 63 | this.one('eventClient:' + args.type + ':' + args.ts, function(eventObj, err) { 64 | if (err) { 65 | $this.off(eventName, func); 66 | } 67 | if (args.cb) { 68 | args.cb(err); 69 | } 70 | }); 71 | this.trigger('eventHub:on', eventName, args); 72 | } 73 | }; 74 | 75 | /* 76 | * An optional helper function to set up compiler-checkable event names 77 | * var clickEvent = hub.addEvent('click'); 78 | * clickEvent.bind(function(...) { ... } ); 79 | * clickEvent.trigger({ foo: 'goo' }, ... ); 80 | */ 81 | $this.addEvent = function(eventName) { 82 | var _this = this; 83 | this.events[eventName] = { 84 | bind: function(callback, args) { _this.bind.call(_this, eventName, callback, args); } 85 | , trigger: function() { 86 | Array.prototype.unshift.call(arguments, eventName); 87 | _this.trigger.apply(_this, arguments); 88 | } 89 | }; 90 | return this.events[eventName]; 91 | }; 92 | 93 | $this.makeListener = function(func, thisp) { 94 | // if no callback assume a local call 95 | return function(data, callback) { 96 | try { 97 | var ret = func.call(thisp, data); 98 | return callback ? callback(null, ret) : ret; 99 | } catch(e) { 100 | if (callback) { 101 | callback(e) 102 | } else { 103 | throw e; 104 | } 105 | } 106 | } 107 | }; 108 | 109 | return $this; 110 | }; 111 | })(jQuery); 112 | -------------------------------------------------------------------------------- /clients/server/eventClient.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2013 Mark Ethan Trostler 2 | // MIT License - http://opensource.org/licenses/mit-license.php 3 | module.exports = { 4 | eid: 0 5 | , getHub: function() { 6 | 7 | var events = require("events") 8 | , util = require("util") 9 | , eventHubF = function() { 10 | 11 | events.EventEmitter.call(this); 12 | this._emit = this.emit; // keep original emit method 13 | this._on = this.on; // keep original emit method 14 | this.events = {}; // keep original emit method 15 | this.emit = function() { 16 | this._emit.apply(this, arguments); // fire locally 17 | if (this.socket) { 18 | this.socket.emit.apply(this.socket, arguments); 19 | } 20 | }; 21 | 22 | /* Tell event switch we're listening for a unicast event... */ 23 | this.on = function(eventName, func, args) { 24 | this._on.apply(this, arguments); 25 | if (typeof(args) !== 'undefined') { 26 | var ts = module.exports.eid++; 27 | args.ts = ts; 28 | this.emit('eventHub:on', eventName, args); 29 | this.once('eventClient:' + args.type + ':' + args.ts, function(err) { 30 | if (args.cb) { 31 | args.cb(err); 32 | } 33 | }); 34 | } 35 | }; 36 | } 37 | ; 38 | 39 | util.inherits(eventHubF, events.EventEmitter); 40 | 41 | eventHubF.prototype.close = function(socket) { 42 | if (this.socket) { 43 | this.socket.disconnect(); 44 | } 45 | }; 46 | 47 | eventHubF.prototype.addSocket = function(socket) { 48 | var _this = this; 49 | this.socket = socket; 50 | 51 | // Replace socket.io's emitter with our local emitter so the we don't have to 52 | // explicitly listen for all events (which isn't possible anyway....) 53 | // SO this hub just passes all socket.io events to this local eventHub/emitter 54 | //socket.$emit = function() { events.EventEmitter.prototype.emit.apply(_this, arguments); }; 55 | socket.$emit = function() { _this._emit.apply(_this, arguments); }; 56 | 57 | this._emit('eventHubReady'); 58 | }; 59 | 60 | /* 61 | * An optional helper function to set up compiler-checkable event names 62 | * var clickEvent = hub.addEvent('click'); 63 | * clickEvent.on(function(...) { ... } ); 64 | * clickEvent.fire({ foo: 'goo' }, ... ); 65 | */ 66 | eventHubF.prototype.addEvent = function(eventName) { 67 | var _this = this; 68 | this.events[eventName] = { 69 | on: function(callback, args) { _this.on.call(_this, eventName, callback, args); } 70 | , emit: function() { 71 | Array.prototype.unshift.call(arguments, eventName); 72 | _this.emit.apply(_this, arguments); 73 | } 74 | }; 75 | return this.events[eventName]; 76 | }; 77 | 78 | eventHubF.prototype.makeListener = function(func, thisp) { 79 | // if no callback assume a local call 80 | return function(data, callback) { 81 | try { 82 | var ret = func.call(thisp, data); 83 | return callback ? callback(null, ret) : ret; 84 | } catch(e) { 85 | if (callback) { 86 | callback(e) 87 | } else { 88 | throw e; 89 | } 90 | } 91 | } 92 | }; 93 | 94 | return new eventHubF(); 95 | }, 96 | getClientHub: function(url) { 97 | var client = require('socket.io/node_modules/socket.io-client') 98 | , socket = client.connect(url, 99 | { 100 | 'force new connection': true 101 | } 102 | ) 103 | , eventHub = module.exports.getHub() 104 | ; 105 | 106 | socket.on('connect', function () { 107 | eventHub.addSocket(socket); 108 | }); 109 | 110 | socket.on('error', function (e) { 111 | console.log("E: " + e); 112 | }); 113 | 114 | return eventHub; 115 | } 116 | }; 117 | -------------------------------------------------------------------------------- /spec/multiSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2013 Mark Ethan Trostler 2 | // MIT License - http://opensource.org/licenses/mit-license.php 3 | var events = require('events') 4 | , HubClient = require("../clients/server/eventClient") 5 | , Hub = require("../hub/hub") 6 | , http = require('http') 7 | ; 8 | 9 | function getClient(url, cb) { 10 | var client = HubClient.getClientHub(url); 11 | client.on('eventHubReady', function() { 12 | cb(client); 13 | }); 14 | } 15 | 16 | function beDone(runs, waitsFor) { 17 | var reallyDone; 18 | runs(function() { 19 | setTimeout(function() { 20 | reallyDone = true; 21 | console.log('================================================='); 22 | }, 8000); }); 23 | waitsFor(function() { return reallyDone; }, "All Done", 11000); 24 | } 25 | 26 | describe("Server Startup", function() { 27 | var port; 28 | 29 | beforeEach(function() { port = Hub.getPort(); Hub.start(); }); 30 | afterEach(function() { Hub.shutdown(); }); 31 | 32 | it("multicast events", function() { 33 | var client1 34 | , client2 35 | , client3 36 | , allConnected 37 | , gotFoo 38 | , listening = 0 39 | , gotResults 40 | ; 41 | 42 | runs( 43 | function() { 44 | getClient('http://localhost:' + port, function(cl) { 45 | client1 = cl; 46 | getClient('http://localhost:' + port, function(cl) { 47 | client2 = cl; 48 | getClient('http://localhost:' + port, function(cl) { 49 | client3 = cl; 50 | allConnected = true; 51 | }); 52 | }); 53 | }); 54 | }); 55 | 56 | waitsFor(function() { return allConnected; }, "Clients could not connect to hub", 5000); 57 | 58 | runs( 59 | function() { 60 | 61 | client2.on('foo', function(data, callback) { 62 | expect(data.data).toBe('rox'); 63 | }, { type: 'multicast', secret: 'multi', cb: function(err) { 64 | expect(err).toBeUndefined(); 65 | listening++; 66 | } 67 | }); 68 | 69 | client1.on('foo', function(data, callback) { 70 | expect(data.data).toBe('rox'); 71 | callback(null, 'groovy'); 72 | }, { type: 'multicast', secret: 'multi', cb: function(err) { 73 | expect(err).toBeUndefined(); 74 | listening++; 75 | } 76 | }); 77 | }); 78 | 79 | waitsFor(function() { return listening === 2; }, "Both listening for multicast", 5000); 80 | 81 | runs(function() { 82 | client3.emit('foo', { data: 'rox' }, function(err, val) { 83 | expect(val).toBe('groovy'); 84 | expect(err).toBeNull(); 85 | gotResults = true; 86 | }); 87 | }); 88 | 89 | waitsFor(function() { return gotResults; }, "Sent multicast event - got results", 5000); 90 | 91 | runs(function() { client1.close(); client2.close(); client3.close(); }); 92 | 93 | beDone(runs, waitsFor); 94 | }); 95 | 96 | it("multicast event fails with wrong password", function() { 97 | var client1 98 | , client2 99 | , client3 100 | , allConnected 101 | , gotFoo 102 | , listening = 0 103 | , gotResults 104 | ; 105 | 106 | runs( 107 | function() { 108 | getClient('http://localhost:' + port, function(cl) { 109 | client1 = cl; 110 | getClient('http://localhost:' + port, function(cl) { 111 | client2 = cl; 112 | getClient('http://localhost:' + port, function(cl) { 113 | client3 = cl; 114 | allConnected = true; 115 | }); 116 | }); 117 | }); 118 | }); 119 | 120 | waitsFor(function() { return allConnected; }, "Clients could not connect to hub", 5000); 121 | 122 | runs( 123 | function() { 124 | client2.on('foo', function(data, callback) { 125 | expect(data.data).toBe('rox'); 126 | callback(null, 'groovy'); 127 | }, { type: 'multicast', secret: 'multi', cb: function(err) { 128 | expect(err).toBeUndefined(); 129 | listening++; 130 | } 131 | }); 132 | 133 | // Should never get called! 134 | client1.on('foo', function(data, callback) { 135 | expect(true).toBe(false); 136 | }, { type: 'multicast', secret: 'multis', cb: function(err) { 137 | expect(err).toBeDefined(); 138 | listening++; 139 | } 140 | }); 141 | }); 142 | 143 | waitsFor(function() { return listening === 2; }, "Both listening for multicast", 5000); 144 | 145 | runs(function() { 146 | client3.emit('foo', { data: 'rox' }, function(err, val) { 147 | expect(val).toBe('groovy'); 148 | expect(err).toBeNull(); 149 | gotResults = true; 150 | }); 151 | }); 152 | 153 | waitsFor(function() { return gotResults; }, "Sent multicast event - got results", 5000); 154 | 155 | runs(function() { client1.close(); client2.close(); client3.close(); }); 156 | 157 | beDone(runs, waitsFor); 158 | }); 159 | 160 | }); 161 | -------------------------------------------------------------------------------- /spec/hubSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2013 Mark Ethan Trostler 2 | // MIT License - http://opensource.org/licenses/mit-license.php 3 | var events = require('events') 4 | , HubClient = require("../clients/server/eventClient") 5 | , Hub = require("../hub/hub") 6 | , http = require('http') 7 | ; 8 | 9 | function getClient(url, cb) { 10 | var client = HubClient.getClientHub(url); 11 | client.on('eventHubReady', function() { 12 | cb(client); 13 | }); 14 | } 15 | 16 | function beDone(runs, waitsFor) { 17 | var reallyDone; 18 | runs(function() { setTimeout(function() { reallyDone = true; }, 20000); }); 19 | waitsFor(function() { return reallyDone; }, "All Done", 21000); 20 | } 21 | 22 | describe("Server Startup", function() { 23 | var port; 24 | 25 | beforeEach(function() { port = Hub.getPort(); Hub.start(); }); 26 | afterEach(function() { Hub.shutdown(); }); 27 | 28 | it("Started and can connect", function() { 29 | var client 30 | , connected 31 | ; 32 | 33 | runs(function() { 34 | getClient('http://localhost:' + port, function(cl) { 35 | client = cl; 36 | connected = true; 37 | }); 38 | }); 39 | 40 | waitsFor(function() { return connected; }, "Client could not connect to hub", 5000); 41 | runs(function() { client.close(); expect(connected).toBeTruthy(); }); 42 | 43 | beDone(runs, waitsFor); 44 | }); 45 | it("Get socket.io JS", function(done) { 46 | var path = '/socket.io/socket.io.js'; 47 | http.get({ hostname: 'localhost', port: port, path: path, agent: false }, 48 | function (res) { 49 | expect(res.statusCode).toEqual(200); 50 | done(); 51 | } 52 | ); 53 | }); 54 | it("Get yui3 JS", function(done) { 55 | var path = '/yui3.js'; 56 | http.get({ hostname: 'localhost', port: port, path: path, agent: false }, 57 | function (res) { 58 | expect(res.statusCode).toEqual(200); 59 | done(); 60 | } 61 | ); 62 | }); 63 | it("Get jquery JS", function(done) { 64 | var path = '/jquery.js'; 65 | http.get({ hostname: 'localhost', port: port, path: path, agent: false }, 66 | function (res) { 67 | expect(res.statusCode).toEqual(200); 68 | done(); 69 | } 70 | ); 71 | }); 72 | it("Get anything else 404", function(done) { 73 | var path = '/foobie.js'; 74 | http.get({ hostname: 'localhost', port: port, path: path, agent: false }, 75 | function (res) { 76 | expect(res.statusCode).toEqual(404); 77 | done(); 78 | } 79 | ); 80 | }); 81 | it("Pass simple broadcast event", function() { 82 | var client1 83 | , client2 84 | , client3 85 | , allConnected 86 | , gotFoo1 87 | , gotFoo2 88 | ; 89 | 90 | runs( 91 | function() { 92 | getClient('http://localhost:' + port, function(cl) { 93 | client1 = cl; 94 | getClient('http://localhost:' + port, function(cl) { 95 | client2 = cl; 96 | getClient('http://localhost:' + port, function(cl) { 97 | client3 = cl; 98 | allConnected = true; 99 | }); 100 | }); 101 | }); 102 | } 103 | ); 104 | 105 | waitsFor(function() { return allConnected; }, "Clients could not connect to hub", 5000); 106 | 107 | runs( 108 | function() { 109 | client1.on('foo', function(data) { 110 | expect(data.some).toBe('data'); 111 | gotFoo1 = true; 112 | }); 113 | client3.on('foo', function(data) { 114 | expect(data.some).toBe('data'); 115 | gotFoo2 = true; 116 | }); 117 | client2.emit('foo', { some: 'data' }); 118 | } 119 | ); 120 | 121 | waitsFor(function() { return gotFoo1 && gotFoo2; }, "Clients send/receive event", 5000); 122 | 123 | runs(function() { 124 | client1.close(); 125 | client2.close(); 126 | client3.close(); 127 | expect(gotFoo1).toBeTruthy(); 128 | expect(gotFoo2).toBeTruthy(); 129 | }); 130 | 131 | beDone(runs, waitsFor); 132 | }); 133 | it("Pass simple unicast event", function() { 134 | var client1 135 | , client2 136 | , bothConnected 137 | , gotFoo 138 | ; 139 | 140 | runs( 141 | function() { 142 | getClient('http://localhost:' + port + '?token=ehrox', function(cl) { 143 | client1 = cl; 144 | getClient('http://localhost:' + port, function(cl) { 145 | client2 = cl; 146 | bothConnected = true; 147 | }); 148 | }); 149 | } 150 | ); 151 | 152 | waitsFor(function() { return bothConnected; }, "Clients could not connect to hub", 5000); 153 | 154 | runs( 155 | function() { 156 | client1.on('foo', function(data) { 157 | expect(data.data).toBe('rox'); 158 | gotFoo = true; 159 | }, { type: 'unicast' }); 160 | client2.emit('foo', { data: 'rox' }); 161 | } 162 | ); 163 | 164 | waitsFor(function() { return gotFoo; }, "Clients send/receive event", 5000); 165 | runs(function() { client1.close(); client2.close(); expect(gotFoo).toBeTruthy(); }); 166 | 167 | beDone(runs, waitsFor); 168 | }); 169 | it("unicast event callback", function() { 170 | var client1 171 | , client2 172 | , bothConnected 173 | , gotFoo 174 | ; 175 | 176 | runs( 177 | function() { 178 | getClient('http://localhost:' + port + '?token=ehrox', function(cl) { 179 | client1 = cl; 180 | getClient('http://localhost:' + port, function(cl) { 181 | client2 = cl; 182 | bothConnected = true; 183 | }); 184 | }); 185 | } 186 | ); 187 | 188 | waitsFor(function() { return bothConnected; }, "Clients could not connect to hub", 5000); 189 | 190 | runs( 191 | function() { 192 | client1.on('foo', function(data, callback) { 193 | expect(data.data).toBe('rox'); 194 | callback(null, { data: 'rox' }); 195 | }, { type: 'unicast', cb: function(err) { 196 | if (!err) { 197 | client2.emit('foo', { data: 'rox' }, 198 | function(err, data) { expect(data.data).toBe('rox'); gotFoo = true; }); 199 | }; 200 | } 201 | }); 202 | } 203 | ); 204 | 205 | waitsFor(function() { return gotFoo; }, "Clients send/receive event", 5000); 206 | runs(function() { client1.close(); client2.close(); expect(gotFoo).toBeTruthy(); }); 207 | 208 | beDone(runs, waitsFor); 209 | }); 210 | 211 | it("unauth unicast event callback", function() { 212 | var client1 213 | , connected 214 | , failed 215 | ; 216 | 217 | runs( 218 | function() { 219 | getClient('http://localhost:' + port, function(cl) { 220 | client1 = cl; 221 | connected = true; 222 | }); 223 | } 224 | ); 225 | 226 | waitsFor(function() { return connected; }, "Client could not connect to hub", 5000); 227 | 228 | runs( 229 | function() { 230 | client1.on('foo', 231 | function() {} 232 | , { 233 | type: 'unicast' 234 | , cb: function(err) { 235 | failed = err; 236 | } 237 | } 238 | ); 239 | } 240 | ); 241 | 242 | waitsFor(function() { return failed; }, "Clients send/receive event", 5000); 243 | runs(function() { client1.close(); expect(failed.error).toMatch(/not authorized/i); }); 244 | 245 | beDone(runs, waitsFor); 246 | }); 247 | 248 | it("unicast event callback with error", function() { 249 | var client1 250 | , client2 251 | , bothConnected 252 | , gotFoo 253 | ; 254 | 255 | runs( 256 | function() { 257 | getClient('http://localhost:' + port + '?token=ehrox', function(cl) { 258 | client1 = cl; 259 | getClient('http://localhost:' + port, function(cl) { 260 | client2 = cl; 261 | bothConnected = true; 262 | }); 263 | }); 264 | } 265 | ); 266 | 267 | waitsFor(function() { return bothConnected; }, "Clients could not connect to hub", 5000); 268 | 269 | runs( 270 | function() { 271 | 272 | client1.on('foo', function(data, callback) { 273 | expect(data.data).toBe('rox'); 274 | callback('ERROR'); 275 | }, { type: 'unicast', 276 | cb: function(err) { 277 | if (!err) { 278 | client2.emit('foo', { data: 'rox' }, 279 | function(err) { expect(err).toBe('ERROR'); gotFoo = true; }); 280 | }; 281 | } 282 | }); 283 | } 284 | ); 285 | 286 | waitsFor(function() { return gotFoo; }, "Clients send/receive event", 5000); 287 | runs(function() { client1.close(); client2.close(); expect(gotFoo).toBeTruthy(); }); 288 | 289 | beDone(runs, waitsFor); 290 | }); 291 | }); 292 | -------------------------------------------------------------------------------- /hub/hub.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2013 Mark Ethan Trostler 2 | // MIT License - http://opensource.org/licenses/mit-license.php 3 | module.exports = (function() { 4 | 5 | var port = process.env['npm_package_config_port'] || 5883 6 | , http = require('http') 7 | , uuid = require('node-uuid') 8 | , io = require('socket.io') 9 | , sockets = {} 10 | , events = {} 11 | , secret = 'ehrox' // for server-side listeners - change it! 12 | , sio 13 | , app 14 | , fs = require('fs') 15 | , base = __dirname + '/../clients/browser' 16 | ; 17 | 18 | function handler (req, res) { 19 | if (req.url === '/yui3.js' || req.url === '/jquery.js') { 20 | sendFile(res, base + req.url); 21 | } else { 22 | res.statusCode = 404; 23 | res.end('boohoo'); 24 | } 25 | } 26 | 27 | function sendFile(res, file) { 28 | fs.readFile(file, function(err, data) { 29 | if (err) { 30 | res.writeHead(500); 31 | return res.end('Error loading ' + file); 32 | } 33 | 34 | res.writeHead(200); 35 | res.end(data); 36 | }); 37 | } 38 | 39 | return { 40 | getPort: function() { return port; } 41 | , setPort: function(p) { port = p; } 42 | , setSocketIO: function(sio) { io = sio; } 43 | , setUUID: function(id) { uuid = id; } 44 | , getSockets: function() { return sockets; } 45 | , getEvents: function() { return events; } 46 | , setSecret: function(sec) { secret = sec; } 47 | , getSecret: function() { return secret; } 48 | , shutdown: function() { 49 | app.close(); 50 | } 51 | , start: function() { 52 | 53 | app = http.createServer(handler); 54 | sio = io.listen(app); 55 | app.listen(port); 56 | 57 | sio.set('authorization', function (data, accept) { 58 | if (data.query.token && (data.query.token === secret)) { 59 | data.authenticated = true; 60 | return accept(null, true); 61 | } else if (!data.query.session) { 62 | data.session = uuid(); 63 | return accept(null, true); 64 | } else if (data.query.session) { 65 | data.session = data.query.session; 66 | return accept(null, true); 67 | } 68 | return accept('No session transmitted.', false); 69 | }); 70 | 71 | var myemit = function (ev) { 72 | if (ev == 'newListener') { 73 | return this.$emit.apply(this, arguments); 74 | } 75 | 76 | var args = Array.prototype.slice.call(arguments, 1) 77 | , lastArg = args[args.length - 1] 78 | , packet = { 79 | type: 'event' 80 | , name: ev 81 | } 82 | ; 83 | 84 | if ('function' == typeof lastArg) { 85 | packet.id = ++this.ackPackets; 86 | packet.ack = 'data'; // OUR ONE CHANGE!!! 87 | 88 | this.acks[packet.id] = lastArg; 89 | args = args.slice(0, args.length - 1); 90 | } 91 | 92 | packet.args = args; 93 | 94 | return this.packet(packet); 95 | }; 96 | 97 | sio.set('transports', ['xhr-polling']); // Needed for NodeJS clients 98 | sio.sockets.on('connection', function (socket) { 99 | var sock 100 | , hs = socket.handshake 101 | , intervalID 102 | ; 103 | 104 | // use our overridded emit function so function callbacks get passed along 105 | socket.emit = myemit; 106 | 107 | // All sockets connected to hub just broadcast their events 108 | // to everyone else.... UNLESS unicast 109 | socket.$emit = function() { 110 | var event, i 111 | , args = Array.prototype.slice.call(arguments) 112 | ; 113 | 114 | function send_with_session(socket_id) { 115 | var ss = sockets[socket_id]; // 'ss' is socket to send to 116 | // 'socket' is where this event came from 117 | if (ss) { 118 | if (typeof(args[1] === 'object')) { 119 | // toss in session key 120 | if (hs.session) { 121 | args[1]['eventHub:session'] = hs.session; 122 | } 123 | } 124 | ss.emit.apply(ss, args); 125 | } 126 | } 127 | 128 | // some sanity 129 | if (args[0].match(/^eventClient/)) { 130 | // Hey only _I_ can send these messages!! 131 | return; 132 | } 133 | 134 | // If an non-authenticated socket just sent us a session key make 135 | // sure it's the right one 136 | if (typeof args[1] === 'object' && args[1] !== null) { 137 | if (typeof args[1]['eventHub:session'] !== 'undefined' && !hs.authenticated) { 138 | if (args[1]['eventHub:session'] !== hs.session) { 139 | // something funny going on..... not passing on this event 140 | return; 141 | } 142 | } 143 | } 144 | 145 | if (args[0] === 'disconnect') { 146 | delete sockets[socket.id]; 147 | for (event in events) { 148 | if (events[event] === socket.id) { 149 | events[event] = null; 150 | } 151 | } 152 | } else if (args[0] === 'eventHub:session') { 153 | if (args[1]) { 154 | hs.session = args[1]; 155 | } 156 | socket.set('session', hs.session, function() { 157 | socket.emit('ready', hs.session); 158 | }); 159 | } else if (args[0] === 'eventHub:on') { 160 | var eventName = args[1] 161 | , xtra = args[2] 162 | ; 163 | if (xtra.type === 'unicast') { 164 | if (hs.authenticated) { 165 | if (events[eventName]) { 166 | // tell previous dude he's been replaced 167 | console.log('tell ' + events[eventName] 168 | + ' they are done with ' + eventName); 169 | socket.emit.call(sockets[events[eventName]] 170 | , 'eventClient:done', eventName); 171 | } 172 | events[eventName] = socket.id; 173 | // tell listener they are good to go 174 | socket.emit("eventClient:unicast:" + xtra.ts); 175 | } else { 176 | // not authorized to listen for unicast events 177 | // do something smart... 178 | socket.emit("eventClient:unicast:" + xtra.ts, { error: 'Not Authorized' }); 179 | console.log('UNAUTH socket tried to listen for unicast event'); 180 | } 181 | } else if (xtra.type === 'multicast') { 182 | if (!events[eventName]) { 183 | // new multicast event 184 | xtra.secret = xtra.secret || secret; // default to global secret 185 | events[eventName] = { secret: xtra.secret, sockets: [ socket.id ] }; 186 | socket.emit("eventClient:multicast:" + xtra.ts); 187 | } else { 188 | if (events[eventName].secret === xtra.secret) { 189 | // add to existing multicast event 190 | events[eventName].sockets.push(socket.id); 191 | socket.emit("eventClient:multicast:" + xtra.ts); 192 | } else { 193 | // multicast password is wrong! 194 | socket.emit("eventClient:multicast:" + xtra.ts, 195 | { error: "Not Authorized" }); 196 | console.log('UNAUTH socket tried to listen for multicast event - bad secret'); 197 | } 198 | } 199 | } 200 | } else { 201 | if (args[0] !== 'newListener') { 202 | 203 | // Everyday i'm shufflin.... - make room for session key 204 | if (hs.session && typeof args[1] !== 'object') { 205 | if (typeof args[1] !== 'undefined') { 206 | args[2] = args[1]; 207 | args[1] = {}; 208 | } 209 | } 210 | 211 | if (events[args[0]] && events[args[0]].secret) { 212 | // MULTICAST 213 | var multi_sockets = events[args[0]].sockets; 214 | multi_sockets.forEach(function(send_socket) { 215 | send_with_session(send_socket); 216 | }); 217 | } else if (events[args[0]]) { // UNICAST 218 | /* 219 | * So this can be a message from a client or from a backend to 220 | * another backend 221 | * 222 | * If it's from a client then we need to insert the session 223 | * If it's from a backend then the backend needs to include the session 224 | * it should already have if it's needed 225 | */ 226 | send_with_session(events[args[0]]); 227 | /* 228 | ss = sockets[events[args[0]]]; // 'ss' is socket to send to 229 | // 'socket' is where this event came from 230 | if (ss) { 231 | if (typeof(args[1] === 'object')) { 232 | // toss in session key 233 | if (hs.session) { 234 | args[1]['eventHub:session'] = hs.session; 235 | } 236 | } 237 | ss.emit.apply(ss, args); 238 | } 239 | */ 240 | } else { // BROADCAST 241 | 242 | console.log('got broadcast event: ' + args[0]); 243 | for (sock in sockets) { 244 | // we'll use the emit from this socket BUT 245 | // set 'this' to be the actual socket we wanna use 246 | // we send oursevles because 'broadcast' does not 247 | // handle acknowledgements 248 | if (args[0] !== 'eventClient:done' || sock !== socket.id) { 249 | // If the guy we're broadcasthing this to is 250 | // authenticated & there's a session 251 | // associated with the sender & then toss in the session 252 | if (sockets[sock].handshake 253 | && sockets[sock].handshake.authenticated 254 | && hs.session && typeof args[1] === 'object') { 255 | args[1]['eventHub:session'] = hs.session; 256 | } else { 257 | // otherwise no session key for you 258 | if (typeof args[1] === 'object') { 259 | delete args[1]['eventHub:session']; 260 | } 261 | } 262 | socket.emit.apply(sockets[sock], args); 263 | } 264 | } 265 | } 266 | } 267 | } 268 | }; 269 | 270 | sockets[socket.id] = socket; 271 | }); 272 | 273 | console.log('hub listening on port ' + port); 274 | } 275 | } 276 | })(); 277 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build status](https://secure.travis-ci.org/zzo/EventHub.png)](http://travis-ci.org/zzo/EventHub) 2 | EventHub 3 | ======== 4 | 5 | EventHub provides a server-side event hub and server and client side libraries to communicate with the hub. The hub broadcasts all events to all connected clients. Event callbacks are supported. 6 | 7 | Installation 8 | ------------ 9 | 10 | % npm install EventHub -g 11 | % npm start EventHub -g 12 | 13 | This will install and start the EventHub on port 5883 14 | 15 | Configuration 16 | ------------- 17 | 18 | ### Port 19 | 20 | The server listens on a port that all clients must use! 21 | 22 | EventHub's default port is 5883. Use the npm configuration variable to change it: 23 | 24 | % npm config set EventHub.port = 8888 25 | 26 | Will change the port the EventHub listens on. Ensure you update your clients to point to this port. 27 | 28 | You can also set the Hub's port using the Hub API as discussed below (make sure you setPort before 'start'ing the Hub!). 29 | 30 | ### Hub Secret 31 | 32 | The Hub has a 'secret' value - a string. Clients who wish to listen for unicast events (see the Unicast section below) must provide this string when connecting to the Hub. Currently this value can only be configured by the 'setSecret' method in the Hub API and should be set BEFORE 'start'ing the Hub! 33 | 34 | Clients append the 'token' query string parameter to the connect URL like so: 35 | 36 | #### NodeJS 37 | 38 | var EventHubClient = require('EventHub/clients/server/eventClient') 39 | , client = EventHubClient.getClientHub('http://HubHost:HubPort?token=SECRET_VALUE') 40 | ; 41 | 42 | #### YUI3 43 | 44 | var hub = new Y.EventHub(io, 'http://HubHost:HubPort?token=SECRET_VALUE'); 45 | 46 | #### jQuery 47 | 48 | var hub = new $.fn.eventHub(io, 'http://HubHost:HubPort?token=SECRET_VALUE'); 49 | 50 | The Hub 51 | ------- 52 | 53 | The hub itself is hub/hub.js but is most easily started by executing hub/eventHub.js. This NodeJS implementation will listen on a given port for connections. You can run this on any host/port, just tell each client where this thing is running and they will connect to it. 54 | 55 | ### The Hub API 56 | 57 | To fiddle with the Hub yourself programmatically first require it: 58 | 59 | Hub = require('EventHub/hub/hub'); 60 | 61 | Methods available are: 62 | 63 | Hub.start(); 64 | 65 | Hub.shutdown(); // BEWARE currently Hub make take up to 20 seconds to shutdown! 66 | 67 | Hub.getPort(); 68 | 69 | Hub.setPort(port); // Must call before start! 70 | 71 | Hub.getSecret(); // MUST call before start! 72 | 73 | Hub.setSecret(secret); // SHOULD call before start! 74 | 75 | The Clients 76 | ----------- 77 | 78 | ### Browser 79 | 80 | #### YUI3 81 | 82 | A YUI3 module called 'EventHub' in clients/browsers/yui3.js provides a client-side module for the EventHub. Look in examples/browser for a test drive. You must first start the eventHub, then set up some HTML like so: 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 116 | 117 | 118 | 119 | Note the <HUB HOST>:<HUB PORT> to connect to your running EventHub. This loads in YUI3, then socket.io, and finally the YUI3 EventHub client library (yui3.js). Simply instantiate a new Y.EventHub, and when it's ready you're ready. 120 | 121 | This example requires a server to server this HTML the socket.io library, and the client hub code, which is simply: 122 | 123 | var connect = require('connect') 124 | , server = connect.createServer( 125 | connect.logger() 126 | , connect.static(__dirname + '/../..') 127 | , connect.static(__dirname) 128 | ) 129 | , io = require('socket.io').listen(server) 130 | , port = 8888 131 | ; 132 | 133 | server.listen(port); 134 | console.log('Listening on port ' + port); 135 | 136 | This uses the Connect framework to serve the static files (the HTML, the socket.io library, and the yui3 event hub module. 137 | 138 | For this to all work. Point your browser at this host, port 8888, and the example will load. Finally you will also need a listener for the 'click' event this example fires. That is in server example directory and explained next. 139 | 140 | #### jQuery 141 | 142 | the jQuery client is used very similarly to the YUI3 client: 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 177 | 178 | 179 | 180 | Loading up jQuery, socket.io, and the jQuery EventHub client and bind to the button's click event. When the hub is ready you can 'trigger' events on it and 'bind' to events from it. I'm no jQuery expert so I hope this is sane for you jQuery people! 181 | 182 | ### NodeJS 183 | 184 | So we can serve the event hub library, the socket.io library, and some example HTML to fire a 'click' event when you click on the button. Finally we need a listener to do something, an example of a server-side event hub client does just that. After the EventHub is started, run examples/nodejs/client.js 185 | 186 | % node examples/nodejs/client.js 187 | 188 | The client will connect to the specified EventHub and uses the clients/server/eventClient.js NodeJS module to handle all EventHub interaction. It listens for a 'click' event and excercises the callback to return data back to the event emitter. 189 | 190 | var eventHub = require('EventHub/clients/server/eventClient.js').getClientHub('http://localhost:5883'); 191 | 192 | eventHub.on('eventHubReady', function() { console.log("EventHub ready!"); }); 193 | eventHub.on('click', function(data, callback) { 194 | console.log('GOT A CLICK Event'); 195 | console.log(data); 196 | callback('howdy from server', { mark: 'trostler' }); 197 | }); 198 | 199 | Putting It All Together 200 | ----------------------- 201 | 202 | So you start the EventHub, start the example server, and start the example NodeJS client. Go to your browser and point it at the example server and click the button. An event will propagate from the browser, to the EventHub, and finally to the NodeJS client. The NodeJS client will call the event's callback which will execute back in the browser. Simple! 203 | 204 | You've not got an awesome event-based architecture so go wild! 205 | 206 | ## Event Metadata 207 | 208 | You can attach metadata to the 'on' or 'bind' eventHub method which will signal the hub to act like a switch for these messages. 209 | 210 | hub.on('eventName', callback, { type: 'unicast' }); 211 | 212 | NOTE the 'on' method is asynchronous itself as it must communicate with the remote Hub to register the event. 213 | 214 | ### Unicast 215 | 216 | If { type: 'unicast' } is specified as the metadata (the only thing currently supported) each time a new listener comes online with this metadata the event hub will ONLY pass the named event to ONLY this listener. 217 | 218 | ONLY AUTHETNICATED CLIENTS CAN LISTEN FOR UNICAST EVENTS! See above for how Authentication works. 219 | 220 | Upon specifying { type: 'unicast' } you can/should also specify a callback to determine the result of your attempted event registration. Currently the only possible error is if you attempt to register for unicast event but your client is not authenticated. That all looks like this: 221 | 222 | hub.on('myunicastevent', function() {}, { type: 'unicast', cb: function(error) { 223 | if (error) { 224 | // I was not able to register my listener for this event! 225 | // 'error' is an OBJECT with a single property: 'error' - which is a string 226 | // console.log('unicast registration failed beacuse: ' + error.error); 227 | } else { 228 | // all is cool & my event is now registered to me 229 | } 230 | }); 231 | 232 | The 'cb' property is a function which has a single 'error' parameter - if that parameter is non-null Houston you've got a problem (currently it is a string that is 'Not Authorized'). 233 | 234 | When another client successfully registers for a unicast event the current unicast listener will receive a special event 'eventHub:done' to notify it that it will no longer receive events of that type. 235 | 236 | hub.on('eventClient:done', function(eventName) { 237 | console.log('I will no longer receive ' + eventName + ' events!'); 238 | }); 239 | 240 | Note the event hub itself emits that event. 241 | 242 | This isi the time to finish up any event handling the listener is currently doing & then perhaps exit. Makes deploying a breeze! 243 | 244 | This aids deployment by allowing safe shutdown of older modules/handlers and bring up of new without dropping any events 245 | 246 | Examples of this kind of event are authentication and session management. 247 | 248 | ### Multicast 249 | 250 | Multicast events allow the event listener(s) to specify the secret necessary to listen for events. The first listener to register for an event sets the event password. All subsequent listeners must supply the same password to successfully listens for those events. 251 | 252 | BE AWARE THAT ONLY ONE LISTENER SHOULD ACTUALLY RESPOND TO THE EVENT!!! 253 | 254 | Multicast events are useful for non-responding cross-cutting concerns such as logging, profiling, and the like. This essentially enables non-responding cross-cutting concers for unicast event listeners without effecting the original listener. 255 | 256 | So if you want to log or profile an event you can hook up a generic logger or profiler for an event without effecting the original listener for that event. It looks like this: 257 | 258 | hub.on('eventName', callback, { type: 'multicast', secret: 'mysecret', cb: function(err) { ... } }); 259 | 260 | Anyone else can listen for this event if they provide the same secret when registering. You can verify you listened successfuly by supplying a 'cb' parameter to the event metadata exactly like the 'unicast' case: 261 | 262 | hub.on('eventName', callback, { type: 'multicast', secret: 'mysecret', function(err) { 263 | if (!err) { 264 | // all is good 265 | } else { 266 | // probably supplied the wrong password! 267 | console.log('Multicast registration failed: ' + error.error); 268 | } 269 | } 270 | }); 271 | 272 | Now when 'eventName' is fired every multicast listener will receive the message. ONLY ONE LISTENER CAN RESPOND if a response is expected! 273 | 274 | ### Broadcast 275 | 276 | Not specifying { type: 'unicast' } means you are listening for a broadcasted event. You do not need any authorization to listen for a broadcasted event. You probably do NOT want to use any callbacks for broadcasted events. Examples of this kind of event are logging and presence notifications. 277 | 278 | Converting Existing Code and Testing 279 | ------------------------------------ 280 | 281 | To ease testing and pre-Hub code conversion an optional 'makeListener' method is provided by the Hub which lets you turn any 'ordinary' function into an event listener. Now you can convert and test your function(s) in isolation from the Hub infratstructure further minimizing testing dependencies. It works like this: 282 | 283 | // No Hub dependencies/knowledge here... Just a plain old regular function 284 | function worker(data) { 285 | var ret = {}; 286 | if (data.foo) { 287 | ret.moo = "goo"; 288 | } else { 289 | throw { message: "Did not get foo!" }; 290 | } 291 | } 292 | 293 | The worker function only takes the same 'data' hash present in a normal listener but instead of also accepting a 'callback' parameter this function simply returns the result if there was no error or 'throws' an object on error. This function can be tested very easily without any reference to any Hub infrastructure: 294 | 295 | // Look ma no mention of Event Hubs! 296 | function testWorkerGoodInput { 297 | var result1 = worker({ foo: 'howdy' }); 298 | assert(result.moo === 'goo'); // works fine! 299 | } 300 | 301 | // Look ma no mention of Event Hubs! 302 | function testWorkerBadInput { 303 | try { 304 | var result1 = worker({}); 305 | assert(false); 306 | } catch(e) { 307 | assert(e.message === "Did not get foo!"); 308 | } 309 | } 310 | 311 | Now to 'convert' this function to an event handler for the Hub is trivial: 312 | 313 | eventHub.on('eventHubReady', function() { 314 | eventHub.on('click', eventHub.makeListener(worker), { type: 'unicast' }); 315 | }); 316 | 317 | Using the eventHub.makeListener method the 'worker' function will now be wrapped by code that will call the Hub-supplied callback correctly: callback(null, <retvalue>) on success and callback(<error value>) if your function throws a value. 318 | 319 | The original function does not reference the Hub infrastructure at all your converting and testing are made that much simpler. 320 | 321 | 322 | --------------------------------------------------------------------------------