├── example ├── package.json ├── chatInterface.js └── app.js ├── tests ├── test.js ├── messagetest.js └── updateInfoTest.js ├── package.json ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md └── lib └── AnyMesh.js /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anyMesh-example", 3 | "version": "0.0.2", 4 | "dependencies": { 5 | "blessed": "*", 6 | "lodash": "*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | var AnyMesh = require("../lib/AnyMesh"); 2 | 3 | var left; 4 | var right; 5 | 6 | exports.tearDown = function (callback){ 7 | right.stop(); 8 | left.stop(); 9 | console.log("tore it down"); 10 | callback(); 11 | } 12 | 13 | 14 | exports.testConnect = function(test){ 15 | test.expect(1); 16 | left = new AnyMesh(); 17 | right = new AnyMesh(); 18 | 19 | left.connectedTo = function(info) { 20 | console.log("test connected to " + info.name); 21 | test.ok(true, "connection made!"); 22 | test.done(); 23 | }; 24 | 25 | left.connect('left', ['odd']); 26 | right.connect('right', ['even']); 27 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "anymesh", 3 | "version" : "0.4.0", 4 | "author" : { "name": "Dave Paul", "email": "davepaul0@gmail.com"}, 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/AnyMesh/anyMesh-Node.git" 8 | }, 9 | "licenses": [ 10 | { 11 | "type": "MIT" 12 | } 13 | ], 14 | "main": "lib/AnyMesh.js", 15 | "description" : "A multi-platform, decentralized, auto-discover and auto-connect mesh networking and messaging API", 16 | "keywords" : [ 17 | "mesh", 18 | "network", 19 | "decentralized", 20 | "messaging" 21 | ], 22 | "dependencies" : { 23 | "ip": "*", 24 | "lodash": "*", 25 | "carrier": "*" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/.name 2 | .idea/AnyShow.iml 3 | .idea/encodings.xml 4 | .idea/misc.xml 5 | .idea/modules.xml 6 | .idea/scopes/scope_settings.xml 7 | .idea/vcs.xml 8 | .idea/workspace.xml 9 | Node-App/node_modules/json-socket/.idea/.name 10 | Node-App/node_modules/json-socket/.idea/encodings.xml 11 | Node-App/node_modules/json-socket/.idea/inspectionProfiles/Project_Default.xml 12 | Node-App/node_modules/json-socket/.idea/inspectionProfiles/profiles_settings.xml 13 | Node-App/node_modules/json-socket/.idea/libraries/sass_stdlib.xml 14 | Node-App/node_modules/json-socket/.idea/misc.xml 15 | Node-App/node_modules/json-socket/.idea/modules.xml 16 | Node-App/node_modules/json-socket/.idea/node-json-socket.iml 17 | Node-App/node_modules/json-socket/.idea/scopes/scope_settings.xml 18 | Node-App/node_modules/json-socket/.idea/vcs.xml 19 | Node-App/node_modules/json-socket/.idea/workspace.xml 20 | node_modules 21 | npm-debug.log 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Current Version: 0.4.0 2 | ## Compatible with Node and Python from 0.4.x up 3 | ## NOT Compatible with Node and Python releases before 0.4.0 4 | 5 | ## New in 0.4.0 11/04/2014 6 | * Communication schema has been updated again, for simplified discovery and connection process. 7 | * New schema is incompatible with previous releases. 8 | 9 | ## New in 0.3.0 7/13/2014 10 | * New communication protocol allows multiple anyMesh instances on the same IP Address, and even multiple instance within the same program! 11 | * Instances can update their subscriptions without having to disconnect. 12 | * Added Unit Tests. Install 'nodeunit' globally with NPM to run tests. 13 | ### Known Issues 14 | * disconnectFrom() callback will sometimes fire when 2 AnyMesh instances do not have a valid connection. 15 | * UpdateInfo Test will fail intermittently when run consecutively after other tests. 16 | 17 | 18 | ## New in 0.2.0 19 | * New demo app, using Blessed library 20 | * added "getConnections()" method to AnyMesh 21 | * converted to lodash library 22 | * anymesh callbacks are now cloning parameters for operation safety. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 AnyMesh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /tests/messagetest.js: -------------------------------------------------------------------------------- 1 | var AnyMesh = require("../lib/AnyMesh"); 2 | 3 | var leftMesh; 4 | var rightMesh; 5 | 6 | exports.tearDown = function (callback){ 7 | rightMesh.stop(); 8 | leftMesh.stop(); 9 | console.log("tore it down"); 10 | callback(); 11 | } 12 | 13 | exports.testMessage = function(test){ 14 | test.expect(1); 15 | console.log("message test - two instances need to send and receive 1 request."); 16 | 17 | leftMesh = new AnyMesh(); 18 | leftMesh.networkID = "message"; 19 | leftMesh.connectedTo = function(info) { 20 | console.log('left connected to ' + info.name); 21 | }; 22 | leftMesh.disconnectedFrom = function(name) { 23 | 24 | }; 25 | leftMesh.received = function(message) { 26 | console.log('leftMesh received ' + message.data.msg); 27 | leftMesh.request('right',{msg:'hey yourself!'}); 28 | }; 29 | leftMesh.connect("left", ["global", "leftevents"]); 30 | 31 | 32 | rightMesh = new AnyMesh(); 33 | rightMesh.networkID = "message"; 34 | rightMesh.connectedTo = function(info) { 35 | console.log('right connected to ' + info.name); 36 | rightMesh.request('left', {msg:'hey lefty!'}); 37 | } 38 | rightMesh.disconnectedFrom = function(name) { 39 | 40 | }; 41 | rightMesh.received = function(message) { 42 | console.log('rightMesh received ' + message.data.msg); 43 | test.ok(true, "both instances received messsages"); 44 | test.done(); 45 | }; 46 | rightMesh.connect("right", ["global", "rightevents"]); 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /tests/updateInfoTest.js: -------------------------------------------------------------------------------- 1 | var AnyMesh = require("../lib/AnyMesh"); 2 | 3 | var sender; 4 | var receiver; 5 | 6 | var senderConnected = false; 7 | var receiverConnected = false; 8 | 9 | exports.tearDown = function (callback){ 10 | sender.stop(); 11 | receiver.stop(); 12 | console.log("tore it down"); 13 | callback(); 14 | } 15 | 16 | exports.testUpdateInfo = function(test){ 17 | test.expect(1); 18 | console.log("testing update info feature."); 19 | 20 | sender = new AnyMesh(); 21 | sender.networkID = "update"; 22 | sender.connectedTo = function(info) { 23 | console.log('sender connected to ' + info.name); 24 | if(senderConnected) test.ok(false, 'duplicate connection!'); 25 | senderConnected = true; 26 | startMessaging(); 27 | }; 28 | sender.disconnectedFrom = function(name) { 29 | console.log('sender disconnected from ' + name); 30 | if (senderConnected == false) test.ok(false, 'duplicate disconnect!'); 31 | senderConnected = false; 32 | }; 33 | sender.received = function(message) { 34 | console.log('sender received ' + message.data); 35 | 36 | }; 37 | sender.updatedSubscriptions = function(subscriptions, name) { 38 | console.log('sender received update'); 39 | //test.ok(subscriptions[0] == "end", "new subscription keywork should be 'end'"); 40 | sender.publish("end", {'index':2}); 41 | } 42 | sender.connect("sender", []); 43 | 44 | 45 | receiver = new AnyMesh(); 46 | receiver.networkID = "update"; 47 | receiver.connectedTo = function(info) { 48 | console.log('receiver connected to ' + info.name); 49 | if(receiverConnected) test.ok(false, 'duplicate connection!'); 50 | receiverConnected = true; 51 | startMessaging(); 52 | } 53 | receiver.disconnectedFrom = function(name) { 54 | console.log('receiver disconnected from ' + name); 55 | if (receiverConnected == false) test.ok(false, 'duplicate disconnect!'); 56 | 57 | receiverConnected = false; 58 | }; 59 | receiver.received = function(message) { 60 | console.log('receiver received ' + message.data.index); 61 | if(message.data.index == 1) { 62 | console.log("receiver updating its subscriptions"); 63 | receiver.updateSubscriptions(['end']); 64 | } 65 | else if(message.data.index == 2) { 66 | test.ok(true, 'received message with new subscription keyword'); 67 | test.done(); 68 | } 69 | 70 | }; 71 | receiver.connect("receiver", ["start"]); 72 | }; 73 | 74 | function startMessaging() { 75 | if (senderConnected && receiverConnected) { 76 | sender.publish("start", {'index':1}); 77 | } 78 | } -------------------------------------------------------------------------------- /example/chatInterface.js: -------------------------------------------------------------------------------- 1 | var blessed = require("blessed"); 2 | var _ = require("lodash"); 3 | 4 | var chatInterface = {}; 5 | chatInterface.setupBoxOffset = 3; 6 | chatInterface.msgBoxOffset = 1; 7 | 8 | chatInterface.getDeviceBox = function() { 9 | return blessed.box({ 10 | top: 'top', left: '80%', width: '20%', height: '100%', content: 'Connected Devices', 11 | scrollable: true, 12 | border: {type: 'line'} 13 | }); 14 | }; 15 | 16 | chatInterface.getMessageBox = function() { 17 | var msgBox = blessed.box({ 18 | top: 'top', left: 'left', width: '80%', height: '85%', 19 | scrollable: true, 20 | border: {type: 'line'} 21 | }); 22 | msgBox.addLine = function(content) { 23 | msgBox.append(blessed.text({ 24 | top: chatInterface.msgBoxOffset, left: '5%', width: '90%', height: 1, content: content 25 | })); 26 | chatInterface.msgBoxOffset++; 27 | msgBox.screen.render(); 28 | }; 29 | return msgBox; 30 | }; 31 | 32 | chatInterface.getInputBox = function() { 33 | var inputBox = blessed.form({ 34 | keys: true, 35 | top: '85%', left: 'left', width: '80%', height: '15%', 36 | border: {type: 'line'} 37 | }); 38 | 39 | inputBox.pubButton = blessed.button({ 40 | keys: true, 41 | top: 3, left: '10%', width: '20%', height: 3, content: 'PUBLISH', 42 | bg: 'blue', 43 | border: {type: 'line'}, 44 | style: { 45 | bg: 'blue', 46 | focus: { 47 | bg: 'red' 48 | } 49 | } 50 | }); 51 | 52 | inputBox.reqButton = blessed.button({ 53 | keys: true, 54 | top: 3, left: '40%', width: '20%', height: 3, content: 'REQUEST', 55 | bg: 'blue', 56 | border: {type: 'line'}, 57 | style: { 58 | bg: 'blue', 59 | focus: { 60 | bg: 'red' 61 | } 62 | } 63 | }); 64 | 65 | inputBox.append(blessed.text({top: 1, left: '2%', width: '19%', height: 1, content: 'Enter a message:'})); 66 | inputBox.append(blessed.text({top: 2, left: '2%', width: '19%', height: 1, content: 'Enter a target:'})); 67 | 68 | inputBox.msgField = blessed.textbox({ 69 | keys: true, 70 | top: 1, left: '20%', width: '70%', height: 1, 71 | style: { 72 | bg: 'blue', 73 | focus: { 74 | bg: 'red' 75 | } 76 | } 77 | }); 78 | inputBox.msgField.on('focus', function(){ 79 | inputBox.msgField.readInput(); 80 | }); 81 | inputBox.targetField = blessed.textbox({ 82 | keys: true, 83 | top: 2, left: '20%', width: '70%', height: 1, 84 | style: { 85 | bg: 'blue', 86 | focus: { 87 | bg: 'red' 88 | } 89 | } 90 | }); 91 | inputBox.targetField.on('focus', function(){ 92 | inputBox.targetField.readInput(); 93 | }); 94 | inputBox.append(inputBox.msgField); 95 | inputBox.append(inputBox.targetField); 96 | inputBox.append(inputBox.reqButton); 97 | inputBox.append(inputBox.pubButton); 98 | return inputBox; 99 | }; 100 | 101 | chatInterface.getSetupBox = function() { 102 | return blessed.form({ 103 | top: 'center', left: 'center', width: '50%', height: '50%', content: 'Enter your device name!', 104 | tags: true, 105 | scrollable: true, 106 | border: {type: 'line'} 107 | }); 108 | }; 109 | 110 | chatInterface.updateDeviceList = function(deviceBox, connections) { 111 | deviceBox.children = []; 112 | 113 | _.each(connections, function(connection, i, connections){ 114 | deviceBox.append(blessed.text({top:3+i, left: '10%', width: '80%', height: 1, content: connection.name})); 115 | }) 116 | deviceBox.screen.render(); 117 | }; 118 | 119 | module.exports = chatInterface; -------------------------------------------------------------------------------- /example/app.js: -------------------------------------------------------------------------------- 1 | var AnyMesh = require("../lib/AnyMesh"); 2 | var blessed = require("blessed"); 3 | var chatInterface = require("./chatInterface"); 4 | 5 | var name; 6 | var subscriptions = []; 7 | 8 | 9 | //Initialize AnyMesh and define CallBacks: 10 | var anyMesh = new AnyMesh(); 11 | anyMesh.connectedTo = function(info) { 12 | chatInterface.updateDeviceList(deviceBox, anyMesh.getConnections()); 13 | msgBox.addLine('Connected to ' + info.name); 14 | }; 15 | anyMesh.disconnectedFrom = function(name) { 16 | chatInterface.updateDeviceList(deviceBox, anyMesh.getConnections()); 17 | msgBox.addLine('Disconnected from ' + name); 18 | }; 19 | anyMesh.received = function(message) { 20 | msgBox.addLine('Message from ' + message.sender); 21 | msgBox.addLine('Message content: ' + message.data.msg); 22 | msgBox.addLine(' '); 23 | }; 24 | 25 | function setupAnyMesh() { 26 | anyMesh.connect(name, subscriptions); 27 | } 28 | 29 | //these are called when a user presses either the publish or request buttons: 30 | function reqButtonPressed() { 31 | var target = inputBox.targetField.value; 32 | var msg = inputBox.msgField.value 33 | anyMesh.request(target, {'msg': msg}); 34 | msgBox.addLine('Sent request to ' + target); 35 | msgBox.addLine('Message content: ' + msg); 36 | msgBox.addLine(' '); 37 | } 38 | function pubButtonPressed() { 39 | var target = inputBox.targetField.value; 40 | var msg = inputBox.msgField.value 41 | anyMesh.publish(target, {'msg': msg}); 42 | msgBox.addLine('Published to keyword: ' + target); 43 | msgBox.addLine('Message content: ' + msg); 44 | msgBox.addLine(' '); 45 | } 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | function addNameInput() { 58 | var nameInput = blessed.textbox({ 59 | top: chatInterface.setupBoxOffset, left: 'center', width: '90%', height: 3 60 | }); 61 | nameInput.on('focus', function(){ 62 | nameInput.readInput(function(){ 63 | name = nameInput.value; 64 | addSubscriptionInput(); 65 | screen.render(); 66 | }) 67 | }); 68 | setupBox.append(nameInput); 69 | chatInterface.setupBoxOffset = chatInterface.setupBoxOffset + 3; 70 | nameInput.focus(); 71 | } 72 | 73 | function addSubscriptionInput() { 74 | var subscriptionInput = blessed.textbox({ 75 | top: chatInterface.setupBoxOffset + 1, left: 'center', width: '90%', height: 3 76 | }); 77 | subscriptionInput.on('focus', function(){ 78 | subscriptionInput.readInput(function(){ 79 | if (subscriptionInput.value.length > 1) { 80 | subscriptions.push(subscriptionInput.value); 81 | addSubscriptionInput(); 82 | screen.render(); 83 | } 84 | }) 85 | }); 86 | subscriptionInput.key('enter', function(ch, key) { 87 | if (subscriptionInput.value.length <= 0) { 88 | setupAnyMesh(); 89 | screen.remove(setupBox); 90 | inputBox.msgField.focus(); 91 | screen.render(); 92 | } 93 | }); 94 | 95 | var labelText = 'Enter a subscription keyword:'; 96 | if(subscriptions.length > 0) labelText = 'Enter another. Press "enter" on a blank line to begin!'; 97 | 98 | var subscriptionLabel = blessed.text({ 99 | top: chatInterface.setupBoxOffset, left: 'center', width: '90%', height: 1, content: labelText 100 | }); 101 | setupBox.append(subscriptionInput); 102 | setupBox.append(subscriptionLabel); 103 | chatInterface.setupBoxOffset = chatInterface.setupBoxOffset + 3; 104 | subscriptionInput.focus(); 105 | } 106 | 107 | var screen = blessed.screen(); 108 | 109 | var msgBox = chatInterface.getMessageBox(); 110 | screen.append(msgBox); 111 | 112 | var inputBox = chatInterface.getInputBox(); 113 | 114 | 115 | inputBox.pubButton.on('press', function() { 116 | pubButtonPressed(); 117 | }); 118 | 119 | inputBox.reqButton.on('press', function() { 120 | reqButtonPressed(); 121 | }); 122 | 123 | 124 | screen.append(inputBox); 125 | 126 | var deviceBox = chatInterface.getDeviceBox(); 127 | screen.append(deviceBox); 128 | 129 | var setupBox = chatInterface.getSetupBox(); 130 | screen.append(setupBox); 131 | 132 | screen.key('escape', function(ch, key) { 133 | return process.exit(0); 134 | }); 135 | 136 | addNameInput(); 137 | screen.render(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NOTE: AnyMesh is no longer under active development or support. 2 | 3 | #AnyMesh 4 | https://github.com/AnyMesh 5 | 6 | 7 | AnyMesh is a multi-platform, decentralized, auto-discovery, auto-connect mesh networking API. 8 | 9 | Current supported platforms: 10 | 11 | * Node.js 12 | * iOS 13 | * Python 14 | 15 | AnyMesh makes it easy to build a decentralized, multi-platform mesh network on any LAN, without having to manually implement TCP / UDP processes. 16 | 17 | All supported platforms work roughly the same way. Configure each AnyMesh instance with 2 properties: 18 | 19 | * Name - a name or identifier for the instance 20 | * Subscriptions - an array of keywords for your instance to listen for 21 | 22 | > AnyMesh will automatically find and connect to other AnyMesh 23 | > instances. 24 | 25 | Then, to communicate across the network, an instance can send two types of messages: 26 | 27 | * Request - send a message to a specific device Name. 28 | * Publish - send a message associated with a keyword. Any other instance that subscribes to the keyword will receive the message. 29 | 30 | That's all there is to it! 31 | ## FAQ 32 | 33 | ### Q: So what is AnyMesh? 34 | A: AnyMesh is a convenient, powerful way to get multiple programs to connect and send information to one another. 35 | Each instance of AnyMesh will automatically find and connect to other instances. AnyMesh instances can be running within the same app, 36 | on separate apps on the same device, or on different devices within the same local network (LAN). 37 | 38 | A network can contain any combination of these relationships, across any languages or platforms - 39 | You may have a Mac OSX desktop computer running 2 instances of AnyMesh-Python alongside 1 instance of AnyMesh-Node. These instances are 40 | also connected to 2 more instances of AnyMesh-Node on a Linux computer down the hall, and a Raspberry Pi running AnyMesh-Python hardwired into the router. 41 | Launch an app on your iPhone that uses AnyMesh-iOS, and instantly connect to all these devices automatically! 42 | 43 | ### Q: Why use AnyMesh instead of RabbitMQ, 0MQ, etc? 44 | A: AnyMesh is certainly not the first mesh networking API on the block. But AnyMesh was created with a few unique purposes in mind that sets it apart 45 | from other libraries: 46 | 47 | * AnyMesh is truly decentralized - Even at the lowest levels of the TCP connections, there is NO device acting as any kind of server or relay. 48 | All AnyMesh instances manage their own connections to every other device. This means any device can enter or leave the mesh at any time with ZERO disruption 49 | to any other connections. 50 | * AnyMesh has EXTREMELY minimal setup and configuration - Just name your instance and optionally give it some keywords to subscribe to. There is no need to define roles for instances - 51 | Every instance uses the same simple message distribution pattern. 52 | * AnyMesh is multi-platform - We currently support iOS, Python, and Node. We hope to start work on Java/Android very soon. 53 | 54 | 55 | ### Q: How can I help? 56 | A: AnyMesh is still very young concept, and although it is fully functional, it will be a little while until we reach v.1.0 on all supported 57 | platforms. See the CONTRIBUTE.md file for suggestions on contributing to development. 58 | #AnyMesh Node 59 | ## Please Read: 60 | 11/4/2014 - 0.4.0 has been released! See CHANGELOG for details. 61 | 62 | Install: 63 | 64 | npm install anymesh 65 | 66 | 67 | ##Quickstart: 68 | Create AnyMesh singleton: 69 | 70 | var AnyMesh = require("anymesh"); 71 | var anyMesh = new AnyMesh(); 72 | 73 | Handle messages received by defining callback function: 74 | 75 | anyMesh.received = function(message) { 76 | //message is object containing message info 77 | //including type, sender, target, and data 78 | console.log("message sent by " + message.sender); 79 | console.log(message.data); 80 | } 81 | 82 | Define callbacks for node connects and disconnects if desired: 83 | 84 | anyMesh.connectedTo = function(info) { 85 | console.log('Connected to ' + info.name); 86 | } 87 | anyMesh.disconnectedFrom = function(name) { 88 | console.log('Disconnected from ' + name); 89 | } 90 | 91 | 92 | Activate connectivity! 1st parameter is the name of your anyMesh instance, 2nd parameter is an array of keywords to subscribe to: 93 | 94 | anyMesh.connect("Dave", ["events", "updates"]); 95 | 96 | Send a request: 97 | 98 | anyMesh.request("Bob", {"msg":"Hello Bob", "priority":1}); 99 | 100 | Publish to subscribers of a specific keyword: 101 | 102 | anyMesh.publish("updates", {"update":"new headlines!", "content":[1, 5, 8]}); 103 | 104 | ##More helpful methods: 105 | Get information on current connections: 106 | 107 | connectionArray = anyMesh.getConnections(); 108 | 109 | Update an instance's subscriptions: 110 | 111 | anyMesh.updateSubscriptions(["events", "updates", "weather"]); 112 | 113 | Close connections and stop all network activity: 114 | 115 | anyMesh.stop() 116 | 117 | ##Some optional settings: 118 | 119 | Change your network ID: 120 | 121 | anyMesh.networkID = "upstairsDevices"; 122 | 123 | > AnyMesh instances will only connect to other instances with the same network ID. By setting the ID explicitly, you can have multiple "Meshes" on the same LAN and decide which instances belong to which mesh. The default network ID on all AnyMesh platforms is "anymesh". 124 | 125 | Set a callback for updated subscriptions on the network: 126 | 127 | anyMesh.updatedSubscriptions = function(subscriptions, name) { 128 | //'name' is the name of the anyMesh instance that has updated its subscriptions, 129 | //'subscriptions' is the array of new subscription keywords for that instance. 130 | } 131 | 132 | 133 | 134 | 135 | ## About the Example App 136 | The included demo app uses the 3rd party libraries Underscore and Blessed 137 | run "npm install" in the example folder to install. 138 | 139 | * when you launch the demo, the first thing to do is enter the name you want to use for your anyMesh instance. 140 | * next you can enter the keywords you wish to "subscribe" to. Press ENTER on a blank line to start up anyMesh. 141 | * if anyMesh connects you to other devices, you can send and receive messages! 142 | * sample apps included in the iOS and Python AnyMesh libraries are compatible with this app. View the Changelog to check version compatibility across platforms! 143 | 144 | ## Unit Testing 145 | AnyMesh uses nodeunit for its unit tests. See their README for instructions on how to install and use nodeunit's testrunner: https://github.com/caolan/nodeunit 146 | 147 | ###AnyMesh software is licensed with the MIT License 148 | 149 | ###Any questions, comments, or suggestions, contact the Author: 150 | Dave Paul 151 | davepaul0@gmail.com 152 | -------------------------------------------------------------------------------- /lib/AnyMesh.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var net = require('net'); 3 | var ip = require("ip"); 4 | var dgram=require("dgram"); 5 | var carrier= require("carrier"); 6 | 7 | function AnyMesh() { 8 | this.networkID = "anymesh"; 9 | this.connections = []; 10 | this.discoveryPort = 12345; 11 | this.tcpPort=0; 12 | 13 | //callbacks: 14 | this.received = function(pkg) {}; 15 | this.connectedTo = function(deviceInfo) {}; 16 | this.disconnectedFrom = function(name) {}; 17 | this.updatedSubscriptions = function(subscriptions, name) {}; 18 | 19 | return this; 20 | } 21 | 22 | AnyMesh.MsgTypeGeneral = { 23 | REQUEST : 0, 24 | PUBLISH : 1, 25 | SYSTEM : 2 26 | }; 27 | AnyMesh.MsgTypeSystem = { 28 | SUBSCRIPTIONS : 0 29 | }; 30 | 31 | AnyMesh.prototype.connect = function (name, subscriptions) { 32 | this.name = name; 33 | this.subscriptions = subscriptions; 34 | 35 | if (this.subscriptions==undefined) this.subscriptions = []; 36 | if (!_.isArray(subscriptions)) subscriptions = [subscriptions]; 37 | 38 | this.createUDPServer(); 39 | this.createTCPServer(); 40 | }; 41 | 42 | /************* 43 | //UDP / DISCOVERY 44 | **************/ 45 | 46 | AnyMesh.prototype.createUDPServer = function() { 47 | this.udp_server = dgram.createSocket("udp4"); 48 | this.udp_client = dgram.createSocket("udp4"); 49 | var self = this; 50 | 51 | this.udp_server.on("message", function (msg, rinfo) { 52 | if (self.tcpPort == 0)return; 53 | 54 | var msgArray = msg.toString().split(','); 55 | var msgId = msgArray[0]; 56 | 57 | if(msgId == self.networkID){ 58 | var msgPort = parseInt(msgArray[1]); 59 | var msgName = msgArray[2]; 60 | if (rinfo.address != ip.address() || msgPort != self.tcpPort){ 61 | if (msgName < self.name && !self.socketForName(msgName)) { 62 | self.addConnection(rinfo.address, msgPort, msgName); 63 | } 64 | } 65 | } 66 | }); 67 | 68 | this.udp_server.bind(this.discoveryPort); 69 | this.udp_client.bind(); 70 | }; 71 | 72 | AnyMesh.prototype.startBroadcasting = function() { 73 | var self = this; 74 | this.timerId = setInterval(function () { 75 | var buffer = new Buffer(self.networkID + ',' + self.tcpPort + ',' + self.name); 76 | self.udp_client.setBroadcast(true); 77 | self.udp_client.send(buffer, 0, buffer.length, self.discoveryPort, "255.255.255.255"); 78 | }, 3000); 79 | }; 80 | 81 | 82 | /************* 83 | //TCP / CONNECTING 84 | **************/ 85 | 86 | AnyMesh.prototype.createTCPServer = function() { 87 | var self = this; 88 | this.attemptPort = 12346; 89 | 90 | this.tcp_server = net.createServer(function (socket) { 91 | // Identify this client 92 | socket.ipAddress = socket.remoteAddress; 93 | socket.port = socket.localPort; 94 | socket.info = {}; 95 | 96 | // Put this new client in the list 97 | self.connections.push(socket); 98 | self.sendInfo(socket, false); 99 | // Handle incoming messages from clients. 100 | var my_carrier = carrier.carry(socket); 101 | my_carrier.on('line', function (pkg) { 102 | 103 | var msgObj = JSON.parse(pkg); 104 | if (msgObj.type == AnyMesh.MsgTypeGeneral.SYSTEM) self._processSystemMessage(socket, msgObj); 105 | else self.received(msgObj); 106 | }); 107 | 108 | // Remove the client from the list when it leaves 109 | socket.on('end', function () { 110 | self.connections.splice(self.connections.indexOf(socket), 1); 111 | if(socket.info.name != null)self.disconnectedFrom(socket.info.name); 112 | }); 113 | }); 114 | this.tcp_server.on('listening', function () { 115 | self.tcpPort = self.attemptPort; 116 | self.startBroadcasting(); 117 | }); 118 | 119 | this.tcp_server.on('error', function (e) { 120 | if (e.code == 'EADDRINUSE') { 121 | self.attemptPort++; 122 | self.tcp_server.listen(self.attemptPort, '0.0.0.0'); 123 | } 124 | }); 125 | this.tcp_server.listen(this.attemptPort, '0.0.0.0'); 126 | }; 127 | 128 | 129 | AnyMesh.prototype.addConnection = function (address, port, name) { 130 | //connect to remote tcp server 131 | var self = this; 132 | 133 | if(!this.socketForName(name)) { 134 | var newServer = new net.Socket(); 135 | newServer.info = {}; 136 | newServer.connect(port, address, function() { 137 | newServer.ipAddress = address; 138 | newServer.port = port; 139 | self.connections.push(newServer); 140 | self.sendInfo(newServer, false); 141 | 142 | newServer.on('end', function (){ 143 | self.connections.splice(self.connections.indexOf(newServer), 1); 144 | self.disconnectedFrom(newServer.info.name); 145 | }); 146 | 147 | var my_carrier = carrier.carry(newServer); 148 | my_carrier.on('line', function (pkg) { 149 | var msgObj = JSON.parse(pkg); 150 | if (msgObj.type == AnyMesh.MsgTypeGeneral.SYSTEM) self._processSystemMessage(newServer, msgObj); 151 | else self.received(msgObj); 152 | }); 153 | }); 154 | } 155 | }; 156 | 157 | AnyMesh.prototype._processSystemMessage = function(socket, msgObj) { 158 | var sysData = msgObj.data; 159 | if (sysData.type == AnyMesh.MsgTypeSystem.SUBSCRIPTIONS) { 160 | if (sysData.isUpdate) { 161 | socket.info.subscriptions = sysData.subscriptions; 162 | this.updatedSubscriptions(socket.info.subscriptions, socket.info.name); 163 | } 164 | else { 165 | socket.info.name = msgObj.sender; 166 | socket.info.subscriptions = sysData.subscriptions; 167 | if(socket.info.name != null) this.connectedTo(_.cloneDeep(socket.info)); 168 | } 169 | } 170 | }; 171 | 172 | 173 | 174 | /************* 175 | //MESSAGING 176 | **************/ 177 | 178 | AnyMesh.prototype.publish = function(target, payload) { 179 | var self = this; 180 | _.each(this.connections, function(connection) { 181 | if (_.contains(connection.info.subscriptions, target)) { 182 | var msgObj = {}; 183 | msgObj["type"] = AnyMesh.MsgTypeGeneral.PUBLISH; 184 | msgObj["target"] = target; 185 | msgObj["data"] = payload; 186 | msgObj["sender"] = self.name; 187 | connection.write(JSON.stringify(msgObj) + '\r\n'); 188 | } 189 | }); 190 | }; 191 | 192 | AnyMesh.prototype.request = function(target, payload) { 193 | var msgObj = {}; 194 | msgObj["type"] = AnyMesh.MsgTypeGeneral.REQUEST; 195 | msgObj["target"] = target; 196 | msgObj["data"] = payload; 197 | msgObj["sender"] = this.name; 198 | 199 | var targetSocket = this.socketForName(target); 200 | if (targetSocket != undefined) targetSocket.write(JSON.stringify(msgObj) + '\r\n'); 201 | }; 202 | 203 | 204 | AnyMesh.prototype.updateSubscriptions = function(subscriptions) { 205 | var self = this; 206 | this.subscriptions = subscriptions; 207 | _.forEach(this.connections, function (connection) { 208 | self.sendInfo(connection, true); 209 | }); 210 | }; 211 | 212 | AnyMesh.prototype.sendInfo = function (socket, isUpdate) { 213 | var msgObj = {}; 214 | msgObj.type = AnyMesh.MsgTypeGeneral.SYSTEM; 215 | msgObj.data = {}; 216 | msgObj.data.subscriptions = this.subscriptions; 217 | msgObj.data.isUpdate = isUpdate; 218 | msgObj.data.type = AnyMesh.MsgTypeSystem.SUBSCRIPTIONS; 219 | msgObj.sender = this.name; 220 | msgObj.target = socket.info.name; 221 | socket.write(JSON.stringify(msgObj) + '\r\n'); 222 | }; 223 | 224 | /************* 225 | //STOPPING 226 | **************/ 227 | 228 | AnyMesh.prototype.stop = function() { 229 | this.tcpPort = 0; 230 | clearInterval(this.timerId); 231 | 232 | _.forEach(this.connections, function (connection){ 233 | connection.destroy(); 234 | }); 235 | }; 236 | 237 | /************* 238 | //UTILITY 239 | **************/ 240 | 241 | AnyMesh.prototype.getConnections = function() { 242 | var validConnections = []; 243 | for (var index in this.connections) { 244 | var connection = this.connections[index]; 245 | if (connection.info.name) 246 | { 247 | var devInfo = {"name": connection.info.name, "subscriptions": connection.info.subscriptions}; 248 | validConnections.push(devInfo); 249 | } 250 | } 251 | return _.cloneDeep(validConnections); 252 | }; 253 | 254 | AnyMesh.prototype.socketForName = function(targetName) { 255 | return _.find(this.connections, function(socket){return socket.info.name == targetName}); 256 | }; 257 | 258 | AnyMesh.prototype.report = function(reportText) { 259 | this.received({"target":"this", "type":AnyMesh.MsgTypeGeneral.REQUEST, "data":{"msg":reportText}, "sender":"diagnostics"}); 260 | } 261 | 262 | module.exports = AnyMesh; --------------------------------------------------------------------------------