├── static ├── node_button_logo.png └── dash-button-logo-blank.jpg ├── test ├── lib │ ├── hex.json │ ├── packets.js │ └── pcap.js ├── test-live.js └── test.js ├── .travis.yml ├── .gitignore ├── helpers.js ├── package.json ├── bin └── findbutton ├── index.js └── README.md /static/node_button_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hortinstein/node-dash-button/HEAD/static/node_button_logo.png -------------------------------------------------------------------------------- /test/lib/hex.json: -------------------------------------------------------------------------------- 1 | { "first": "8f:3f:20:33:54:44", 2 | "second": "8f:3f:20:33:54:43", 3 | "third": "8f:3f:20:33:54:42" 4 | } -------------------------------------------------------------------------------- /static/dash-button-logo-blank.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hortinstein/node-dash-button/HEAD/static/dash-button-logo-blank.jpg -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | sudo: true 4 | 5 | addons: 6 | apt: 7 | sources: 8 | - ubuntu-toolchain-r-test 9 | packages: 10 | - libpcap-dev 11 | - gcc-4.9 12 | - g++-4.9 13 | before_install: 14 | - export CC="gcc-4.9" CXX="g++-4.9" 15 | 16 | node_js: 17 | - "4.2" 18 | - "4.1" 19 | - "4.0" 20 | - "0.12" 21 | - "0.11" 22 | - "0.10" 23 | - "node" 24 | - "iojs" 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /test/lib/packets.js: -------------------------------------------------------------------------------- 1 | var hexes = require('./hex'); 2 | module.exports = { 3 | "first": { 4 | 'packet_payload_ethertype': 2054, 5 | 'packet_payload_payload_sender_ha_addr': hexes.first, 6 | }, 7 | "second": { 8 | 'packet_payload_ethertype': 2054, 9 | 'packet_payload_payload_sender_ha_addr': hexes.second, 10 | }, 11 | "third": { 12 | 'packet_payload_ethertype': 2054, 13 | 'packet_payload_payload_sender_ha_addr': hexes.third, 14 | }, 15 | "bad": { 16 | 'packet_payload_ethertype': 666, 17 | 'packet_payload_payload_sender_ha_addr': hexes.first, 18 | }, 19 | "udp": { 20 | 'packet_payload_ethertype': 2048, 21 | 'packet_payload_shost_addr': hexes.first, 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /helpers.js: -------------------------------------------------------------------------------- 1 | // converts a string: "8f:3f:20:33:54:44" 2 | // to a numeric array: [ 143, 63, 32, 51, 84, 68 ] 3 | // for comparison 4 | exports.hex_to_int_array = function(hex){ 5 | var hex_array = hex.split(":"); 6 | var int_array = []; 7 | for (var i in hex_array) { 8 | int_array.push( parseInt(hex_array[i], 16)); 9 | } 10 | //console.log(hex,int_array) 11 | return int_array; 12 | }; 13 | 14 | // converts a numeric array: [ 143, 63, 32, 51, 84, 68 ] 15 | // to a string: "8f:3f:20:33:54:44"= 16 | // for comparison 17 | exports.int_array_to_hex = function (int_array) { 18 | var hex = ""; 19 | for (var i in int_array){ 20 | var h = int_array[i].toString(16); // converting to hex 21 | if (h.length < 2) {h = '0' + h}; //adding a 0 for non 2 digit numbers 22 | if (i !== int_array.length) {hex+=":"}; //adding a : for all but the last group 23 | hex += h; 24 | } 25 | return hex.slice(1);//slice is to get rid of the leading : 26 | }; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-dash-button", 3 | "version": "0.6.1", 4 | "description": "a module for integrating amazon dash button presses into a node project", 5 | "main": "index.js", 6 | "scripts": { 7 | "livetest": "istanbul cover ./node_modules/mocha/bin/_mocha test/test-live.js --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", 8 | "test": "istanbul cover ./node_modules/mocha/bin/_mocha test/test.js --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" 9 | }, 10 | "bin": { 11 | "findbutton": "./bin/findbutton" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/hortinstein/node-dash-button" 16 | }, 17 | "keywords": [ 18 | "amazon", 19 | "amazon-dash", 20 | "dash", 21 | "button", 22 | "arp" 23 | ], 24 | "author": "hortinstein", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/hortinstein/node-dash-button/issues" 28 | }, 29 | "homepage": "https://github.com/hortinstein/node-dash-button", 30 | "dependencies": { 31 | "pcap": "git+https://github.com/mranney/node_pcap.git#d920204745c8b00ef4b7a3fe27d902b263cdb70f", 32 | "underscore": "^1.8.3" 33 | }, 34 | "devDependencies": { 35 | "arpjs": "^1.0.0", 36 | "autochecker": "^0.5.1", 37 | "coveralls": "^2.11.4", 38 | "istanbul": "0.3.22", 39 | "mocha": "^2.3.0", 40 | "mockery": "^1.4.0", 41 | "should": "^7.1.0", 42 | "sinon": "^1.17.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/lib/pcap.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var hex_to_int_array = require('../../helpers.js').hex_to_int_array; 3 | var packets = require('./packets'); 4 | 5 | function pcap(){ 6 | this.session = false; 7 | this.badMode = false; 8 | } 9 | pcap.prototype.createSession = function() { 10 | if (this.badMode === true) { 11 | throw new Error("Error: pcap_findalldevs didn't find any devs [mocked]"); 12 | } 13 | //console.log("sending reference to fake event emitter") 14 | this.session = new events.EventEmitter(); 15 | return this.session; 16 | }; 17 | pcap.prototype.getSession = function() { 18 | return this.session; 19 | }; 20 | pcap.prototype.test = function() { 21 | return 'inject works!'; 22 | }; 23 | pcap.prototype.decode = function() { 24 | }; 25 | pcap.prototype.decode.packet = function(packet) { 26 | if (packet.packet_payload_ethertype === packets.bad.packet_payload_ethertype) { 27 | throw new Error('Catch me!'); 28 | } 29 | var mock_packet = { 30 | "payload": { 31 | "ethertype": packet.packet_payload_ethertype, 32 | "payload": { } 33 | } 34 | }; 35 | 36 | if (packet.packet_payload_ethertype === 2048) { 37 | mock_packet.payload.shost = { 38 | addr: hex_to_int_array(packet.packet_payload_shost_addr) 39 | }; 40 | } 41 | else { 42 | mock_packet.payload.payload.sender_ha = { 43 | addr: hex_to_int_array(packet.packet_payload_payload_sender_ha_addr) 44 | }; 45 | } 46 | 47 | return mock_packet; 48 | }; 49 | pcap.prototype.enableBadMode = function() { 50 | this.badMode = true; 51 | }; 52 | pcap.prototype.disableBadMode = function() { 53 | this.badMode = false; 54 | }; 55 | module.exports = pcap; 56 | -------------------------------------------------------------------------------- /bin/findbutton: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | process.env.NODE_ENV = 'test' 4 | //simple module for finding your button's mac 5 | 6 | var int_array_to_hex = require('../index.js').int_array_to_hex; 7 | var create_session = require('../index.js').create_session; 8 | var manufacturer_directory = require('../stor.js').manufacturer_directory; 9 | var pcap = require('pcap'); 10 | var iface = undefined; 11 | var last_argument = process.argv[process.argv.length - 1]; 12 | if (last_argument.indexOf('findbutton') === -1) { 13 | iface = last_argument; 14 | } 15 | var pcap_session = create_session(iface, 'all'); 16 | 17 | console.log("Watching for arp & udp requests on your local network, please try to press your dash now\nDash buttons should appear as manufactured by 'Amazon Technologies Inc.' "); 18 | 19 | pcap_session.on('packet', function(raw_packet) { 20 | var packet = pcap.decode.packet(raw_packet); //decodes the packet 21 | if(packet.payload.ethertype === 2054 || packet.payload.ethertype === 2048) { //ensures it is an arp or udp packet 22 | var protocol, possible_dash; 23 | if (packet.payload.ethertype === 2054) { 24 | protocol = 'arp'; 25 | possible_dash = packet.payload.payload.sender_ha.addr; //getting the hardware address of the possible dash 26 | } 27 | else { 28 | protocol = 'udp'; 29 | possible_dash = packet.payload.shost.addr; 30 | } 31 | possible_dash = int_array_to_hex(possible_dash); 32 | 33 | var log = 'Possible dash hardware address detected: {0} Manufacturer: {1} Protocol: {2}', 34 | manufacturerKey = possible_dash.slice(0,8).toString().toUpperCase().split(':').join(''), 35 | manufacturer; 36 | 37 | if(manufacturer_directory.hasOwnProperty(manufacturerKey)) { 38 | manufacturer = manufacturer_directory[manufacturerKey]; 39 | } else { 40 | manufacturer = 'unknown'; 41 | } 42 | 43 | console.log(log.replace('{0}', possible_dash).replace('{1}', manufacturer).replace('{2}', protocol)); 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /test/test-live.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | 3 | var should = require('should'); 4 | var dash_button = require('../index.js'); 5 | var pcap = require('pcap'); 6 | var hex = '8f:3f:20:33:54:44'; 7 | var hex2 = '8f:3f:20:33:54:43'; 8 | var hex3 = '8f:3f:20:33:54:42'; 9 | var int_array = []; 10 | 11 | var arp = require('arpjs'); 12 | var sendarp = function(){ 13 | arp.send({ 14 | 'op': 'request', 15 | 'src_ip': '10.105.50.100', 16 | 'dst_ip': '10.105.50.1', 17 | 'src_mac': hex, 18 | 'dst_mac': 'ff:ff:ff:ff:ff:ff' 19 | }); 20 | }; 21 | var sendarp2 = function(){ 22 | arp.send({ 23 | 'op': 'request', 24 | 'src_ip': '10.105.50.100', 25 | 'dst_ip': '10.105.50.1', 26 | 'src_mac': hex2, 27 | 'dst_mac': 'ff:ff:ff:ff:ff:ff' 28 | }); 29 | }; 30 | var sendarp3 = function(){ 31 | arp.send({ 32 | 'op': 'request', 33 | 'src_ip': '10.105.50.100', 34 | 'dst_ip': '10.105.50.1', 35 | 'src_mac': hex3, 36 | 'dst_mac': 'ff:ff:ff:ff:ff:ff' 37 | }); 38 | }; 39 | 40 | startTests = function() { 41 | before(function(done) { 42 | done(); 43 | }); 44 | it('should correctly convert string hex to decimal array', function(done) { 45 | int_array = dash_button.hex_to_int_array(hex); 46 | done(); 47 | }); 48 | it('should correctly convert a decimal array to string hex', function(done) { 49 | dash_button.int_array_to_hex(int_array).should.equal(hex); 50 | done(); 51 | }); 52 | it('should recognize an arp request', function(done) { 53 | this.timeout(30000);//sometimes the detection takes a while 54 | setTimeout(sendarp, 2500); //giving pcap time to set up a listener 55 | dash_button.register(hex).on('detected', function(){ 56 | done(); 57 | }); 58 | }); 59 | it('should not fire with more than 2 arp requests in 2 seconds', function(done) { 60 | this.timeout(30000);//sometimes the detection takes a while 61 | setInterval(sendarp, 250); //giving pcap time to set up a listener 62 | dash_button.register(hex).on('detected', function(){ 63 | done(); 64 | }); 65 | }); 66 | two_tester = dash_button.register([hex2,hex3]); 67 | 68 | it('should recognize first of two arp requests', function(done) { 69 | this.timeout(30000);//sometimes the detection takes a while 70 | setInterval(sendarp2, 250); //giving pcap time to set up a listener 71 | two_tester.on('detected', function(mac_address){ 72 | if (mac_address === hex2) done(); 73 | }); 74 | }); 75 | it('should recognize second of two arp requests', function(done) { 76 | this.timeout(30000);//sometimes the detection takes a while 77 | setInterval(sendarp3, 250); //giving pcap time to set up a listener 78 | two_tester.on('detected', function(mac_address){ 79 | if (mac_address === hex3) done(); 80 | }); 81 | }); 82 | }; 83 | startTests(); 84 | 85 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var pcap = require('pcap'); 4 | var stream = require('stream'); 5 | var _ = require('underscore'); 6 | var hex_to_int_array = require('./helpers.js').hex_to_int_array; 7 | var int_array_to_hex = require('./helpers.js').int_array_to_hex; 8 | 9 | 10 | var create_session = function (iface, protocol) { 11 | var filter; 12 | switch(protocol) { 13 | case 'all': 14 | filter = 'arp or ( udp and ( port 67 or port 68 ) )'; 15 | break; 16 | case 'udp': 17 | filter = 'udp and ( port 67 or port 68 )'; 18 | break; 19 | default: 20 | filter = 'arp'; 21 | } 22 | 23 | try { 24 | var session = pcap.createSession(iface, filter); 25 | } catch (err) { 26 | console.error(err); 27 | console.error("Failed to create pcap session: couldn't find devices to listen on.\n" + "Try running with elevated privileges via 'sudo'"); 28 | throw new Error('Error: No devices to listen'); 29 | } 30 | return session; 31 | }; 32 | 33 | //Function to register the node button 34 | var register = function(mac_addresses, iface, timeout, protocol) { 35 | if (timeout === undefined || timeout === null) { 36 | timeout = 5000; 37 | } 38 | if (protocol === undefined || protocol === null) { 39 | protocol = 'arp'; 40 | } 41 | if (Array.isArray(mac_addresses)){ 42 | //console.log("array detected") 43 | } else { 44 | //console.log("single element detected") 45 | mac_addresses = [mac_addresses];//cast to array 46 | } 47 | var pcap_session = create_session(iface, protocol); 48 | var readStream = new stream.Readable({ 49 | objectMode: true 50 | }); 51 | var just_emitted = {}; 52 | mac_addresses.forEach(function(mac_address){ 53 | just_emitted[mac_address] = false; 54 | }); 55 | pcap_session.on('packet', function(raw_packet) { 56 | var packet; 57 | 58 | /** 59 | * Perform a try/catch on packet decoding until pcap 60 | * offers a non-throwing mechanism to listen for errors 61 | * (We're just ignoring these errors because TCP packets with an 62 | * unknown offset should have no impact on this application) 63 | * 64 | * See https://github.com/mranney/node_pcap/issues/153 65 | */ 66 | try { 67 | packet = pcap.decode.packet(raw_packet); //decodes the packet 68 | } catch (err) { 69 | //console.error(err); 70 | return; 71 | } 72 | 73 | //for element in the mac addresses array 74 | for (var i = 0, l = mac_addresses.length; i < l; i++) { 75 | var mac_address = mac_addresses[i]; 76 | 77 | if((packet.payload.ethertype === 2054 //ensures it is an arp packet 78 | && _.isEqual(packet.payload.payload.sender_ha.addr, 79 | hex_to_int_array(mac_address))) 80 | || (packet.payload.ethertype === 2048 81 | && _.isEqual(packet.payload.shost.addr, 82 | hex_to_int_array(mac_address)))) { 83 | if (just_emitted[mac_address]) { 84 | break; 85 | } 86 | 87 | readStream.emit('detected', mac_address); 88 | just_emitted[mac_address] = true; 89 | setTimeout(function () { just_emitted[mac_address] = false; }, timeout); 90 | 91 | break; 92 | } 93 | } 94 | }); 95 | return readStream; 96 | }; 97 | 98 | if (process.env.NODE_ENV === 'test') { 99 | 100 | 101 | module.exports = { hex_to_int_array: hex_to_int_array, 102 | int_array_to_hex: int_array_to_hex, 103 | create_session: create_session, 104 | register: register 105 | }; 106 | 107 | } else { 108 | module.exports = register; 109 | } 110 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | require('buffer'); 3 | ///http://bulkan-evcimen.com/using_mockery_to_mock_modules_nodejs.html 4 | //this should be an effective way to mock functions 5 | var should = require('should'); 6 | var mockery = require('mockery'); // https://github.com/nathanmacinnes/injectr 7 | 8 | var pcap_mock = require('./lib/pcap'); 9 | var hexes = require('./lib/hex'); 10 | var packets = require('./lib/packets'); 11 | 12 | startTests = function() { 13 | var pcap = ""; //for linting purps 14 | var dash_button = ""; //for linting purps 15 | beforeEach(function() { 16 | mockery.enable({ 17 | warnOnReplace: false, 18 | warnOnUnregistered: false, 19 | useCleanCache: true 20 | }); 21 | pcap = new pcap_mock(); 22 | mockery.registerMock('pcap', pcap); // replace the module `pcap` with a stub object 23 | dash_button = require('../index.js'); 24 | }); 25 | afterEach(function() { 26 | mockery.disable(); 27 | }); 28 | it('should correctly mock pcap functions for testing', function(done) { 29 | pcap.test().should.equal('inject works!'); 30 | done(); 31 | }); 32 | it('should correctly convert string hex to decimal array', function(done) { 33 | var int_array = dash_button.hex_to_int_array(hexes.first); 34 | done(); 35 | }); 36 | it('should correctly convert a decimal array to string hex', function(done) { 37 | var int_array = dash_button.hex_to_int_array(hexes.first); 38 | dash_button.int_array_to_hex(int_array).should.equal(hexes.first); 39 | done(); 40 | }); 41 | it('should recognize an arp request', function(done) { 42 | dash_button.register(hexes.first).on('detected', function() { 43 | done(); 44 | }); 45 | pcap.getSession().emit('packet', packets.first); 46 | }); 47 | it('should recognize an arp request, when listening to \'all\' protocols', function(done) { 48 | dash_button.register(hexes.first, null, null, 'all').on('detected', function() { 49 | done(); 50 | }); 51 | pcap.getSession().emit('packet', packets.first); 52 | }); 53 | it('should recognize a udp request', function(done) { 54 | dash_button.register(hexes.first, null, null, 'udp').on('detected', function() { 55 | done(); 56 | }); 57 | pcap.getSession().emit('packet', packets.udp); 58 | }); 59 | it('should not fire with more than 2 arp requests in 2 seconds', function(done) { 60 | dash_button.register(hexes.second).on('detected', function() { 61 | setTimeout(function() { 62 | done(); 63 | }, 50); 64 | //console.log("should only see this once") 65 | }); 66 | for(count = 0; count < 10; count++) { 67 | //console.log("firing packet!"); 68 | pcap.getSession().emit('packet', packets.second); 69 | } 70 | }); 71 | it('should recognize first of two arp requests', function(done) { 72 | var two_tester = dash_button.register([hexes.second, hexes.third]); 73 | two_tester.on('detected', function(mac_address) { 74 | if(mac_address === hexes.second) done(); 75 | }); 76 | pcap.getSession().emit('packet', packets.second); 77 | }); 78 | it('should recognize second of two arp requests', function(done) { 79 | var two_tester = dash_button.register([hexes.second, hexes.third]); 80 | two_tester.on('detected', function(mac_address) { 81 | if(mac_address === hexes.third) done(); 82 | }); 83 | pcap.getSession().emit('packet', packets.third); 84 | }); 85 | it('should throw an error if no interfaces are available', function(done) { 86 | pcap.enableBadMode(); 87 | var dash_button_bad = require('../index.js'); 88 | try { 89 | dash_button_bad.register(hexes.first).on('detected', function() { 90 | console.log('Should never get here'); 91 | done(); 92 | }); 93 | pcap.getSession().emit('packet', packets.first); 94 | } catch(err) { 95 | done(); 96 | } 97 | }); 98 | it('should not throw an error when pcap encounters a bad packet', function(done) { 99 | try { 100 | dash_button.register(hexes.first); 101 | pcap.getSession().emit('packet', packets.bad); 102 | } catch(err) { 103 | throw new Error("Did not catch the error"); 104 | } 105 | done(); 106 | }); 107 | it('should adjust the timeout', function(done) { 108 | try { 109 | dash_button.register(hexes.first, null, 5000); 110 | pcap.getSession().emit('packet', packets.bad); 111 | } catch(err) { 112 | throw new Error("Did not catch the error"); 113 | } 114 | done(); 115 | }); 116 | }; 117 | startTests(); 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Blank Dash](http://i.imgur.com/PP0CJ3s.png?1) 2 | # node-dash-button 3 | [![Travis-CI Build Status](https://travis-ci.org/hortinstein/node-dash-button.svg)](https://travis-ci.org/hortinstein/node-dash-button) [![Coverage Status](https://coveralls.io/repos/hortinstein/node-dash-button/badge.svg?branch=master&service=github)](https://coveralls.io/github/hortinstein/node-dash-button?branch=master) [![gitter](https://img.shields.io/badge/gitter-join%20chat-green.svg?style=flat)](https://gitter.im/hortinstein/node-dash-button) 4 | 5 | Place it. *Hack it.* Press it. ~~Get it.~~ 6 | 7 | This module was inspired by [this fantastic article by Edward Bensen](https://medium.com/@edwardbenson/how-i-hacked-amazon-s-5-wifi-button-to-track-baby-data-794214b0bdd8). 8 | 9 | It is a simple library that will allow you to utilize a dash button to emit an event. I am using the same strategy of watching for dash generated ARP requests as the article above. 10 | 11 | ### Contents 12 | ----------------- 13 | - [Installation Instructions](#installation-instructions) 14 | - [First Time Dash Setup](#first-time-dash-setup) 15 | - [Find a Dash](#find-a-dash) 16 | - [Example Usage](#example-usage) 17 | - [Example Projects](#example-projects) 18 | - [Running Tests](#running-tests) 19 | - [To do](#to-do) 20 | - [Contributions](#contributions) 21 | - [License](#license) 22 | 23 | #### Installation Instructions 24 | The following should work for ubuntu, the main thing for any os is getting the libpcap dependancy. 25 | ``` sh 26 | # dependancy on libpcap for reading packets 27 | $ sudo apt-get install libpcap-dev 28 | $ npm install node-dash-button 29 | ``` 30 | #### First Time Dash Setup 31 | 32 | Follow Amazon's instructions to configure your button to send messages when you push them but not actually order anything. When you get a Dash button, Amazon gives you a list of setup instructions to get going. Just follow this list of instructions, but don’t complete the final step (#3 I think) **Do not select a product, just exit the app**. 33 | 34 | #### Find a Dash 35 | To find a dash on your network, run the following from the node-dash-button directory in node_modules: 36 | ``` sh 37 | # you may need to use sudo due to libpcap running in permiscuous mode 38 | $ cd node_modules/node-dash-button 39 | $ node bin/findbutton 40 | ``` 41 | 42 | It will watch for new arp and udp requests on your network. There may be several such requests, so press it a few times to make sure. Copy the hardware address as shown below, and make a note of the protocol used. 43 | 44 | ![hw address](http://i.imgur.com/BngokPC.png) 45 | 46 | Note: If your computer has multiple active network interfaces, `findbutton` will use the first one listed. If you need to overwrite this setting, pass your preferred interface 47 | as the first argument, such as `node bin/findbutton eth6`. 48 | 49 | #### Usage docs: 50 | 51 | ``` js 52 | var dash_button = require('node-dash-button'); 53 | var dash = dash_button(mac_addresses, iface, timeout, protocol); 54 | ``` 55 | 56 | - `mac_addresses` string or array of strings 57 | - `iface` the name of the interface on which to capture packets. If passed an empty string, libpcap will try to pick a "default" interface, which is often just the first one in some list and not what you want. (from [the node_pcap README](https://github.com/mranney/node_pcap)) 58 | - `timeout` timeout between presses in ms, default `5000` (5 seconds) 59 | - `protocol` either `arp`, `udp`, or `all`. default `arp` 60 | 61 | 62 | #### Example Usage: 63 | 64 | **For a single dash** 65 | ``` js 66 | //warning this may trigger multiple times for one press 67 | //...usually triggers twice based on testing for each press 68 | var dash_button = require('node-dash-button'); 69 | var dash = dash_button("8f:3f:20:33:54:44", null, null, 'all'); //address from step above 70 | dash.on("detected", function (){ 71 | console.log("omg found"); 72 | }); 73 | ``` 74 | 75 | **For multiple dashes**: 76 | ```js 77 | var dash_button = require('node-dash-button'); 78 | var dash = dash_button(["8f:3f:20:33:54:44","2e:3f:20:33:54:22"], null, null, 'all'); //address from step above 79 | dash.on("detected", function (dash_id){ 80 | if (dash_id === "8f:3f:20:33:54:44"){ 81 | console.log("omg found"); 82 | } else if (dash_id === "2e:3f:20:33:54:22"){ 83 | console.log("found the other!"); 84 | } 85 | }); 86 | ``` 87 | 88 | **Binding To Specific Interface**: 89 | By default, the dash button is bound to the [first device with an address](https://github.com/mranney/node_pcap/blob/master/pcap_binding.cc#L89). To bind the button to a specific interface, such as `eth6`, pass the name of the interface as the 2nd argument to the invocation method. 90 | ``` js 91 | var dash_button = require('node-dash-button'); 92 | var dash = dash_button("8f:3f:20:33:54:44", "eth6"); //address from step above 93 | dash.on("detected", function (){ 94 | console.log("omg found - on eth6!"); 95 | }); 96 | ``` 97 | 98 | **Adjusting the Timeout (if multiple presses are detected)**: 99 | By default the timeout between presses is 5 seconds. Depending on the network this may not be enough. Use the following syntax to specify a new timeout in miliseconds: 100 | ``` js 101 | var dash_button = require('node-dash-button'); 102 | var dash = dash_button("8f:3f:20:33:54:44", null, 60000); //address from step above 103 | dash.on("detected", function (){ 104 | console.log("omg found - on eth6!"); 105 | }); 106 | ``` 107 | 108 | **ARP, UDP or both**: 109 | By default the protocol monitored is ARP, which is what the earlier buttons tend to use. Newer buttons however, are using UDP to make thier request. By setting protocol to 'arp', 'udp', or 'all' (both), you can optimise the script to your setup. 110 | 111 | Note: If your button was initially picked up using ARP, but is now not being picked up, it's possible that the button has switched to UDP. 112 | 113 | ``` js 114 | var dash_button = require('node-dash-button'); 115 | var dash = dash_button("8f:3f:20:33:54:44", null, null, "all"); //address from step above 116 | dash.on("detected", function (){ 117 | console.log("omg found"); 118 | }); 119 | ``` 120 | 121 | #### Running Tests: 122 | Due to the use of pcap permiscuous monitoring this was difficult to test in CI environments, so I ended up making two testing suites. One uses the live pcap library and does actual packet capturing/arp injections. The other uses [mockery](https://github.com/mfncooper/mockery) to fake pcap packets. I will have an upcoming blog post on how I did this, because it was interesting. 123 | 124 | To run a live test of the code (requiring root due to permiscuous access please run). 125 | ``` 126 | sudo npm run-script livetest 127 | ``` 128 | This will actually inject ARP packets to the network to run the tests to ensure detection. 129 | 130 | I wanted to use various CI tools that would not allow the pcap functions to work, so I ended up mocking their functions. To run the mock tests use: 131 | ``` 132 | npm test 133 | ``` 134 | 135 | 136 | #### Example Projects: 137 | I collected a few examples I found on github of how people are using this module, some projects are more mature than others 138 | - [PizzaDash](https://github.com/bhberson/pizzadash) uses a node dash to order Domino's pizza. [The Verge](http://www.theverge.com/2015/9/28/9407669/amazon-dash-button-hack-pizza), [Gizmodo](http://gizmodo.com/an-american-hero-hacked-an-amazon-dash-button-to-order-1733347471) and [Grubstreet](http://www.grubstreet.com/2015/09/amazon-dash-button-dominos-hack.html#) did short writeups on the PizzaDash project]. 139 | - [dashgong](https://github.com/danboy/dashgong) uses the node dash to send a message to slack 140 | - [dash-listener](https://github.com/dkordik/dash-listener) performs various home automation tasks like adjusting lights and interacting with a music player 141 | - [dasher](https://github.com/maddox/dasher) lets you map a dash button press to an HTTP request. 142 | - [Nest-Dash](https://github.com/djrausch/Nest-Dash) toggles the Nest setting from away to home via Amazon Dash Button 143 | - [dash-hipchat-doorbell](https://github.com/Sfeinste/dash-hipchat-doorbell) quick and dirty node app that intercepts traffic from an amazon dash button and creates a hipchat notification (think doorbell) 144 | - [netflixandchill](https://github.com/sidho/netflixandchill) button to netflix and chill, dims the lights (no interface with netflix yet) 145 | - [dash-rickroll](https://github.com/girliemac/dash-rickroll/blob/8f0396c7fec871427fe016a2dd5787f07b1402cc/README.md) title explains it all 146 | 147 | #### To do 148 | - refactor 149 | 150 | #### Contributions 151 | Accepting pull requests! 152 | 153 | #### License 154 | 155 | The MIT License (MIT) 156 | 157 | Copyright (c) 2016 Alex Hortin 158 | 159 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 160 | 161 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 162 | 163 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 164 | --------------------------------------------------------------------------------