diff --git a/.gitignore b/.gitignore index 71731a0..f19f484 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules lib dist es -.tmp \ No newline at end of file +.tmp +misc \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..cab13a7 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v14.17.0 diff --git a/README.md b/README.md index f482606..9ee0cc9 100644 --- a/README.md +++ b/README.md @@ -162,3 +162,31 @@ ArduinoIoTCloud.connect(options) }) .catch(error => console.error(error)); ``` + +## Development + +### Testing +In order to test the library you have to export a couple of environment variables and then +launch a specific `npm` script as follows: + +```sh +$ export CLIENT_ID= +$ export CLIENT_SECRET= +$ npm run test +``` + +## Changelog +### [0.9.0] - 2023-01-16 + +#### Changed +A few development settings have been updated, this should not affect how the library works. +- 'mqtt' is imported differently if the library is used in the browser or in node. + In browser we're using 'mqtt/dist/mqtt' because of some issues with React with some bundlers (namely, Parcel 2) + + See: + + [https://github.com/mqttjs/MQTT.js/issues/1412#issuecomment-1193363330](https://github.com/mqttjs/MQTT.js/issues/1412#issuecomment-1193363330) + + [https://github.com/mqttjs/MQTT.js/issues/1233](https://github.com/mqttjs/MQTT.js/issues/1233) + +- updated README file with this changelog and some instructions about testing diff --git a/package-lock.json b/package-lock.json index e5f0b71..ddc4778 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "arduino-iot-js", - "version": "0.7.1", + "version": "0.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -5988,7 +5988,8 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "resolved": "", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "emoji-regex": { diff --git a/package.json b/package.json index c110521..7070478 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "arduino-iot-js", - "version": "0.7.1", + "version": "0.9.0", "license": "GPLv3", "description": "JS module providing Arduino Create IoT Cloud Connection", "main": "./lib/index.js", @@ -58,7 +58,8 @@ "clean": "rimraf lib es", "build:es": "rollup -c", "build:lib": "rollup -c ./rollup.config.lib.js", - "build": "npm run clean && npm run build:es && npm run build:lib" + "build": "npm run clean && npm run build:es && npm run build:lib", + "dev": "rollup -c -w" }, "repository": { "type": "git", diff --git a/src/builder/APIConnectionBuilder.ts b/src/builder/APIConnectionBuilder.ts index 86ef5ea..15f7da7 100644 --- a/src/builder/APIConnectionBuilder.ts +++ b/src/builder/APIConnectionBuilder.ts @@ -1,3 +1,4 @@ +import { MqttClient } from 'mqtt'; import { IHttpClient } from '../http/IHttpClient'; import { Connection } from '../connection/Connection'; import { IConnection } from '../connection/IConnection'; @@ -10,8 +11,12 @@ type AccessResponse = { token_type: string; }; +function isApiOptions(options: CloudOptions): options is APIOptions { + return !!(options as APIOptions).clientId; +} + export class APIConnectionBuilder implements IConnectionBuilder { - constructor(private client: IHttpClient) {} + constructor(private client: IHttpClient, private mqttConnect: (string, IClientOptions) => MqttClient) {} public canBuild(options: CloudOptions): boolean { return isApiOptions(options); @@ -27,11 +32,7 @@ export class APIConnectionBuilder implements IConnectionBuilder { body.append('client_secret', options.clientSecret); body.append('audience', options.audience || 'https://api2.arduino.cc/iot'); - const { access_token } = await this.client.post(apiUrl, body, headers); - return Connection.From(options.host, options.port, access_token); + const { access_token: accessToken } = await this.client.post(apiUrl, body, headers); + return Connection.From(options.host, options.port, accessToken, this.mqttConnect); } } - -function isApiOptions(options: CloudOptions): options is APIOptions { - return !!(options as APIOptions).clientId; -} diff --git a/src/builder/TokenConnectionBuilder.ts b/src/builder/TokenConnectionBuilder.ts index d563a07..7f99aea 100644 --- a/src/builder/TokenConnectionBuilder.ts +++ b/src/builder/TokenConnectionBuilder.ts @@ -1,14 +1,17 @@ +import { MqttClient } from 'mqtt'; import { Connection } from '../connection/Connection'; import { IConnection } from '../connection/IConnection'; import { IConnectionBuilder } from './IConnectionBuilder'; import { BrowserOptions, CloudOptions, BaseCloudOptions } from '../client/ICloudClient'; export class TokenConnectionBuilder implements IConnectionBuilder { + constructor(private mqttConnect: (string, IClientOptions) => MqttClient) {} + canBuild(options: CloudOptions): boolean { return !!(options as BrowserOptions).token; } build({ host, port, token }: BrowserOptions & BaseCloudOptions): Promise { - return Connection.From(host, port, token); + return Connection.From(host, port, token, this.mqttConnect); } } diff --git a/src/connection/Connection.ts b/src/connection/Connection.ts index 8a00540..65bc3b7 100644 --- a/src/connection/Connection.ts +++ b/src/connection/Connection.ts @@ -34,7 +34,12 @@ export class Connection implements IConnection { }); } - public static async From(host: string, port: string | number, token: string): Promise { + public static async From( + host: string, + port: string | number, + token: string, + mqttConnect: (string, IClientOptions) => mqtt.MqttClient + ): Promise { if (!token) throw new Error('connection failed: you need to provide a valid token'); if (!host) throw new Error('connection failed: you need to provide a valid host (broker)'); @@ -46,7 +51,7 @@ export class Connection implements IConnection { }; const connection = new Connection(); - connection.client = mqtt.connect(`wss://${host}:${port}/mqtt`, { + connection.client = mqttConnect(`wss://${host}:${port}/mqtt`, { ...BaseConnectionOptions, ...options, }); @@ -106,7 +111,10 @@ export class Connection implements IConnection { else valueToSend = value; }); - if (valueToSend !== {}) messages.push({ topic, propertyName: current, value: valueToSend }); + // Checking if valueToSend is NOT {} + if (Utils.isNotAnEmptyObject(valueToSend)) { + messages.push({ topic, propertyName: current, value: valueToSend }); + } return messages; } diff --git a/src/index.lib.ts b/src/index.lib.ts index 6862230..fa6f153 100644 --- a/src/index.lib.ts +++ b/src/index.lib.ts @@ -1,33 +1,38 @@ /* -* Copyright 2020 ARDUINO SA (http://www.arduino.cc/) -* This file is part of arduino-iot-js. -* Copyright (c) 2020 -* Authors: Fabrizio Mirabito, Francesco Pirrotta -* -* This software is released under: -* The GNU General Public License, which covers the main part of -* arduino-iot-js -* The terms of this license can be found at: -* https://www.gnu.org/licenses/gpl-3.0.en.html -* -* You can be released from the requirements of the above licenses by purchasing -* a commercial license. Buying such a license is mandatory if you want to modify or -* otherwise use the software for commercial activities involving the Arduino -* software without disclosing the source code of your own applications. To purchase -* a commercial license, send an email to license@arduino.cc. -* -*/ + * Copyright 2020 ARDUINO SA (http://www.arduino.cc/) + * This file is part of arduino-iot-js. + * Copyright (c) 2020 + * Authors: Fabrizio Mirabito, Francesco Pirrotta + * + * This software is released under: + * The GNU General Public License, which covers the main part of + * arduino-iot-js + * The terms of this license can be found at: + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * You can be released from the requirements of the above licenses by purchasing + * a commercial license. Buying such a license is mandatory if you want to modify or + * otherwise use the software for commercial activities involving the Arduino + * software without disclosing the source code of your own applications. To purchase + * a commercial license, send an email to license@arduino.cc. + * + */ import SenML from './senML'; -import fetch from "node-fetch"; +import fetch from 'node-fetch'; +import mqtt from 'mqtt'; + import { HttpClientFactory } from './http/HttpClientFactory'; -import { CloudClient } from "./client/CloudClient"; -import { APIConnectionBuilder } from "./builder/APIConnectionBuilder"; -import { TokenConnectionBuilder } from "./builder/TokenConnectionBuilder"; +import { CloudClient } from './client/CloudClient'; +import { APIConnectionBuilder } from './builder/APIConnectionBuilder'; +import { TokenConnectionBuilder } from './builder/TokenConnectionBuilder'; -const builders = [new TokenConnectionBuilder(), new APIConnectionBuilder(HttpClientFactory.Create(fetch))]; +const builders = [ + new TokenConnectionBuilder(mqtt.connect), + new APIConnectionBuilder(HttpClientFactory.Create(fetch), mqtt.connect), +]; const ArduinoIoTCloud = new CloudClient(builders); export { SenML }; export { ArduinoIoTCloud }; -export { CloudOptions, CloudMessageValue } from "./client/ICloudClient"; +export { CloudOptions, CloudMessageValue } from './client/ICloudClient'; diff --git a/src/index.ts b/src/index.ts index 896f273..49b9f9c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,33 +1,37 @@ /* -* Copyright 2020 ARDUINO SA (http://www.arduino.cc/) -* This file is part of arduino-iot-js. -* Copyright (c) 2020 -* Authors: Fabrizio Mirabito, Francesco Pirrotta -* -* This software is released under: -* The GNU General Public License, which covers the main part of -* arduino-iot-js -* The terms of this license can be found at: -* https://www.gnu.org/licenses/gpl-3.0.en.html -* -* You can be released from the requirements of the above licenses by purchasing -* a commercial license. Buying such a license is mandatory if you want to modify or -* otherwise use the software for commercial activities involving the Arduino -* software without disclosing the source code of your own applications. To purchase -* a commercial license, send an email to license@arduino.cc. -* -*/ + * Copyright 2020 ARDUINO SA (http://www.arduino.cc/) + * This file is part of arduino-iot-js. + * Copyright (c) 2020 + * Authors: Fabrizio Mirabito, Francesco Pirrotta + * + * This software is released under: + * The GNU General Public License, which covers the main part of + * arduino-iot-js + * The terms of this license can be found at: + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * You can be released from the requirements of the above licenses by purchasing + * a commercial license. Buying such a license is mandatory if you want to modify or + * otherwise use the software for commercial activities involving the Arduino + * software without disclosing the source code of your own applications. To purchase + * a commercial license, send an email to license@arduino.cc. + * + */ -import "whatwg-fetch"; +import 'whatwg-fetch'; +import mqtt from 'mqtt/dist/mqtt'; import SenML from './senML'; import { HttpClientFactory } from './http/HttpClientFactory'; -import { CloudClient } from "./client/CloudClient"; -import { APIConnectionBuilder } from "./builder/APIConnectionBuilder"; -import { TokenConnectionBuilder } from "./builder/TokenConnectionBuilder"; +import { CloudClient } from './client/CloudClient'; +import { APIConnectionBuilder } from './builder/APIConnectionBuilder'; +import { TokenConnectionBuilder } from './builder/TokenConnectionBuilder'; -const builders = [new TokenConnectionBuilder(), new APIConnectionBuilder(HttpClientFactory.Create(fetch))]; +const builders = [ + new TokenConnectionBuilder(mqtt.connect), + new APIConnectionBuilder(HttpClientFactory.Create(fetch), mqtt.connect), +]; const ArduinoIoTCloud = new CloudClient(builders); export { SenML }; export { ArduinoIoTCloud }; -export { CloudOptions, CloudMessageValue } from "./client/ICloudClient"; +export { CloudOptions, CloudMessageValue } from './client/ICloudClient'; diff --git a/src/utils/index.ts b/src/utils/index.ts index 810e0ba..c5c918a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -31,6 +31,10 @@ function isArray(value: CloudMessageValue): value is T[] { return Array.isArray(value); } +function isNotAnEmptyObject(value): boolean { + return !(typeof value === 'object' && Object.keys(value).length == 0); +} + function toArrayBuffer(buf: { length: number }): ArrayBuffer { const ab = new ArrayBuffer(buf.length); const view = new Uint8Array(ab); @@ -69,4 +73,5 @@ export default { toArrayBuffer, toBuffer, arrayBufferToBase64, + isNotAnEmptyObject, }; diff --git a/test/arduino-cloud.test.js b/test/arduino-cloud.test.js index 291aaed..43c30bb 100644 --- a/test/arduino-cloud.test.js +++ b/test/arduino-cloud.test.js @@ -92,6 +92,12 @@ describe('Test the library basic functionalities', () => { await ArduinoIoTCloud.onPropertyValue(thingId, propertyBoolName, (value) => value === propertyBoolVal ? done() : null); sendPropertyAsDevice(deviceId, thingId, propertyBoolName, propertyBoolVal); }); + + it('Simulate client read boolean as FALSE property sent by device', async (done) => { + await ArduinoIoTCloud.onPropertyValue(thingId, propertyBoolName, (value) => !value ? done() : null); + sendPropertyAsDevice(deviceId, thingId, propertyBoolName, false); + }); + }) });