├── .babelrc ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src └── index.js └── test └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "standard" 5 | ], 6 | "plugins": [ 7 | "babel" 8 | ], 9 | "rules": { 10 | "babel/generator-star-spacing": 1, 11 | "semi": [ 12 | 2, 13 | "always" 14 | ] 15 | }, 16 | "env": { 17 | "browser": true, 18 | "mocha": true 19 | }, 20 | "globals": { 21 | "chai": true, 22 | "expect": true, 23 | "sinon": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.seed 2 | *.log 3 | *.csv 4 | *.dat 5 | *.out 6 | *.pid 7 | *.gz 8 | *.orig 9 | 10 | work 11 | build 12 | dist 13 | pids 14 | logs 15 | results 16 | coverage 17 | lib-cov 18 | html-report 19 | xunit.xml 20 | node_modules 21 | npm-debug.log 22 | 23 | .project 24 | .idea 25 | .settings 26 | .iml 27 | *.sublime-workspace 28 | *.sublime-project 29 | 30 | .DS_Store* 31 | ehthumbs.db 32 | Icon? 33 | Thumbs.db 34 | .AppleDouble 35 | .LSOverride 36 | .Spotlight-V100 37 | .Trashes 38 | 39 | # Ignore ES5 output 40 | lib/ 41 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Automatically ignored per: 2 | # https://www.npmjs.org/doc/developers.html#Keeping-files-out-of-your-package 3 | # 4 | # .*.swp 5 | # ._* 6 | # .DS_Store 7 | # .git 8 | # .hg 9 | # .lock-wscript 10 | # .svn 11 | # .wafpickle-* 12 | # CVS 13 | # npm-debug.log 14 | # node_modules 15 | 16 | *.seed 17 | *.log 18 | *.csv 19 | *.dat 20 | *.out 21 | *.pid 22 | *.gz 23 | *.orig 24 | 25 | work 26 | build 27 | test 28 | pids 29 | logs 30 | results 31 | coverage 32 | lib-cov 33 | html-report 34 | xunit.xml 35 | 36 | .project 37 | .idea 38 | .settings 39 | .iml 40 | *.sublime-workspace 41 | *.sublime-project 42 | 43 | ehthumbs.db 44 | Icon? 45 | Thumbs.db 46 | .AppleDouble 47 | .LSOverride 48 | .Spotlight-V100 49 | .Trashes 50 | 51 | # added by KAS 52 | test/ 53 | src/ 54 | babelhook.js 55 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - stable 5 | - 4 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimmyn/aws-mqtt-client/21664b18acf224c6ab9fe400fd8d59597f82eedd/LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Websocket Pub/Sub client 2 | 3 | [AWS MQTT](http://docs.aws.amazon.com/iot/latest/developerguide/protocols.html) Websocket Pub/Sub with AWS IoT based on [MQTT.js](https://github.com/mqttjs/MQTT.js). 4 | Recently [AWS released support of WebSockets for IoT](https://aws.amazon.com/about-aws/whats-new/2016/01/aws-iot-now-supports-websockets-custom-keepalive-intervals-and-enhanced-console/) service. It is very easy to use as Pub/Sub message system for serverless web applications. You can post new messages from `AWS lambda function` via `http post request` and receive them as websocket messages on client. 5 | 6 | ## Installing it 7 | 8 | ````bash 9 | npm i aws-mqtt-client --save 10 | ```` 11 | 12 | ## Basic usage 13 | 14 | 1. Create an IAM role and asign predefined `AWSIoTDataAccess` policy. (It is better to use [AWS Cognito](https://aws.amazon.com/cognito/) to provide temporary credentials for the front-end application, you can also customize policy to allow access only to user specific topics). 15 | 2. Run AWS CLI command `aws iot describe-endpoint` to get IoT endpoint url. 16 | 3. Create `mqttClient` with AWS credentials. 17 | ````js 18 | import AWSMqtt from "aws-mqtt-client"; 19 | 20 | const mqttClient = new AWSMqtt({ 21 | accessKeyId: AWS_ACCESS_KEY, 22 | secretAccessKey: AWS_SECRET_ACCESS_KEY, 23 | sessionToken: AWS_SESSION_TOKEN, 24 | endpointAddress: AWS_IOT_ENDPOINT_HOST, 25 | region: "us-east-1" 26 | }); 27 | ```` 28 | 4. Connect and receive messages from your topic. 29 | ````js 30 | mqttClient.on("connect", () => { 31 | mqttClient.subscribe("test-topic"); 32 | console.log("connected to iot mqtt websocket"); 33 | }); 34 | mqttClient.on("message", (topic, message) => { 35 | console.log(message.toString()); 36 | }); 37 | ```` 38 | 5. Publish a message. 39 | ````js 40 | mqttClient.publish(MQTT_TOPIC, message); 41 | ```` 42 | 43 | ### Complete [MQTT.js API](https://github.com/mqttjs/MQTT.js#api) 44 | 45 | ## Credits 46 | Based on [Serverless JS-Webapp Pub/Sub with AWS IoT](http://stesie.github.io/2016/04/aws-iot-pubsub) article by [Stefan Siegl](https://github.com/stesie). This library is a wrapper around [MQTT.js](https://github.com/mqttjs/MQTT.js) npm package. 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-mqtt-client", 3 | "description": "", 4 | "version": "0.0.6", 5 | "author": "Dmitriy Nevzorov ", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/jimmyn/aws-mqtt-client.git" 10 | }, 11 | "bugs": "http://github.com/jimmyn/aws-mqtt-client/issues", 12 | "keywords": [ 13 | "amazon", 14 | "aws", 15 | "iot", 16 | "pub/sub", 17 | "websocket", 18 | "mqtt" 19 | ], 20 | "dependencies": { 21 | "aws-signature-v4": "^1.0.1", 22 | "core-js": "^2.4.1", 23 | "mqtt": "^4.3.7", 24 | "websocket-stream": "^3.3.0" 25 | }, 26 | "devDependencies": { 27 | "babel-cli": "^6.14.0", 28 | "babel-preset-es2015": "^6.14.0", 29 | "babel-preset-stage-0": "^6.5.0", 30 | "babel-register": "^6.14.0", 31 | "chai": "^3.5.0", 32 | "chai-string": "^1.2.0", 33 | "mocha": "^3.0.2", 34 | "moment": "^2.14.1" 35 | }, 36 | "scripts": { 37 | "compile": "babel -e -d lib/ src/", 38 | "prepublish": "npm run compile", 39 | "test": "mocha --recursive --require babel-register" 40 | }, 41 | "main": "lib/index.js" 42 | } 43 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import v4 from 'aws-signature-v4'; 3 | import crypto from 'crypto'; 4 | import MqttClient from 'mqtt/lib/client'; 5 | import websocket from 'websocket-stream'; 6 | 7 | class AWSMqtt extends MqttClient { 8 | constructor(options = {}) { 9 | const { 10 | endpointAddress, 11 | accessKeyId, 12 | secretAccessKey, 13 | sessionToken, 14 | region, 15 | expires = 15, 16 | wsOptions, 17 | ...mqttOptions 18 | } = options; 19 | super(() => { 20 | let url = v4.createPresignedURL( 21 | 'GET', 22 | endpointAddress, 23 | '/mqtt', 24 | 'iotdevicegateway', 25 | crypto.createHash('sha256').update('', 'utf8').digest('hex'), 26 | { 27 | key: accessKeyId, 28 | secret: secretAccessKey, 29 | region, 30 | expires, 31 | protocol: 'wss' 32 | } 33 | ); 34 | if (sessionToken) { 35 | url += '&X-Amz-Security-Token=' + encodeURIComponent(sessionToken); 36 | } 37 | return websocket(url, ['mqttv3.1'], wsOptions); 38 | }, mqttOptions); 39 | } 40 | } 41 | 42 | export default AWSMqtt; 43 | 44 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import AWSMqtt from '../src'; 3 | import MqttClient from 'mqtt/lib/client'; 4 | import chai from 'chai'; 5 | import chaiString from 'chai-string'; 6 | import moment from 'moment'; 7 | chai.use(chaiString); 8 | const expect = chai.expect; 9 | 10 | let awsMqttClient; 11 | 12 | describe("AWSMqtt", () => { 13 | beforeEach(() => { 14 | awsMqttClient = new AWSMqtt({ 15 | accessKeyId: 'ASIAJKQ5TEVEKOEAUXCQ', 16 | secretAccessKey: 'NDHu8pMF7MusU5ObAXS3nHTZHBNg/1dz6J/TVjE6', 17 | sessionToken: 'FQoDYXdzEC4aDAXdeKfijZ+FZEDOwyKuAnvcowRhlgZjcsitQh5ICV+TBwrfd1K65A8rzWV6X7tR3nOJSq6YB/QQmWak7D4+7FXNiaLa2szf6YeOaSm6pb6gervq+vi/TJH4mQ38HXM0mHsceqmx28T3Hj7enqCNmEp8C/tIPRfnyQ0jhfvdS9FKoURKPgRU1m/1BZku0Q+tUirFZcHu8mCEjqAAUG3OWcfNaYyhMoYUnEPmQVBWKs2vYzgObC3sDxQq8glXSms5u8/djCWxM1bvpZbvQhll8QfSFUV0ov59DLz7CS51pomLGSkbEoJC5fb+v2KeGLLAbv3hwP6RfkRodjF/H0PkjHzVyfWry5xfbFaoi65eQ/xexBvvZf8NAYWZuNl7jnzAKVL4xIHZWKm/SeOmi/5+C07xm+kqeZJRNmaUeZtfKNa0jrwF', 18 | endpointAddress: 'bfurjhgcnbvcx.iot.eu-west-1.amazonaws.com', 19 | region: 'eu-west-1', 20 | reconnectPeriod: 60000, 21 | connectTimeout: 60000 22 | }) 23 | }); 24 | 25 | it("should be an instance of MqttClient", () => { 26 | expect(awsMqttClient).instanceof(MqttClient); 27 | }); 28 | 29 | it("should pass options to MqttClient", () => { 30 | expect(awsMqttClient.options.reconnectPeriod).to.equal(60000); 31 | expect(awsMqttClient.options.connectTimeout).to.equal(60000); 32 | }); 33 | 34 | it("should contain correct url", () => { 35 | expect(awsMqttClient.stream.socket.url).to.startsWith('wss://bfurjhgcnbvcx.iot.eu-west-1.amazonaws.com/mqtt'); 36 | }); 37 | 38 | it("should contain correct amz credentials", () => { 39 | const date = moment().format('YYYYMMDD'); 40 | expect(awsMqttClient.stream.socket.url).to.contain(`X-Amz-Credential=ASIAJKQ5TEVEKOEAUXCQ%2F${date}%2Feu-west-1%2Fiotdevicegateway%2Faws4_request`); 41 | }); 42 | 43 | it("should contain security token", () => { 44 | expect(awsMqttClient.stream.socket.url).to.contain('Amz-Security-Token=FQoDYXdzEC4aDAXdeKfijZ%2BFZEDOwyKuAnvcowRhlgZjcsitQh5ICV%2BTBwrfd1K65A8rzWV6X7tR3nOJSq6YB%2FQQmWak7D4%2B7FXNiaLa2szf6YeOaSm6pb6gervq%2Bvi%2FTJH4mQ38HXM0mHsceqmx28T3Hj7enqCNmEp8C%2FtIPRfnyQ0jhfvdS9FKoURKPgRU1m%2F1BZku0Q%2BtUirFZcHu8mCEjqAAUG3OWcfNaYyhMoYUnEPmQVBWKs2vYzgObC3sDxQq8glXSms5u8%2FdjCWxM1bvpZbvQhll8QfSFUV0ov59DLz7CS51pomLGSkbEoJC5fb%2Bv2KeGLLAbv3hwP6RfkRodjF%2FH0PkjHzVyfWry5xfbFaoi65eQ%2FxexBvvZf8NAYWZuNl7jnzAKVL4xIHZWKm%2FSeOmi%2F5%2BC07xm%2BkqeZJRNmaUeZtfKNa0jrwF'); 45 | }); 46 | 47 | }); 48 | --------------------------------------------------------------------------------