├── .gitignore ├── examples ├── basic │ └── app.js └── multiple │ └── app.js ├── LICENSE ├── package.json ├── README.th.md ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | test.js 4 | DS_Store 5 | microgear-*.cache 6 | local 7 | -------------------------------------------------------------------------------- /examples/basic/app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var MicroGear = require('microgear'); 4 | 5 | const KEY = ; 6 | const SECRET = ; 7 | const APPID = ; 8 | 9 | var microgear = MicroGear.create({ 10 | key : KEY, 11 | secret : SECRET 12 | }); 13 | 14 | microgear.on('connected', function() { 15 | console.log('Connected...'); 16 | microgear.setalias("mygear"); 17 | setInterval(function() { 18 | microgear.chat('mygear', 'Hello world.'); 19 | },1000); 20 | }); 21 | 22 | microgear.on('message', function(topic,body) { 23 | console.log('incoming : '+topic+' : '+body); 24 | }); 25 | 26 | microgear.on('closed', function() { 27 | console.log('Closed...'); 28 | }); 29 | 30 | microgear.connect(APPID); 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2015, NECTEC 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "microgear", 3 | "version": "0.8.1", 4 | "scripts": {}, 5 | "dependencies": { 6 | "hat": "*", 7 | "localstorage-fs": "^0.1.0", 8 | "mqtt": "*", 9 | "oauth": "*" 10 | }, 11 | "description": "NETPIE client library", 12 | "main": "index.js", 13 | "directories": { 14 | "example": "examples" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/netpieio/microgear-nodejs.git" 19 | }, 20 | "keywords": [ 21 | "netpie", 22 | "iot", 23 | "platform" 24 | ], 25 | "author": "Chavee Issariyapat ", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/netpieio/microgear-nodejs/issues" 29 | }, 30 | "homepage": "https://github.com/netpieio/microgear-nodejs" 31 | } 32 | -------------------------------------------------------------------------------- /examples/multiple/app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var MicroGear = require('../../index.js'); 4 | 5 | /* --- mg1 --------------------------------- */ 6 | var mg1 = MicroGear.create({ 7 | key : , 8 | secret : , 9 | alias : "mg1" 10 | }); 11 | 12 | mg1.on('connected', function() { 13 | console.log('mg1 connected...'); 14 | setInterval(function() { 15 | mg1.chat('mg2', 'Hello from mg1.'); 16 | },5000); 17 | }); 18 | 19 | mg1.on('message', function(topic,msg) { 20 | console.log('mg1 receives :' + msg); 21 | }); 22 | 23 | mg1.connect(); 24 | 25 | /* --- mg2 --------------------------------- */ 26 | var mg2 = MicroGear.create({ 27 | key : , 28 | secret : , 29 | alias : "mg2" 30 | }); 31 | 32 | mg2.on('connected', function() { 33 | console.log('mg2 connected...'); 34 | setInterval(function() { 35 | mg1.chat('mg1', 'Hello from mg2.'); 36 | },5000); 37 | }); 38 | 39 | mg2.on('message', function(topic,msg) { 40 | console.log('mg2 receives :'+ msg); 41 | }); 42 | 43 | mg2.connect(); 44 | -------------------------------------------------------------------------------- /README.th.md: -------------------------------------------------------------------------------- 1 | # microgear-nodejs 2 | 3 | microgear-nodejs คือ client library ภาษา Node.js ที่ทำหน้าที่เป็นตัวกลางในการเชื่อมโยง application code หรือ hardware เข้ากับบริการของ netpie platform เพื่อการพัฒนา IOT application รายละเอียดเกี่ยวกับ netpie platform สามารถศึกษาได้จาก https://netpie.io 4 | 5 | ## Port ที่มีการใช 6 | 7 | หากพบปัญหาการใช้งาน กรุณาตรวจสอบว่า port ต่อไปนี้ได้รับอนุญาตให้เข้าถึงจาก n 8 | - Non-TLS mode : 8080 and 1883 (microgear-nodejs ใช้ mode นี้เป็นค่าเริ่มต้น) 9 | - TLS mode : 8081 and 8883 10 | 11 | ## การติดตั้ง 12 | 13 | ``` 14 | npm install microgear 15 | ``` 16 | 17 | ตัวอย่างการเรียกใช้ 18 | ```js 19 | var MicroGear = require('microgear'); 20 | 21 | const APPID = ; 22 | const KEY = ; 23 | const SECRET = ; 24 | 25 | var microgear = MicroGear.create({ 26 | key : KEY, 27 | secret : SECRET 28 | }); 29 | 30 | microgear.on('connected', function() { 31 | console.log('Connected...'); 32 | microgear.setAlias("mygear"); 33 | setInterval(function() { 34 | microgear.chat('mygear', 'Hello world.'); 35 | },1000); 36 | }); 37 | 38 | microgear.on('message', function(topic,body) { 39 | console.log('incoming : '+topic+' : '+body); 40 | }); 41 | 42 | microgear.on('closed', function() { 43 | console.log('Closed...'); 44 | }); 45 | 46 | microgear.connect(APPID); 47 | ``` 48 | ## การใช้งาน library 49 | **microgear create (config)** 50 | 51 | **arguments** 52 | * *config* เป็น json object ที่ที่มี attribute ดังนี้ 53 | * *key* `string` - เป็น key สำหรับ gear ที่จะรัน ใช้ในการอ้างอิงตัวตนของ gear 54 | * *secret* `string` - เป็น secret ของ key ซึ่งจะใช้ประกอบในกระบวนการยืนยันตัวตน 55 | * *alias* `string` - เป็นการตั้งชื่อเรียก จะใส่ที่นี่หรือ setAlias() ทีหลังก็ได้ 56 | 57 | ```js 58 | var microgear = MicroGear.create({ 59 | gearkey : "sXfqDcXHzbFXiLk", 60 | gearsecret : "DNonzg2ivwS8ceksykGntrfQjxbL98", 61 | alias : "mygear" 62 | }); 63 | ``` 64 | --- 65 | ## microgear 66 | **void microgear.connect (*appid*, *callback*)** 67 | 68 | **arguments** 69 | * *appid* `string` - คือกลุ่มของ application ที่ microgear จะทำการเชื่อมต่อ 70 | ```js 71 | microgear.connect("happyfarm"); 72 | ``` 73 | --- 74 | **void microgear.setAlias (*gearalias*)** 75 | microgear สามารถตั้งนามแฝงของตัวเองได้ ซึ่งสามารถใช้เป็นชื่อให้คนอื่นเรียกในการใช้ฟังก์ชั่น chat() และชื่อที่ตั้งในโค้ด จะไปปรากฎบนหน้าจัดการ key บนเว็บ netpie.io อย่างอัตโนมัติ 76 | 77 | **arguments** 78 | * *gearalias* `string` - ชื่อของ microgear นี้ 79 | 80 | ```js 81 | microgear.setAlias("plant"); 82 | ``` 83 | --- 84 | **void microgear.chat (*gearname*, *message*)** 85 | 86 | **arguments** 87 | * *gearname* `string` - ชื่อของ microgear ที่ต้องการจะส่งข้อความไปถึง 88 | * *message* `string` - ข้อความ 89 | 90 | ```js 91 | microgear.chat("valve","I need water"); 92 | ``` 93 | --- 94 | **void microgear.publish (*topic*, *message*, [retained])** 95 | ในการณีที่ต้องการส่งข้อความแบบไม่เจาะจงผู้รับ สามารถใช้ฟังชั่น publish ไปยัง topic ที่กำหนดได้ ซึ่งจะมีแต่ microgear ที่ subscribe topoic นี้เท่านั้น ที่จะได้รับข้อความ 96 | 97 | **arguments** 98 | * *topic* `string` - ชื่อของ topic ที่ต้องการจะส่งข้อความไปถึง 99 | * *message* `string` - ข้อความ 100 | * *retained* `boolean` - ให้ retain ข้อความไว้หรือไม่ default เป็น `false` 101 | 102 | ```js 103 | microgear.publish("/outdoor/temp","28.5"); 104 | microgear.publish("/outdoor/humid","56",true); 105 | ``` 106 | --- 107 | **void microgear.subscribe (*topic*)** 108 | microgear อาจจะมีความสนใจใน topic ใดเป็นการเฉพาะ เราสามารถใช้ฟังก์ชั่น subscribe() ในการบอกรับ message ของ topic นั้นได้ และหาก topic นั้นเคยมีการ retain ข้อความไว้ microgear จะได้รับข้อความนั้นทุกครั้งที่ subscribe topic 109 | 110 | **arguments** 111 | * *topic* `string` - ชื่อของ topic ที่ต้องการจะส่งข้อความไปถึง 112 | 113 | ```js 114 | microgear.subscribe("/outdoor/temp"); 115 | ``` 116 | --- 117 | **void microgear.unsubscribe (*topic*)** 118 | ยกเลิกการ subscribe 119 | 120 | **arguments** 121 | * *topic* `string` - ชื่อของ topic ที่ต้องการจะส่งข้อความไปถึง 122 | 123 | ```js 124 | microgear.unsubscribe("/outdoor/temp"); 125 | ``` 126 | 127 | --- 128 | **void microgear.writeFeed (*feedid*, *datajson* [, *apikey*])** 129 | เขียนข้อมูลลง feed storage 130 | 131 | **arguments** 132 | * *feedid* `string` - ชื่อของ feed ที่ต้องการจะเขียนข้อมูล 133 | * *datajson* `string` - ข้อมูลที่จะบันทึก ในรูปแบบ json 134 | * *apikey* `string` - apikey สำหรับตรวจสอบสิทธิ์ หากไม่กำหนด จะใช้ default apikey ของ feed ที่ให้สิทธิ์ไว้กับ AppID 135 | 136 | ```js 137 | microgear.writeFeed("homesensor",{temp:25.7,humid:62.8,light:8.5}); 138 | ``` 139 | --- 140 | 141 | **void microgear.setCachePath (path)** 142 | โดยปกติแล้ว microgear จะเก็บไฟล์ token cache ใน directory เดียวกับ application โดยตั้งชื่อไฟล์ชื่อไฟล์ในรูปแบบ microgear-.cache เราสามารถกำหนด path ของ token cache file​ ใหม่ด้วยฟังก์ชั่น setCachePath() ซึ่งอาจจำเป็ฯต้องใช้ หากในไฟล์ Node.js application เดียวกัน มีการสร้าง microgear มากกว่าหนึ่งตัว 143 | 144 | **arguments** 145 | * *path* `string` - path ของไฟล์ cache 146 | 147 | ```js 148 | microgear.setCachePath('microgear-g1.cache'); 149 | ``` 150 | 151 | --- 152 | 153 | **void microgear.resetToken (callback)** 154 | ส่งคำสั่ง revoke token ไปยัง netpie และลบ token ออกจาก cache ส่งผลให้ microgear ต้องขอ token ใหม่ในการเชื่อมต่อครั้งต่อไป 155 | 156 | **arguments** 157 | * *callback* `function` - callback function ที่จะถูกเรียกเมื่อการ reset token เสร็จสิ้น 158 | 159 | ```js 160 | microgear.resetToken(function(result){ 161 | }); 162 | ``` 163 | 164 | เนื่องจาก resettoken() เป็น asynchronous function หากต้องการ connect หลังจาก resettoken ต้องเขียนโค้ดในลักษณะนี้ 165 | ```js 166 | microgear.resetToken(function(result){ 167 | microgear.connect(APPID); 168 | }); 169 | ``` 170 | 171 | --- 172 | 173 | **void microgear.useTLS (tlsmode)** 174 | เลือกใช้หรือไม่ใช้การเข้ารหัสแบบ TLS. หากกไม่ได้เซตอะไร โดยค่าเริ่มต้น microgear-nodejs จะไม่ใช้ TLS 175 | 176 | **arguments** 177 | * *tlsmode* `boolean` - `true คือใช้ TLS` 178 | 179 | ```js 180 | microgear.useTLS(false); 181 | ``` 182 | 183 | --- 184 | 185 | ## Events 186 | application ที่รันบน microgear จะมีการทำงานในแบบ event driven คือเป็นการทำงานตอบสนองต่อ event ต่างๆ ด้วยการเขียน callback function ขึ้นมารองรับในลักษณะนี้ 187 | 188 | **void microgear.on (*event*, *callback*)** 189 | 190 | **arguments** 191 | * *event* `string` - ชื่อ event 192 | * *callback* `function` - callback function 193 | 194 | netpie platform เวอร์ชั่นปัจจุบัน มี event ดังต่อไปนี้ 195 | 196 | **Event: 'connected'** 197 | เกืดขึ้นเมื่อ microgear library เชื่อมต่อกับ platform สำเร็จ 198 | ``` 199 | microgear.on("connected", function() { 200 | console.log("connected"); 201 | }); 202 | ``` 203 | 204 | **Event: 'closed'** 205 | เกืดขึ้นเมื่อ microgear library ตัดการเชื่อมต่อกับ platform 206 | ``` 207 | microgear.on("closed", function() { 208 | console.log("closed"); 209 | }); 210 | ``` 211 | 212 | **Event: 'error'** 213 | เป็น event ที่เกิดมี error ขึ้นภายใน microgear 214 | ``` 215 | microgear.on("error", function(err) { 216 | console.log("Error: "+err); 217 | }); 218 | ``` 219 | 220 | **Event: 'warning'** 221 | เป็น event ที่เกิดมีเหตุการณ์บางอย่างเกิดขึ้นขึ้น และมีการเตือนให้ทราบ 222 | ``` 223 | microgear.on("warning", function(msg) { 224 | console.log("Connection rejected: "+msg); 225 | }); 226 | ``` 227 | 228 | **Event: 'info'** 229 | เป็น event ที่เกิดมีเหตุการณ์บางอย่างเกิดขึ้นขึ้นภายใน microgear 230 | ``` 231 | microgear.on("info", function(msg) { 232 | console.log("Connection rejected: "+msg); 233 | }); 234 | ``` 235 | 236 | **Event: 'message'** 237 | เมื่อมี message เข้ามา จะเกิด event นี้ขึ้น พร้อมกับส่งผ่านข้อมูลเกี่ยวกับ message นั้นมาทาง argument ของ callback function 238 | ``` 239 | microgear.on("message", function(topic,msg) { 240 | console.log("Incoming message: "+mesage); 241 | }); 242 | ``` 243 | 244 | **Event: 'present'** 245 | event นี้จะเกิดขึ้นเมื่อมี microgear ใน appid เดียวกันมีการเปลี่ยนสถานะ เช่น online หรือ offline หรือเปลี่ยน alias 246 | ``` 247 | microgear.on("present", function(event) { 248 | console.log(event); 249 | }); 250 | ``` 251 | event เป็น json string ในรูปแบบ 252 | ``` 253 | { 254 | type : [online|offline|alias], 255 | gear : DEVICE_TOKEN, 256 | alias : DEVICE_ALIAS 257 | } 258 | ``` 259 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # microgear-nodejs 2 | 3 | microgear-nodejs is a client library for Node.js. The library is used to connect application code or hardware with the NETPIE Platform's service for developing IoT applications. For more details on the NETPIE Platform, please visit https://netpie.io . 4 | 5 | ## Outgoing Network Port 6 | 7 | Make sure ther following ports are allowed to connect from your network. 8 | - Non-TLS mode : 8080 and 1883 (microgear-nodejs uses this mode by default) 9 | - TLS mode : 8081 and 8883 10 | 11 | ## Installation 12 | 13 | ``` 14 | npm install microgear 15 | ``` 16 | 17 | ## Usage example 18 | ```js 19 | var MicroGear = require('microgear'); 20 | 21 | const APPID = ; 22 | const KEY = ; 23 | const SECRET = ; 24 | 25 | var microgear = MicroGear.create({ 26 | key : KEY, 27 | secret : SECRET 28 | }); 29 | 30 | microgear.on('connected', function() { 31 | console.log('Connected...'); 32 | microgear.setAlias("mygear"); 33 | setInterval(function() { 34 | microgear.chat('mygear', 'Hello world.'); 35 | },1000); 36 | }); 37 | 38 | microgear.on('message', function(topic,body) { 39 | console.log('incoming : '+topic+' : '+body); 40 | }); 41 | 42 | microgear.on('closed', function() { 43 | console.log('Closed...'); 44 | }); 45 | 46 | microgear.connect(APPID); 47 | ``` 48 | 49 | ## Library Usage 50 | 51 | **Microgear.create (*gearkey*, *gearsecret*, *scope*)** 52 | 53 | **arguments** 54 | * *config* is a json object with the following attributes: 55 | * *gearkey* `string` - is used as a microgear identity. 56 | * *gearsecret* `string` comes in a pair with gearkey. The secret is used for authentication and integrity. 57 | * *alias* `string` - specifies the device alias. 58 | 59 | ```js 60 | var microgear = MicroGear.create({ 61 | key : "sXfqDcXHzbFXiLk", 62 | secret : "DNonzg2ivwS8ceksykGntrfQjxbL98", 63 | alias : "myplant" 64 | }); 65 | ``` 66 | --- 67 | ## microgear 68 | **void microgear.connect (*appid*, *callback*)** 69 | 70 | **arguments** 71 | * *appid* `string` - a group of application that microgear will connect to. 72 | ```js 73 | microgear.connect("happyfarm"); 74 | ``` 75 | --- 76 | **void microgear.setAlias (*gearalias*)** 77 | microgear can set its own alias, which to be used for others make a function call chat(). The alias will appear on the key management portal of netpie.io . 78 | 79 | **arguments** 80 | * *alias* `string` - name of this microgear. 81 | 82 | ```js 83 | microgear.setAlias("plant"); 84 | ``` 85 | --- 86 | **void microgear.chat (*gearname*, *message*)** 87 | 88 | **arguments** 89 | * *gearname* `string` - name of microgear to which to send a message. 90 | * *message* `string` - message to be sent. 91 | 92 | ```js 93 | microgear.chat("valve","I need water"); 94 | ``` 95 | --- 96 | **void microgear.publish (*topic*, *message*, [retained])** 97 | In the case that the microgear want to send a message to an unspecified receiver, the developer can use the function publish to the desired topic, which all the microgears that subscribe such topic will receive a message. 98 | 99 | **arguments** 100 | * *topic* `string` - name of topic to be send a message to. 101 | * *message* `string` - message to be sent. 102 | * *retained* `boolean` - retain a message or not (the default is `false`) 103 | 104 | ```js 105 | microgear.publish("/outdoor/temp","28.5"); 106 | microgear.publish("/outdoor/humid","56",true); 107 | ``` 108 | --- 109 | **void microgear.subscribe (*topic*)** 110 | microgear may be interested in some topic. The developer can use the function subscribe() to subscribe a message belong to such topic. If the topic used to retain a message, the microgear will receive a message everytime it subscribes that topic. 111 | 112 | **arguments** 113 | * *topic* `string` - name of the topic to send a message to. 114 | 115 | ```js 116 | microgear.subscribe("/outdoor/temp"); 117 | ``` 118 | --- 119 | **void microgear.unsubscribe (*topic*)** 120 | cancel subscription 121 | 122 | **arguments** 123 | * *topic* `string` - name of the topic to send a message to. 124 | 125 | ```js 126 | microgear.unsubscribe("/outdoor/temp"); 127 | ``` 128 | --- 129 | **void microgear.writeFeed (*feedid*, *datajson* [, *apikey*])** 130 | write time series data to a feed storage 131 | 132 | **arguments** 133 | * *feedid* `string` - name of the feed 134 | * *datajson* `string` - data in json format 135 | * *apikey* `string` - apikey for authorization. If apikey is not specified, you will need to allow the AppID to access feed and then the default apikey will be assigned automatically. 136 | 137 | ```js 138 | microgear.writeFeed("homesensor",{temp:25.7,humid:62.8,light:8.5}); 139 | ``` 140 | --- 141 | 142 | **void microgear.setCachePath (path)** 143 | By default, a microgear token cache file is stored in the same directory as the application within a file name of this format : 'microgear-.cache'. This function is for setting a path of microgear token cache file. It will be useful when you want to run multiple microgears of the same device key on the same location. 144 | 145 | **arguments** 146 | * *path* `string` - file path 147 | 148 | ```js 149 | microgear.setCachePath('microgear-g1.cache'); 150 | ``` 151 | 152 | --- 153 | **void microgear.resetToken (callback)** 154 | send a revoke token control message to NETPIE and delete the token from cache. As a result, the microgear will need to request a new token for the next connection. 155 | 156 | **arguments** 157 | * *callback* `function` - this function will be called when the token reset is finished. 158 | 159 | ```js 160 | microgear.resetToken(function(result){ 161 | }); 162 | ``` 163 | 164 | Since the function resetToken() is asynchronous, to connect applicatin after token reset, the code should be as follows. 165 | ```js 166 | microgear.resetToken(function(result){ 167 | microgear.connect(APPID); 168 | }); 169 | ``` 170 | 171 | --- 172 | 173 | **void microgear.useTLS (tlsmode)** 174 | Enable or disable TLS. For microgear-nodejs, TLS is disabled by default. 175 | 176 | **arguments** 177 | * *tlsmode* `boolean` - The default is true (use TLS). 178 | 179 | ```js 180 | microgear.useTLS(false); 181 | ``` 182 | 183 | --- 184 | 185 | ## Events 186 | An application that runs on a microgear is an event-driven type, which responses to various events with the callback function in a form of event function call: 187 | 188 | **void microgear.on (*event*, *callback*)** 189 | 190 | **arguments** 191 | * *event* `string` - name of an event 192 | * *callback* `function` - callback function 193 | 194 | NETPIE consists of the following events: 195 | 196 | **Event: 'connected'** 197 | This event is created when the microgear library successfully connects to the NETPIE platform. 198 | ``` 199 | microgear.on("connected", function() { 200 | console.log("connected"); 201 | }); 202 | ``` 203 | 204 | **Event: 'closed'** 205 | This event is created when the microgear library disconnects the NETPIE platform. 206 | ``` 207 | microgear.on("closed", function() { 208 | console.log("closed"); 209 | }); 210 | ``` 211 | 212 | **Event: 'error'** 213 | This event is created when an error occurs within a microgear. 214 | ``` 215 | microgear.on("error", function(err) { 216 | console.log("Error: "+err); 217 | }); 218 | ``` 219 | 220 | **Event: 'warning'** 221 | This event is created when some event occurs, and a warning message will be notified. 222 | ``` 223 | microgear.on("warning", function(msg) { 224 | console.log("Connection rejected: "+msg); 225 | }); 226 | ``` 227 | 228 | **Event: 'info'** 229 | This event is created when there is some event occurs within a microgear 230 | ``` 231 | microgear.on("info", function(msg) { 232 | console.log("Connection rejected: "+msg); 233 | }); 234 | ``` 235 | 236 | **Event: 'message'** 237 | When there is an incomming message, this event is created with the related information to be sent via the callback function. 238 | 239 | ``` 240 | microgear.on("message", function(topic,msg) { 241 | console.log("Incoming message: "+mesage); 242 | }); 243 | ``` 244 | 245 | **Event: 'present'** 246 | This event is created when any microgear under the same appid changes its state (online/offline/alias changed). 247 | ``` 248 | microgear.on("present", function(event) { 249 | console.log(event); 250 | }); 251 | ``` 252 | event is a json string of the form: 253 | ``` 254 | { 255 | type : [online|offline|alias], 256 | gear : DEVICE_TOKEN, 257 | alias : DEVICE_ALIAS 258 | } 259 | ``` 260 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NetPIE microgear Library for Node.js 3 | * http://netpie.io 4 | */ 5 | 6 | module.exports.create = create; 7 | 8 | /** 9 | * General API Endpoint 10 | */ 11 | const GEARAPIADDRESS = 'ga.netpie.io'; 12 | const GEARAPIPORT = '8080'; 13 | const GEARAPISECUREPORT = '8081'; 14 | 15 | const GBMQTTPORT = '1883'; 16 | const GBMQTTSPORT = '8883'; 17 | const GBWSPORT = '8083'; 18 | const GBWSSPORT = '8084'; 19 | 20 | /** 21 | * Microgear API version 22 | */ 23 | const MGREV = 'NJS1b'; 24 | 25 | /** 26 | * Constants 27 | */ 28 | const DEBUGMODE = false; 29 | const MINTOKDELAYTIME = 100; 30 | const MAXTOKDELAYTIME = 30000; 31 | const RETRYCONNECTIONINTERVAL = 5000; 32 | 33 | var GEARAUTH = GEARAPIADDRESS; 34 | var OAuth = require('oauth'); 35 | var crypto = require('crypto'); 36 | var topModule = module; 37 | while(topModule.parent) { 38 | topModule = topModule.parent; 39 | } 40 | var appdir = process.browser?'':require('path').dirname(topModule.filename); 41 | const ps = {p:'online',a:'offline',n:'aliased',u:'unaliased'}; 42 | 43 | /** 44 | * Create MicroGear client 45 | * @param {object} param client parameter 46 | * @return {object} microgear client 47 | */ 48 | function create(param) { 49 | var events = require('events'); 50 | var mqtt = require('mqtt'); 51 | var httpclient = null; 52 | var oauth; 53 | 54 | var microgear = function(gearkey,gearsecret,gearalias) { 55 | this.securemode = true; 56 | this.debugmode = DEBUGMODE; 57 | this.gearkey = gearkey; 58 | this.gearsecret = gearsecret; 59 | this.gearalias = gearalias?gearalias.substring(0,16):null; 60 | this.appid = null; 61 | this.gearname = null; 62 | this.accesstoken = null; 63 | this.requesttoken = null; 64 | this.client = null; 65 | this.scope = ''; 66 | this.gearexaddress = null; 67 | this.gearexport = null; 68 | this.subscriptions = []; 69 | this.options = {}; 70 | this.toktime = MINTOKDELAYTIME; 71 | this.microgearcache = appdir+'/microgear-'+this.gearkey+'.cache'; 72 | } 73 | 74 | microgear.prototype = new events.EventEmitter; 75 | 76 | microgear.prototype.cache = { 77 | getItem : function(key) { 78 | var fs = process.browser?require('localstorage-fs'):require('fs'); 79 | try { 80 | var val = fs.readFileSync(key); 81 | if (typeof(val)!='undefined') { 82 | var jsonobj; 83 | try { 84 | jsonobj = JSON.parse(val); 85 | } 86 | catch(e) { 87 | return null; 88 | } 89 | return jsonobj._; 90 | } 91 | else return null; 92 | } 93 | catch(e) { 94 | return null; 95 | } 96 | }, 97 | setItem : function(key,val) { 98 | var fs = process.browser?require('localstorage-fs'):require('fs'); 99 | fs.writeFileSync(key,JSON.stringify({_:val})); 100 | } 101 | }; 102 | 103 | /** 104 | * Override cache file path 105 | * @param {string} path cache file path 106 | */ 107 | microgear.prototype.setCachePath = function(path) { 108 | this.microgearcache = path; 109 | } 110 | 111 | /** 112 | * Override cache file path 113 | * @param {string} path cache file path 114 | */ 115 | microgear.prototype.useTLS = function(usetls) { 116 | this.securemode = usetls; 117 | } 118 | 119 | /** 120 | * Cache getter 121 | * @param {string} key key name 122 | * @return {String} value 123 | */ 124 | microgear.prototype.getGearCacheValue = function(key) { 125 | var c = this.cache.getItem(this.microgearcache); 126 | if (c == null) return null; 127 | else return c[key]; 128 | } 129 | 130 | /** 131 | * Cache setter 132 | * @param {String} key key name 133 | * @param {String} value value 134 | */ 135 | microgear.prototype.setGearCacheValue = function(key,value) { 136 | var c = this.cache.getItem(this.microgearcache); 137 | if (c == null) c = {}; 138 | c[key] = value; 139 | this.cache.setItem(this.microgearcache,c); 140 | } 141 | 142 | /** 143 | * Clear cache 144 | * @param {String} key key name 145 | */ 146 | microgear.prototype.clearGearCache = function(key) { 147 | var c = this.cache.getItem(this.microgearcache); 148 | if (c == null) return; 149 | else { 150 | if (key) { 151 | c[key] = null; 152 | this.cache.setItem(this.microgearcache,c); 153 | } 154 | else { 155 | this.cache.setItem(this.microgearcache,null); 156 | } 157 | } 158 | } 159 | 160 | /** 161 | * Helper function to obtain access token 162 | * @param {Function} callback Callback 163 | */ 164 | microgear.prototype.gettoken = function(callback) { 165 | var self = this; 166 | 167 | if (this.securemode) httpclient = require('https'); 168 | else httpclient = require('http'); 169 | 170 | if (this.debugmode) console.log('Check stored token'); 171 | 172 | var cachekey = this.getGearCacheValue('key'); 173 | if (cachekey && cachekey != this.gearkey) { 174 | self.resettoken(); 175 | self.clearGearCache(); 176 | } 177 | this.setGearCacheValue('key',this.gearkey); 178 | if (!this.accesstoken) 179 | this.accesstoken = this.getGearCacheValue('accesstoken'); 180 | if (this.accesstoken) { 181 | 182 | if (this.accesstoken.endpoint != "") { 183 | var endpoint = require('url').parse(this.accesstoken.endpoint); 184 | this.gearexaddress = endpoint.hostname; 185 | this.gearexport = endpoint.port; 186 | if (typeof(callback)=='function') callback(3); 187 | } 188 | else { 189 | var opt; 190 | if (this.securemode) { 191 | opt = { 192 | host: GEARAUTH, 193 | path: '/api/endpoint/'+this.gearkey, 194 | port: GEARAPISECUREPORT, 195 | method: 'GET' 196 | }; 197 | } 198 | else { 199 | opt = { 200 | host: GEARAUTH, 201 | path: '/api/endpoint/'+this.gearkey, 202 | port: GEARAPIPORT, 203 | method: 'GET' 204 | }; 205 | } 206 | var rq = httpclient.request(opt, function(res){ 207 | var buff = ''; 208 | res.on('data', function(chunk){ 209 | buff += chunk; 210 | }); 211 | res.on('end', function(){ 212 | if (buff) { 213 | self.accesstoken.endpoint = buff; 214 | self.setGearCacheValue('accesstoken',self.accesstoken); 215 | if (typeof(callback)=='function') callback(3); 216 | } 217 | if (typeof(callback)=='function') callback(2); 218 | }); 219 | }); 220 | rq.on('error',function(e) { 221 | if (typeof(callback)=='function') callback(2); 222 | }); 223 | rq.end(); 224 | } 225 | } 226 | else { 227 | if (!this.requesttoken) 228 | this.requesttoken = this.getGearCacheValue('requesttoken'); 229 | if (this.requesttoken) { 230 | /* send requesttoken to obtain accesstoken*/ 231 | 232 | if (self.debugmode) { 233 | console.log('already has request token'); 234 | console.dir(this.requesttoken); 235 | console.log("Requesting an access token."); 236 | } 237 | 238 | var oauthurl; 239 | if (this.securemode) oauthurl = 'https://'+GEARAUTH+':'+GEARAPISECUREPORT+'/api/atoken'; 240 | else oauthurl = 'http://'+GEARAUTH+':'+GEARAPIPORT+'/api/atoken'; 241 | 242 | var oauth = new OAuth.OAuth( 243 | null, 244 | oauthurl, 245 | this.gearkey, 246 | this.gearsecret, 247 | '1.0', 248 | '', 249 | 'HMAC-SHA1' 250 | ); 251 | 252 | oauth.getOAuthAccessToken(this.requesttoken.token, this.requesttoken.secret,this.requesttoken.verifier, function (err, oauth_token, oauth_token_secret, results){ 253 | if (!err) { 254 | var hkey = oauth_token_secret+'&'+self.gearsecret; 255 | var revokecode = crypto.createHmac('sha1', hkey).update(oauth_token).digest('base64').replace(/\//g,'_'); 256 | 257 | self.accesstoken = {token:oauth_token, secret: oauth_token_secret, appkey: results.appkey, endpoint: results.endpoint, revokecode: revokecode}; 258 | if (results.flag != 'S') { 259 | self.setGearCacheValue('accesstoken',self.accesstoken); 260 | self.setGearCacheValue('requesttoken',null); 261 | } 262 | else { 263 | self.clearGearCache(); 264 | } 265 | if (typeof(callback)=='function') callback(2); 266 | } 267 | else { 268 | switch (err.statusCode) { 269 | case 401: // not authorized yet 270 | if (typeof(callback)=='function') callback(1); 271 | break; 272 | case 500: // eg. 500 request token not found 273 | default : 274 | self.emit('rejected','Request token rejected'); 275 | if (typeof(callback)=='function') callback(1); 276 | break; 277 | } 278 | } 279 | }); 280 | } 281 | else { 282 | if (self.debugmode) { 283 | console.log("Requesting a request token."); 284 | } 285 | 286 | var verifier; 287 | if (this.gearalias) verifier = this.gearalias; 288 | else verifier = MGREV; 289 | 290 | if (!this.scope) this.scope = ''; 291 | 292 | var oauthurl; 293 | if (this.securemode) oauthurl = 'https://'+GEARAUTH+':'+GEARAPISECUREPORT+'/api/rtoken'; 294 | else oauthurl = 'http://'+GEARAUTH+':'+GEARAPIPORT+'/api/rtoken'; 295 | 296 | var oauth = new OAuth.OAuth( 297 | oauthurl, 298 | null, 299 | this.gearkey, 300 | this.gearsecret, 301 | '1.0', 302 | 'scope='+this.scope+'&appid='+this.appid+'&mgrev='+MGREV+'&verifier='+verifier, 303 | 'HMAC-SHA1' 304 | ); 305 | 306 | oauth.getOAuthRequestToken({},function(err, oauth_token, oauth_token_secret, results ){ 307 | if (!err) { 308 | self.requesttoken = {token: oauth_token, secret: oauth_token_secret, verifier: verifier}; 309 | self.setGearCacheValue('requesttoken',self.requesttoken); 310 | if (typeof(callback)=='function') callback(1); 311 | } 312 | else if (typeof(callback)=='function') callback(0); 313 | }); 314 | } 315 | } 316 | } 317 | 318 | /** 319 | * Authenticate with broker using a current access token 320 | * @param {Function} callback Callback 321 | */ 322 | microgear.prototype.brokerConnect = function(callback) { 323 | var self = this; 324 | 325 | var hkey = this.accesstoken.secret+'&'+this.gearsecret; 326 | var mqttuser = this.gearkey+'%'+Math.floor(Date.now()/1000); 327 | var mqttpassword = crypto.createHmac('sha1', hkey).update(this.accesstoken.token+'%'+mqttuser).digest('base64'); 328 | var mqttclientid = this.accesstoken.token; 329 | 330 | if (this.debugmode) { 331 | console.log("mqttuser : "+mqttuser); 332 | console.log("mqttpassword : "+mqttpassword); 333 | } 334 | 335 | this.clientid = mqttclientid; 336 | 337 | if (this.securemode) { 338 | this.client = mqtt.connect( 339 | (process.browser?'wss://':'mqtts://')+this.gearexaddress, 340 | { port: process.browser?GBWSSPORT:GBMQTTSPORT, 341 | username: mqttuser, 342 | password: mqttpassword, 343 | clientId: mqttclientid, 344 | protocolVersion: 3, 345 | keepalive: 10, 346 | will: this.options?this.options.will:{} 347 | } 348 | ); 349 | } 350 | else { 351 | this.client = mqtt.connect( 352 | (process.browser?'ws://':'mqtt://')+this.gearexaddress, 353 | { port: process.browser?GBWSPORT:GBMQTTPORT, 354 | username: mqttuser, 355 | password: mqttpassword, 356 | clientId: mqttclientid, 357 | protocolVersion: 3, 358 | keepalive: 10, 359 | will: this.options?this.options.will:{} 360 | } 361 | ); 362 | } 363 | 364 | if (this.client) { 365 | /* subscribe for control messages */ 366 | this.client.subscribe('/&id/'+this.clientid+'/#'); 367 | if (typeof(callback)=='function') callback(null); 368 | } 369 | else { 370 | if (typeof(callback)=='function') callback('error'); 371 | return; 372 | } 373 | 374 | this.client.on('error', function(err) { 375 | switch (err.toString()) { 376 | case 'Error: Connection refused: Bad username or password' : // code 4 377 | // token may be nolonger valid, try to request a new one 378 | self.emit('info','invalid token, requesting a new one'); 379 | 380 | self.clearGearCache(); 381 | self.requesttoken = null; 382 | self.accesstoken = null; 383 | 384 | self.client.end(); 385 | setTimeout(function() { 386 | self.initiateConnection(function() { 387 | if (self.debugmode) console.log('auto reconnect'); 388 | }); 389 | }, RETRYCONNECTIONINTERVAL); 390 | break; 391 | case 'Error: Connection refused: Not authorized' : // code 5 392 | self.emit('warning','microgear unauthorized'); 393 | 394 | self.client.end(); 395 | setTimeout(function() { 396 | self.initiateConnection(function() { 397 | if (self.debugmode) console.log('auto reconnect'); 398 | }); 399 | }, RETRYCONNECTIONINTERVAL); 400 | break; 401 | } 402 | 403 | }); 404 | 405 | this.client.on('message', function (topic, message) { 406 | var plen = self.appid.length +1; 407 | var rtop = topic.substr(plen,topic.length-plen); 408 | 409 | if (rtop.substr(0,2)=='/&') { 410 | var p = (rtop.substr(1,rtop.length-1)+'/').indexOf('/'); 411 | var ctop = rtop.substr(2,p); 412 | 413 | switch (ctop) { 414 | case 'present' : 415 | case 'absent' : 416 | var pm; 417 | try { 418 | pm = JSON.parse(message.toString()); 419 | } 420 | catch(e) { 421 | pm = message.toString(); 422 | } 423 | self.emit(ctop, pm); 424 | break; 425 | case 'resetendpoint' : 426 | if (self.accesstoken && self.accesstoken.endpoint) { 427 | self.accesstoken.endpoint = ""; 428 | self.setGearCacheValue('accesstoken',self.accesstoken); 429 | self.emit('info','endpoint reset'); 430 | } 431 | break; 432 | } 433 | } 434 | else if (topic.substr(0,1)=='@') { 435 | switch (topic) { 436 | case '@info' : self.emit('info',message); 437 | break; 438 | case '@error' : self.emit('error',message); 439 | break; 440 | } 441 | } 442 | else { 443 | self.emit('message',topic, message); 444 | } 445 | }); 446 | 447 | this.client.on('close', function() { 448 | if (self.debugmode) console.log('client close'); 449 | self.emit('disconnected'); 450 | }); 451 | 452 | this.client.on('connect', function(pack) { 453 | for(var i=0; i 702 | * @param {String} box The name of the postbox 703 | */ 704 | microgear.prototype.readpostbox = function(box) { 705 | this.publish('/@readpostbox/'+box); 706 | } 707 | 708 | /** 709 | * put data to a specific postbox 710 | * @param {String} box The name of the postbox 711 | * @param {String} data the text data to be stored 712 | */ 713 | microgear.prototype.writepostbox = function(box,data) { 714 | this.publish('/@writepostbox/'+box,data); 715 | } 716 | 717 | /** 718 | * Revoke and remove token from cache 719 | * @param {Function} callback Callabck 720 | */ 721 | microgear.prototype.resettoken = function(callback) { 722 | var httpclient; 723 | var self = this; 724 | 725 | if (this.securemode) httpclient = require('https'); 726 | else httpclient = require('http'); 727 | 728 | this.accesstoken = this.getGearCacheValue('accesstoken'); 729 | if (this.accesstoken) { 730 | var opt; 731 | var revokecode = this.accesstoken.revokecode.replace(/\//g,'_'); 732 | 733 | if (this.securemode) { 734 | opt = { 735 | host: GEARAUTH, 736 | path: '/api/revoke/'+this.accesstoken.token+'/'+revokecode, 737 | port: GEARAPISECUREPORT, 738 | method: 'GET' 739 | }; 740 | } 741 | else { 742 | opt = { 743 | host: GEARAUTH, 744 | path: '/api/revoke/'+this.accesstoken.token+'/'+revokecode, 745 | port: GEARAPIPORT, 746 | method: 'GET' 747 | }; 748 | } 749 | 750 | var rq = httpclient.request(opt, function(res){ 751 | var result = ''; 752 | res.on('data', function(chunk){ 753 | result += chunk; 754 | }); 755 | res.on('end', function(){ 756 | if (result !== 'FAILED') { 757 | self.clearGearCache(); 758 | if (typeof(callback)=='function') callback(null); 759 | } 760 | else if (typeof(callback)=='function') callback(result); 761 | }); 762 | }); 763 | rq.on('error',function(e) { 764 | self.emit('error','Reset token error : '+e.message); 765 | if(typeof(callback)=='function') callback(e.message); 766 | }); 767 | rq.end(); 768 | } 769 | else { 770 | if (typeof(callback)=='function') callback(null); 771 | } 772 | } 773 | 774 | /** 775 | * Set configuration value by key 776 | * @param {String} key Key 777 | * @param {String} value Value 778 | */ 779 | microgear.prototype.setconfig = function(key,value) { 780 | switch(key) { 781 | case 'GEARAUTH' : GEARAUTH = value.toString(); 782 | break; 783 | } 784 | } 785 | 786 | /** 787 | * Get configuration value by key 788 | * @param {String} key Key 789 | * @return {String} value assigned to the input key 790 | */ 791 | microgear.prototype.getconfig = function(key) { 792 | switch(key) { 793 | case 'GEARAUTH' : return GEARAUTH; 794 | break; 795 | } 796 | } 797 | 798 | /** 799 | * Get connection status 800 | * @return {Boolean} true if connected 801 | */ 802 | microgear.prototype.connected = function() { 803 | if (!this.client) return false; 804 | else return this.client.connected; 805 | } 806 | 807 | /** 808 | * Push message to the owner of this device 809 | */ 810 | microgear.prototype.pushowner = function(msg) { 811 | this.publish('/@push/owner', msg, {}, function() { 812 | }); 813 | } 814 | 815 | process.on('uncaughtException', function(err) { 816 | if (DEBUGMODE) { 817 | console.log(err.toString()); 818 | } 819 | }); 820 | 821 | microgear.prototype.secureConnect = microgear.prototype.secureconnect; 822 | microgear.prototype.setName = microgear.prototype.setname; 823 | microgear.prototype.unsetName = microgear.prototype.unsetname; 824 | microgear.prototype.writeFeed = microgear.prototype.writefeed; 825 | microgear.prototype.setAlias = microgear.prototype.setalias; 826 | microgear.prototype.resetToken = microgear.prototype.resettoken; 827 | microgear.prototype.setConfig = microgear.prototype.setconfig; 828 | microgear.prototype.getConfig = microgear.prototype.getconfig; 829 | microgear.prototype.pushOwner = microgear.prototype.pushowner; 830 | 831 | var gkey = param.key || param.gearkey || ""; 832 | var gsecret = param.secret || param.gearsecret || ""; 833 | var galias = param.alias || param.gearalias || ""; 834 | 835 | if (!param) return; 836 | var scope = param.scope; 837 | 838 | if (gkey && gsecret) { 839 | var mg = new microgear(gkey, gsecret, galias); 840 | mg.scope = param.scope; 841 | mg.on('newListener', function(event,listener) { 842 | switch (event) { 843 | case 'present' : 844 | if (this.client) { 845 | if (this.client.connected) { 846 | this.subscribe('/&present'); 847 | } 848 | } 849 | break; 850 | case 'absent' : 851 | if (this.client) { 852 | if (this.client.connected) { 853 | this.subscribe('/&absent'); 854 | } 855 | } 856 | break; 857 | } 858 | }); 859 | return mg; 860 | } 861 | else { 862 | return null; 863 | } 864 | } 865 | --------------------------------------------------------------------------------