├── .gitignore ├── .travis.yml ├── README.md ├── examples ├── 0_basic │ ├── README.md │ ├── client.js │ └── server.js ├── 1_resource-events │ ├── README.md │ ├── client.js │ ├── creature.js │ └── server.js ├── 2_authorization │ ├── client.js │ └── server.js ├── 3_mesh │ ├── README.md │ └── node.js └── 4_browser │ └── server.js ├── index.js ├── lib ├── Mesh.js ├── browser │ ├── index.js │ └── shimMesh.js ├── client │ ├── connect.js │ └── uplink.js └── server │ ├── downlink.js │ ├── listen.js │ ├── public │ ├── index.html │ ├── jquery.js │ └── mesh.js │ └── start.js ├── package.json └── test ├── basic-tests.js ├── client-server-test.js ├── server-client-test.js └── siblings-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.11 4 | notifications: 5 | email: 6 | - travis@marak.com 7 | irc: "irc.freenode.org#big" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mesh 2 | 3 | provides a distributed event emitter mesh 4 | 5 | 6 | 7 | ## Features 8 | 9 | - Distributed WebSocket event emitter 10 | - Intelligent event broadcasting among mesh 11 | - Auto-detection of role as `server` or `client` 12 | - Powered by `engine.io` 13 | 14 | 15 | ## Examples 16 | 17 | See: `./examples` folder 18 | 19 | ### Basic Mesh 20 | 21 | This example can be run N times in order to create a mesh of event emitting nodes. 22 | 23 | `myEvent` is added on each node and also emitted on a timer. 24 | 25 | Every node will then recieve `myEvent` from other nodes. 26 | 27 | ```js 28 | mesh.start({ port: 8888 }, function (err) { 29 | 30 | if (err) { 31 | throw err; 32 | } 33 | 34 | mesh.emitter.on('myEvent', function(data){ 35 | console.log('mesh ' + this.event + " - " + data.pid); 36 | }); 37 | 38 | setInterval(function(){a 39 | mesh.emitter.emit('myEvent', { "pid": process.pid }); 40 | }, 1000); 41 | 42 | }); 43 | ``` 44 | 45 | Explicit `server` / `client` can be created using `mesh.listen` and `mesh.connect`. 46 | 47 | For more examples see: `./examples` folder 48 | 49 | ### Browser Usage 50 | 51 | For browser examples see: `./examples/4_browser/` folder 52 | 53 | ### Frequent Concerns 54 | 55 | **I can't get my nodes to receive custom events!** 56 | 57 | Did you bind an event listener using `mesh.emitter.on`? Events **must** be bound in order to be seen on the mesh. 58 | 59 | **How can I listen for events using `mesh.emitter.onAny`?** 60 | 61 | By design, `mesh.emitter.onAny` will not receive remote events. This is in order to keep network traffic to a minimum. 62 | 63 | **How are events broadcasted among the mesh?** 64 | 65 | `mesh` uses a [star topography](http://en.wikipedia.org/wiki/Star_network). The first node is the server and all subsequent nodes are clients. 66 | 67 | All nodes in the mesh are eligible for receiving events from any other node. 68 | 69 | 70 | 71 | - Any events emitted on a `client` will be recieved on the `server` 72 | - Provided the `server` has a listener for that event 73 | - Any events emitted on the `server` will re-broadcast to all `clients` 74 | - Provided that `client` has a listener for that event 75 | - Any events recieved on the `server` will re-broadcast to all `clients` 76 | - Excluding the original `client` sender 77 | - Provided that `client` has a listener for that event 78 | 79 | **How is network traffic kept low if all nodes are in communication?** 80 | 81 | An event map of every node is kept and exchanged on connection with the mesh. Events are then only broadcasted to a node if that event name has been previously registered. 82 | 83 | **Can I bind new live events after a connection is made to the mesh?** 84 | 85 | Yes! Live events are fully supported. This also means you can interact with the mesh in real-time using a repl. 86 | 87 | **Is it possible to get a remote callback for emitted mesh events?** 88 | 89 | The mesh does not support remote callbacks. The overhead of safely enabling remote callbacks requires `V8::Persistent` and the use of `MakeWeak` method through `node-weak`. Since node core doesn't have access to WeakMaps yet it's somewhat cumbersome to use them. 90 | 91 | Apart from these minor technical issues, using remote callbacks is not a great design choice for distributed applications. In the real world actors will crash and message confirmations may get lost ( even though the event was received and fired on the receiving node ). It is generally a better design choice to stick with an event emitter pattern with no built in confirmations. This same functionality of a remote callback can be achieved using two separate named events and a unique message identifier. 92 | 93 | 94 | 95 | ## Tests 96 | 97 | ```bash 98 | npm test 99 | ``` 100 | -------------------------------------------------------------------------------- /examples/0_basic/README.md: -------------------------------------------------------------------------------- 1 | # Basic 2 | 3 | This will setup a client node and server node. 4 | 5 | Event are broadcasted directly on the `mesh.emitter` Event Emitter. -------------------------------------------------------------------------------- /examples/0_basic/client.js: -------------------------------------------------------------------------------- 1 | var mesh = require('../../'); 2 | var resource = require('resource'); 3 | var creature = resource.define('creature'); 4 | 5 | require('colors') 6 | /* 7 | creature.method('talk', function(options, cb){ 8 | console.log('creature talked', options.text.green) 9 | cb(null, options.text); 10 | }); 11 | */ 12 | 13 | mesh.connect({ port: 7777, name: "test-client" }, function(err){ 14 | 15 | if (err) { 16 | console.log('error connecting to mesh server. most likely the server is not running.', err); 17 | process.exit(); 18 | } 19 | 20 | setInterval(function(){ 21 | mesh.emitter.emit('client-foo'); 22 | }, 2000); 23 | 24 | mesh.emitter.on("server-foo", function(data){ 25 | console.log('got server-foo event'.green, data); 26 | }) 27 | 28 | mesh.emitter.on("server-echo-client-foo", function(data){ 29 | console.log('got server-echo-client-foo event'.green, data); 30 | }) 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /examples/0_basic/server.js: -------------------------------------------------------------------------------- 1 | var resource = require('resource'), 2 | http = require('resource-http'), 3 | mesh = require('../../'); 4 | 5 | require('colors'); 6 | 7 | mesh.listen({ port: 7777 }, function (err){ 8 | 9 | if (err) { 10 | console.log('error starting to mesh server.', err); 11 | process.exit(); 12 | } 13 | 14 | setInterval(function(){ 15 | mesh.emitter.emit('server-foo', 'i am server ' + process.pid); 16 | }, 5000); 17 | 18 | mesh.emitter.on('client-foo', function(data){ 19 | console.log('got client foo event'); 20 | mesh.emitter.emit('server-echo-client-foo', 'hello'); 21 | }) 22 | 23 | 24 | mesh.emitter.on('hello', function(data){ 25 | console.log('hello', data) 26 | }) 27 | 28 | 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /examples/1_resource-events/README.md: -------------------------------------------------------------------------------- 1 | # Resource Events 2 | 3 | This will setup a client node and server node where the server has a public resource enabled to remote. 4 | 5 | -------------------------------------------------------------------------------- /examples/1_resource-events/client.js: -------------------------------------------------------------------------------- 1 | var mesh = require('../../').mesh; 2 | var resource = require('resource'); 3 | // var creature = require('./creature').creature; 4 | 5 | require('colors') 6 | 7 | mesh.connect({ port: 7777, name: "test-client" }, function(err){ 8 | 9 | if (err) { 10 | console.log('error connecting to mesh server. most likely the server is not running.', err); 11 | process.exit(); 12 | } 13 | 14 | setInterval(function(){ 15 | console.log('emitting creature::talk') 16 | mesh.emitter.emit('creature::talk', { text: "Hi!" }); 17 | }, 2000); 18 | 19 | }); -------------------------------------------------------------------------------- /examples/1_resource-events/creature.js: -------------------------------------------------------------------------------- 1 | var resource = require('resource'); 2 | 3 | var creature = resource.define('creature'); 4 | // TODO: make remote a getter / setter that updates event table 5 | creature.remote = true; 6 | 7 | // TODO: add granular controls for remote methods 8 | // so that entire resource isn't exposed 9 | creature.method('talk', function(options, callback){ 10 | console.log('talking', options) 11 | callback(null, options.text); 12 | }); 13 | 14 | module['exports'].creature = creature; -------------------------------------------------------------------------------- /examples/1_resource-events/server.js: -------------------------------------------------------------------------------- 1 | var resource = require('resource'), 2 | http = require('resource-http'), 3 | mesh = require('../../').mesh; 4 | 5 | require('colors'); 6 | 7 | 8 | var creature = require('./creature').creature; 9 | 10 | // sets creature resource to be available for remote method calls from the mesh 11 | // creature.remote = true; 12 | 13 | 14 | mesh.listen({ port: 7777 }, function (err){ 15 | 16 | if (err) { 17 | console.log('error starting to mesh server.', err); 18 | process.exit(); 19 | } 20 | 21 | // since creature resource has been set to remote, 22 | /// all creature events are available to the mesh 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /examples/2_authorization/client.js: -------------------------------------------------------------------------------- 1 | var mesh = require('../../').mesh; 2 | var resource = require('resource'); 3 | var http = require('resource-http'); 4 | 5 | require('colors'); 6 | 7 | mesh.connect({ 8 | port: 7777, 9 | name: "test-client", 10 | user: "marak", 11 | password: "password" 12 | }, function(err){ 13 | 14 | if (err) { 15 | console.log('error connecting to mesh server. most likely the server is not running.', err); 16 | process.exit(); 17 | } 18 | 19 | setInterval(function(){ 20 | mesh.emitter.emit('client-foo', { bar: "foo" }); 21 | }, 2000); 22 | 23 | mesh.emitter.on("server-foo", function(data){ 24 | console.log('got server-foo event'.green, data); 25 | }); 26 | 27 | mesh.emitter.on("server-echo-client-foo", function(data){ 28 | console.log('got server-echo-client-foo event'.green, data); 29 | }); 30 | 31 | }); -------------------------------------------------------------------------------- /examples/2_authorization/server.js: -------------------------------------------------------------------------------- 1 | var resource = require('resource'), 2 | http = require('resource-http'), 3 | mesh = require('../../').mesh; 4 | 5 | require('colors'); 6 | 7 | // 8 | // Generic authorize method 9 | // This can replaced with any custom auth function 10 | // 11 | function authorize (user, password, callback) { 12 | var result = false; 13 | if (user === "marak" && password === "password") { 14 | result = true; 15 | } 16 | callback(null, result); 17 | } 18 | 19 | mesh.listen({ 20 | port: 7777, 21 | auth: authorize 22 | }, function (err){ 23 | 24 | if (err) { 25 | throw err; 26 | } 27 | 28 | mesh.emitter.on('client-foo', function (data){ 29 | mesh.emitter.emit('server-echo-client-foo', data); 30 | }); 31 | 32 | setInterval(function(){ 33 | console.log('emitting server-foo message to mesh.emitter'.blue) 34 | mesh.emitter.emit('server-foo', { bar: "foo" }); 35 | }, 2000); 36 | 37 | }); 38 | 39 | -------------------------------------------------------------------------------- /examples/3_mesh/README.md: -------------------------------------------------------------------------------- 1 | # Mesh 2 | 3 | This example uses the auto-detecting ability of `mesh.start` to automatically either connect to an existing mesh, or to start up a new mesh server. -------------------------------------------------------------------------------- /examples/3_mesh/node.js: -------------------------------------------------------------------------------- 1 | var resource = require('resource'), 2 | http = require('resource-http'), 3 | mesh = require('../../index'); 4 | 5 | require('colors'); 6 | 7 | mesh.start({ port: 7777 }, function (err){ 8 | 9 | if (err) { 10 | throw err; 11 | } 12 | 13 | mesh.emitter.on('customEvent', function(data){ 14 | console.log(process.pid + ' - mesh '.yellow + this.event + " - " + data.pid); 15 | }); 16 | 17 | setInterval(function(){ 18 | mesh.emitter.emit('customEvent', { "pid": process.pid }); 19 | }, 1000); 20 | 21 | }); -------------------------------------------------------------------------------- /examples/4_browser/server.js: -------------------------------------------------------------------------------- 1 | var resource = require('resource'), 2 | mesh = require('../../'); 3 | 4 | // 5 | // Remark: See: ./lib/server/public folder for index.html file and mesh.js browser bundle 6 | // 7 | 8 | // 9 | // The mesh has started, visit the WebSocket Gateway at: http://localhost:8888 10 | // 11 | mesh.listen({ port: 8888 }, function (err){ 12 | 13 | if (err) { 14 | console.log('error starting to mesh server.', err); 15 | process.exit(); 16 | } 17 | 18 | mesh.emitter.on('hello', function (data){ 19 | console.log('Hello ', data); 20 | }); 21 | 22 | setInterval(function(){ 23 | mesh.emitter.emit('hello', 'i am server ' + process.pid); 24 | }, 5000); 25 | 26 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var resource = require('resource'), 2 | mesh = resource.define('mesh'); 3 | 4 | var Mesh = require('./lib/Mesh'); 5 | 6 | mesh.method('listen', listen); 7 | mesh.method('connect', connect); 8 | mesh.method('start', start); 9 | 10 | function listen (opts, cb) { 11 | var _mesh = new Mesh(); 12 | mesh.emitter = _mesh.emitter; 13 | return _mesh.listen(opts, cb); 14 | }; 15 | 16 | function connect (opts, cb) { 17 | var _mesh = new Mesh(); 18 | mesh.emitter = _mesh.emitter; 19 | return _mesh.connect(opts, cb); 20 | }; 21 | 22 | function start (opts, cb) { 23 | var _mesh = new Mesh(); 24 | mesh.emitter = _mesh.emitter; 25 | return _mesh.start(opts, cb); 26 | }; 27 | 28 | 29 | module['exports'] = mesh; -------------------------------------------------------------------------------- /lib/Mesh.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('eventemitter2').EventEmitter2; 2 | 3 | var resource = require('resource'); 4 | 5 | // class for constructing new Mesh instances 6 | function Mesh (opts) { 7 | 8 | var self = this; 9 | 10 | opts = opts || {}; 11 | 12 | self.port = opts.port || 8888; 13 | self.host = opts.host || "localhost"; 14 | 15 | self.mode = "unknown"; 16 | self.autoheal = false; 17 | 18 | self.eventTable = {}; 19 | // 20 | // Any events emitted on this eventEmitter will be broadcast to the mesh 21 | // by listeners added by the uplink and downlink methods 22 | // 23 | self.emitter = new EventEmitter({ 24 | wildcard: true, 25 | delimiter: '::', 26 | maxListeners: 0, 27 | newListener: true 28 | }); 29 | 30 | // Whenever a new listener is added to the mesh.emitter 31 | // add that new event to the resource eventTable namedspaced under "mesh::*" 32 | // 33 | // Remark: mesh.emitter events are considered remote by default 34 | // This means that these events will be available to remote sources unless, 35 | // unless remote property is set to `false` 36 | // 37 | // This is special behavior which only applies to the mesh.emitter. 38 | // All other resource event emitters will add events as NOT remote by default 39 | // see: https://github.com/bigcompany/resource 40 | // 41 | // 42 | self.emitter.on('newListener', function(ev){ 43 | resource.eventTable["mesh::" + ev] = { 44 | remote: true 45 | }; 46 | self.eventTable[ev] = {}; 47 | // 48 | // Everytime the client adds a new mesh event listener, 49 | // resend a handshake containing the updated eventTable 50 | // 51 | if (self.mode === "client") { 52 | self.emitter.emit('handshake'); 53 | } 54 | }); 55 | 56 | }; 57 | 58 | Mesh.prototype.connect = require('./client/connect'); 59 | Mesh.prototype.listen = require('./server/listen'); 60 | 61 | Mesh.prototype.uplink = require('./client/uplink'); 62 | Mesh.prototype.downlink = require('./server/downlink'); 63 | 64 | Mesh.prototype.start = require('./server/start'); 65 | 66 | module['exports'] = Mesh; -------------------------------------------------------------------------------- /lib/browser/index.js: -------------------------------------------------------------------------------- 1 | var resource = require('resource'), 2 | mesh = resource.define('mesh'); 3 | 4 | var Mesh = require('./shimMesh'); 5 | 6 | mesh.method('connect', connect); 7 | mesh.method('start', start); 8 | 9 | function connect (opts, cb) { 10 | var _mesh = new Mesh(); 11 | mesh.emitter = _mesh.emitter; 12 | 13 | return _mesh.connect(opts, cb); 14 | }; 15 | 16 | function start (opts, cb) { 17 | var _mesh = new Mesh(); 18 | mesh.emitter = _mesh.emitter; 19 | return _mesh.start(opts, cb); 20 | }; 21 | 22 | 23 | module['exports'] = mesh; -------------------------------------------------------------------------------- /lib/browser/shimMesh.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('eventemitter2').EventEmitter2; 2 | 3 | var resource = require('resource'); 4 | 5 | // class for constructing new Mesh instances 6 | function Mesh (opts) { 7 | 8 | var self = this; 9 | 10 | opts = opts || {}; 11 | 12 | self.port = opts.port || 8888; 13 | self.host = opts.host || "localhost"; 14 | 15 | self.mode = "unknown"; 16 | self.autoheal = false; 17 | 18 | self.eventTable = {}; 19 | // 20 | // Any events emitted on this eventEmitter will be broadcast to the mesh 21 | // by listeners added by the uplink and downlink methods 22 | // 23 | self.emitter = new EventEmitter({ 24 | wildcard: true, 25 | delimiter: '::', 26 | maxListeners: 0, 27 | newListener: true 28 | }); 29 | 30 | // Whenever a new listener is added to the mesh.emitter 31 | // add that new event to the resource eventTable namedspaced under "mesh::*" 32 | // 33 | // Remark: mesh.emitter events are considered remote by default 34 | // This means that these events will be available to remote sources unless, 35 | // unless remote property is set to `false` 36 | // 37 | // This is special behavior which only applies to the mesh.emitter. 38 | // All other resource event emitters will add events as NOT remote by default 39 | // see: https://github.com/bigcompany/resource 40 | // 41 | // 42 | self.emitter.on('newListener', function(ev){ 43 | resource.eventTable["mesh::" + ev] = { 44 | remote: true 45 | }; 46 | self.eventTable[ev] = {}; 47 | }); 48 | 49 | }; 50 | 51 | Mesh.prototype.connect = require('../client/connect'); 52 | Mesh.prototype.uplink = require('../client/uplink'); 53 | Mesh.prototype.start = require('../client/connect'); 54 | 55 | module['exports'] = Mesh; -------------------------------------------------------------------------------- /lib/client/connect.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('resource::mesh'); 2 | 3 | module['exports'] = function connect (options, callback) { 4 | 5 | debug('Attempting to connect to existing mesh'); 6 | 7 | var mesh = this, 8 | client = require('engine.io-client'); 9 | 10 | mesh.client = new client.Socket({ host: options.host, port: options.port }); 11 | mesh.client.on('error', function (err) { 12 | return callback(err); 13 | }); 14 | 15 | mesh.client.on('open', function(socket){ 16 | debug('Client connected to mesh'); 17 | mesh.mode = "client"; 18 | mesh.uplink(options, callback); 19 | }); 20 | 21 | return mesh; 22 | }; -------------------------------------------------------------------------------- /lib/client/uplink.js: -------------------------------------------------------------------------------- 1 | module['exports'] = function uplink (options, callback) { 2 | var mesh = this; 3 | var resource = require('resource'); 4 | var handler = function (data, broadcast) { 5 | data = data || {}; 6 | 7 | // 8 | // This event was recieved from a remote source, do not rebroadcast it 9 | // 10 | if (broadcast === false){ 11 | return; 12 | } 13 | 14 | mesh.client.send(JSON.stringify({ 15 | event: this.event, 16 | payload: data, 17 | eventTable: resource.eventTable, 18 | headers: { 19 | "auth": { 20 | user: options.user, 21 | password: options.password 22 | } 23 | } 24 | }), function(data){ 25 | // console.log('client send callback', data) 26 | }); 27 | }; 28 | 29 | mesh.emitter.onAny(handler); 30 | 31 | // Emit a handshake after connection to communicate the client's eventTable 32 | // Remark: It is possible that the server might try to send an event to a client which 33 | // should be eligible to recieve an event but has not yet broadcasted its eventTable with the handshake. 34 | // This will cause the client to not recieve the event. 35 | // 36 | mesh.emitter.emit('handshake'); 37 | 38 | // 39 | // Any mesh client events should be rebroadcasted locally, 40 | // but they should not be re-emitted 41 | // 42 | mesh.client.on('message', function(data){ 43 | var msg = JSON.parse(data); 44 | 45 | // 46 | // Emit the event on resource scope 47 | // 48 | resource.emit(msg.event, msg.payload); 49 | 50 | // 51 | // Emit event on mesh.emitter scope with rebroadcast set to false 52 | // This will trigger the event for locale listeners, but not broadcast the event back to the mes 53 | // 54 | mesh.emitter.emit(msg.event, msg.payload, false) 55 | }) 56 | 57 | mesh.client.on('error', function(err) { 58 | console.log('error with client', err) 59 | // mesh.emitter.removeListener(handler); 60 | }); 61 | 62 | mesh.client.on('close', function() { 63 | 64 | // TODO: add autoheal logic 65 | // server has been lost, attempt to heal the network 66 | // we could add reconnect logic here so client attempts to reconnect a few times before assuming total server failure 67 | if(mesh.autoheal && mesh.rank === 0) { 68 | mesh.listen(options, function(){}); 69 | } else { 70 | mesh.connect(options, function(){}) 71 | } 72 | // mesh.emitter.removeListener(handler); 73 | }); 74 | 75 | // 76 | // Continue with information about the newly connected to node 77 | // 78 | callback(null, { 79 | id: options.host + ":" + options.port, 80 | port: options.port, 81 | host: options.host, 82 | status: "connected", 83 | lastSeen: new Date().toString(), 84 | role: "server", 85 | eventTable: resource.eventTable 86 | }); 87 | 88 | }; -------------------------------------------------------------------------------- /lib/server/downlink.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('resource::mesh') 2 | 3 | module['exports'] = function downlink (options, callback) { 4 | 5 | debug('new connection recieved', options.socket.id); 6 | 7 | var http = require('resource-http'), 8 | resource = require('resource'); 9 | 10 | var mesh = this; 11 | mesh.mode = "server"; 12 | 13 | mesh.clients = mesh.clients || {}; 14 | mesh.clientCount = Object.keys(mesh.clients); 15 | 16 | var socket = options.socket; 17 | var handler = function (data, broadcast) { 18 | if (typeof broadcast === "undefined") { 19 | broadcast = true; 20 | } 21 | var self = this, 22 | parts = self.event.split('::'), 23 | ev; 24 | if (parts.length === 2) { 25 | ev = self.event 26 | } else { 27 | ev = "mesh::" + self.event; 28 | } 29 | 30 | function broadcastToClients() { 31 | for (var client in mesh.server.clients) { 32 | // Do not rebroadcast the message back to the client which sent it 33 | if (data && mesh.server.clients[client].id === data.id) { 34 | continue; 35 | } 36 | if (broadcast !== false) { 37 | if (mesh.server.clients // any clients are still connected to the server 38 | && mesh.server.clients[client] // and this client is still connected 39 | && mesh.server.clients[client].eventTable // and this client has a registerd eventTable 40 | && typeof mesh.server.clients[client].eventTable[ev] === "object" // and the recieved event is defined in the client's eventTable 41 | && mesh.server.clients[client].eventTable[ev].remote !== false) { // and that event is set to `remote` 42 | if(socket.id === client) { 43 | mesh.server.clients[client].send(JSON.stringify({ 44 | event: self.event, 45 | payload: data 46 | })); 47 | } else { 48 | } 49 | } else { 50 | // console.log('refusing to send event ' + ev + ' to client, it is not registered', broadcast) 51 | } 52 | } 53 | } 54 | } 55 | broadcastToClients(); 56 | }; 57 | 58 | mesh.emitter.onAny(handler); 59 | 60 | socket.on('message', function(data){ 61 | var msg = JSON.parse(data); 62 | msg.payload.id = socket.id; 63 | // If incoming message has an eventTable, assumes its an updated event table for that client 64 | // and update the server's definition of that client's eventTable 65 | if (typeof msg.eventTable === "object") { 66 | mesh.server.clients[socket.id].eventTable = msg.eventTable; 67 | } 68 | // TODO: better handling of handshake event. copy cached client headers back into message, 69 | // so that headers like username and password don't have to be sent in every message for authorization 70 | /* 71 | if (msg.event === "handshake") { 72 | mesh.server.clients[socket.id].eventTable = msg.payload.eventTable; 73 | } 74 | */ 75 | 76 | // Check if there is an authorize method provided in server options 77 | // If so, don't broadcast any recieved events if authorization fails 78 | if (options.auth) { 79 | options.auth(msg.headers.auth.user, msg.headers.auth.password, function(err, success){ 80 | if(success) { 81 | _emit(); 82 | } else { 83 | // do nothing, bad auth 84 | } 85 | }) 86 | } else { 87 | _emit(); 88 | } 89 | 90 | // 91 | // All incoming messages from the mesh should be broadcasted as local event 92 | // unless the event is not registered in the local eventTable and set to `remote` 93 | // 94 | function _emit () { 95 | // Determine if broadcasted message is enabled as remote resource method locally 96 | var ev = msg.event; 97 | 98 | var parts = ev.split('::'), 99 | ev; 100 | 101 | if (parts.length === 2) { 102 | ev = ev; 103 | } else { 104 | ev = "mesh::" + ev; 105 | } 106 | 107 | if (typeof resource.eventTable[ev] === "object" 108 | && resource.eventTable[ev].remote !== false) { 109 | // if so, emit it 110 | var parts = msg.event.split("::"); 111 | if (parts.length == 2) { 112 | // if the message looks like `creature::talk`, emit it to the resource library emitter 113 | resource.emit(msg.event, msg.payload); 114 | } else { 115 | // if the message is like `talk` emit it to the mesh resource emitter 116 | mesh.emitter.emit(msg.event, msg.payload); 117 | } 118 | } else { 119 | mesh.emitter.emit(msg.event, msg.payload); 120 | } 121 | } 122 | 123 | }); 124 | 125 | socket.on('disconnect', function(data){ 126 | // TODO: do something with this event 127 | // mesh.emitter.removeListener(handler); 128 | }); 129 | 130 | // 131 | // Continue with information about the client 132 | // 133 | callback(null, { 134 | id: socket.id, 135 | lastSeen: new Date().toString(), 136 | role: "client", 137 | status: "connected", 138 | eventTable: resource.eventTable 139 | }); 140 | 141 | }; -------------------------------------------------------------------------------- /lib/server/listen.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('resource::mesh') 2 | 3 | module['exports'] = function listen (options, callback) { 4 | 5 | debug('Attempting to create new server node'); 6 | 7 | var mesh = this, 8 | http = require('resource-http'), 9 | engine = require('engine.io'); 10 | 11 | if (typeof http.app === 'object') { 12 | attach(http.app); 13 | } 14 | else { 15 | options.root = options.root || __dirname + "/public"; 16 | // 17 | // Remark: resource-http autoport must be set to false or resource-mesh autonode funtionality will not work 18 | // 19 | options.autoport = false; 20 | http.listen(options, function (err, app) { 21 | if (err) { 22 | return callback(err); 23 | } 24 | debug('Node is going into server mode.'); 25 | debug('Created websocket gateway at ' + options.host + ":" + options.port); 26 | mesh.mode = "server"; 27 | attach(app); 28 | }); 29 | } 30 | 31 | function attach(app) { 32 | // 33 | // Remark: mesh.server is the same as http.server 34 | // 35 | mesh.server = engine.attach(app.server); 36 | 37 | mesh.server.on('connection', function(socket){ 38 | options.socket = socket; 39 | mesh.downlink(options, function(err, result){ 40 | if (err) { 41 | throw err; 42 | } 43 | }); 44 | }); 45 | 46 | callback(null, mesh.server); 47 | } 48 | return mesh; 49 | }; -------------------------------------------------------------------------------- /lib/server/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |

Mesh WebSocket Gateway

13 | 14 |

You have reached the generic webpage for the mesh's WebSocket Gateway. You can connect to this url using a WebSocket client.

15 | 16 |

For additional documentation visit: http://github.com/bigcompany/mesh 17 | 18 |

Live Example code

19 |
20 | 21 | 22 | 23 | 42 | 43 |

Send a hello message

44 | Hello: 45 | 46 | 47 |

Mesh Activity

48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /lib/server/public/jquery.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcompany/mesh/c31051a799858c4d7a7281ae2a3fd8c6b7d8e20a/lib/server/public/jquery.js -------------------------------------------------------------------------------- /lib/server/start.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('resource::mesh'); 2 | 3 | module['exports'] = function start (options, callback) { 4 | 5 | var mesh = this; 6 | 7 | if (!callback && typeof options === 'function') { 8 | callback = options; 9 | options = {}; 10 | } 11 | 12 | mesh.listen(options, function (err) { 13 | if (err && err.code === 'EADDRINUSE') { 14 | debug('Service already listening on ' + options.port); 15 | return mesh.connect(options, callback); 16 | } 17 | if (callback) { 18 | callback(null, mesh.server); 19 | } 20 | }); 21 | 22 | return mesh; 23 | 24 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resource-mesh", 3 | "version": "0.5.1", 4 | "description": "provides a distributed p2p event emitter mesh", 5 | "keywords": [ 6 | "mesh", 7 | "p2p", 8 | "eventemitter" 9 | ], 10 | "main": "./index.js", 11 | "scripts": { 12 | "test": "tap test" 13 | }, 14 | "dependencies": { 15 | "debug": "*", 16 | "engine.io": "1.4.1", 17 | "engine.io-client": "1.4.1", 18 | "eventemitter2": "*", 19 | "resource": "https://github.com/bigcompany/resource/tarball/master", 20 | "resource-http": "0.5.x" 21 | }, 22 | "devDependencies": { 23 | "tap": "*", 24 | "colors": "*" 25 | } 26 | } -------------------------------------------------------------------------------- /test/basic-tests.js: -------------------------------------------------------------------------------- 1 | var spawn = require('child_process').spawn, 2 | tap = require('tap'), 3 | test = tap.test, 4 | resource = require('resource'), 5 | http = require('resource-http'), 6 | Mesh = require('../lib/Mesh'), 7 | server, 8 | client; 9 | 10 | test("create server", function (t) { 11 | 12 | server = new Mesh(); 13 | 14 | server.emitter.on('client-foo', function (data){ 15 | console.log('server got client-foo', data); 16 | server.emitter.emit('server-echo-client-foo', data); 17 | }); 18 | 19 | setInterval(function(){ 20 | server.emitter.emit('server-foo', { bar: "foo" }); 21 | }, 100); 22 | 23 | 24 | server.listen({ port: 8888 }, function(err){ 25 | t.equal(null, err); 26 | t.end(); 27 | }); 28 | 29 | }); 30 | 31 | test("create client", function (t) { 32 | 33 | client = new Mesh(); 34 | client.connect({ port: 8888 }, function(err){ 35 | t.equal(null, err); 36 | t.end(); 37 | }); 38 | 39 | setInterval(function(){ 40 | client.emitter.emit('client-foo', { bar: "foo" }); 41 | }, 100); 42 | 43 | client.emitter.on("server-foo", function(data){ 44 | console.log('got server-foo event'.green, data); 45 | }) 46 | 47 | client.emitter.on("server-echo-client-foo", function(data){ 48 | console.log('got server-echo-client-foo event'.green, data); 49 | }) 50 | 51 | }); 52 | 53 | test("server can recieve single event from client", function (t) { 54 | 55 | t.plan(1); 56 | 57 | server.emitter.once('client-foo', function (data){ 58 | t.ok(true, 'server got client-foo message'); 59 | }); 60 | /* 61 | client.emitter.once('server-echo-client-foo', function (data) { 62 | t.equal(data.bar, 'foo', 'received server echo'); 63 | }); 64 | 65 | client.emitter.once('server-foo', function (data) { 66 | t.equal(data.bar, 'foo', 'received server message'); 67 | }); 68 | */ 69 | 70 | }); 71 | 72 | 73 | 74 | test("client can recieve an event from server", function (t) { 75 | 76 | t.plan(1); 77 | 78 | client.emitter.once('server-foo', function (data) { 79 | t.equal(data.bar, 'foo', 'received server message'); 80 | }); 81 | 82 | /* 83 | client.emitter.once('server-echo-client-foo', function (data) { 84 | t.equal(data.bar, 'foo', 'received server echo'); 85 | }); 86 | 87 | */ 88 | 89 | }); 90 | 91 | 92 | /* 93 | 94 | test("client send an event and recieve a reply from server", function (t) { 95 | 96 | t.plan(1); 97 | 98 | client.emitter.on('server-echo-client-foo', function (data) { 99 | t.equal(data.bar, 'foo', 'received server echo'); 100 | }); 101 | 102 | }); 103 | 104 | */ 105 | 106 | test("end tests", function (t) { 107 | process.exit(0); 108 | }); -------------------------------------------------------------------------------- /test/client-server-test.js: -------------------------------------------------------------------------------- 1 | var tap = require('tap'), 2 | test = tap.test, 3 | resource = require('resource'), 4 | http = require('resource-http'), 5 | Mesh = require('../lib/Mesh'), 6 | server, 7 | client; 8 | 9 | test("create server", function (t) { 10 | 11 | server = new Mesh(); 12 | 13 | server.listen({ port: 8888 }, function (err){ 14 | t.equal(null, err); 15 | t.end(); 16 | }); 17 | 18 | }); 19 | 20 | test("create client", function (t) { 21 | 22 | t.plan(3); 23 | client = new Mesh(); 24 | 25 | server.emitter.on('hello', function(){ 26 | console.log('!!hello called') 27 | t.ok(true); 28 | }); 29 | 30 | client.connect({ port: 8888 }, function(err){ 31 | t.equal(null, err); 32 | t.ok(true); 33 | client.emitter.emit('hello'); 34 | }); 35 | 36 | }); 37 | 38 | test("end tests", function (t) { 39 | process.exit(0); 40 | }); -------------------------------------------------------------------------------- /test/server-client-test.js: -------------------------------------------------------------------------------- 1 | var tap = require('tap'), 2 | test = tap.test, 3 | resource = require('resource'), 4 | http = require('resource-http'), 5 | Mesh = require('../lib/Mesh'), 6 | server, 7 | client; 8 | 9 | test("create server", function (t) { 10 | 11 | server = new Mesh(); 12 | 13 | server.listen({ port: 8888 }, function(err){ 14 | t.equal(null, err); 15 | t.end(); 16 | }); 17 | 18 | }); 19 | 20 | test("create client", function (t) { 21 | 22 | t.plan(3); 23 | client = new Mesh(); 24 | 25 | client.emitter.on('hello', function(){ 26 | console.log('!!hello called') 27 | t.ok(true); 28 | }); 29 | 30 | client.connect({ port: 8888 }, function(err) { 31 | t.equal(null, err); 32 | t.ok(true); 33 | 34 | setTimeout(function(){ 35 | server.emitter.emit('hello'); 36 | }, 500); 37 | }); 38 | 39 | }); 40 | 41 | test("end tests", function (t) { 42 | process.exit(0); 43 | }); -------------------------------------------------------------------------------- /test/siblings-test.js: -------------------------------------------------------------------------------- 1 | var tap = require('tap'), 2 | test = tap.test, 3 | resource = require('resource'), 4 | http = require('resource-http'), 5 | Mesh = require('../lib/Mesh'), 6 | server, 7 | client1, 8 | client2; 9 | 10 | test("create server", function (t) { 11 | 12 | server = new Mesh(); 13 | 14 | server.listen({ port: 8888 }, function(err){ 15 | t.equal(null, err); 16 | t.end(); 17 | }); 18 | 19 | }); 20 | 21 | test("create client 1", function (t) { 22 | 23 | client1 = new Mesh(); 24 | 25 | client1.connect({ port: 8888 }, function(err){ 26 | t.equal(null, err); 27 | t.end(); 28 | }); 29 | 30 | }); 31 | 32 | test("create client 2", function (t) { 33 | 34 | client2 = new Mesh(); 35 | 36 | client2.connect({ port: 8888 }, function(err){ 37 | t.equal(null, err); 38 | t.end(); 39 | }); 40 | 41 | }); 42 | 43 | test("add an event to client 1 emit that event from client 2", function (t) { 44 | 45 | client1.emitter.on('hello', function(){ 46 | console.log('client- hello called') 47 | t.ok(true) 48 | t.end(); 49 | }); 50 | 51 | setTimeout(function(){ 52 | client2.emitter.emit('hello', 'there'); 53 | }, 100) 54 | 55 | }); 56 | 57 | 58 | test("end tests", function (t) { 59 | process.exit(0); 60 | }); 61 | 62 | --------------------------------------------------------------------------------