From 33aacb9752dc5618ae7e80991292eb2b4ee5d018 Mon Sep 17 00:00:00 2001 From: Mirko Curtolo <mirko.curtolo86@gmail.com> Date: Wed, 24 Jul 2024 12:17:05 +0200 Subject: [PATCH 1/6] Extend Space ID support to all nodes --- arduino-iot-cloud.html | 17 +++ arduino-iot-cloud.js | 27 ++-- package-lock.json | 169 +++++++++++++------------ package.json | 2 +- utils/arduino-connection-manager.js | 2 +- utils/arduino-iot-cloud-api-wrapper.js | 12 +- 6 files changed, 133 insertions(+), 96 deletions(-) diff --git a/arduino-iot-cloud.html b/arduino-iot-cloud.html index 2a390c5..b8025df 100644 --- a/arduino-iot-cloud.html +++ b/arduino-iot-cloud.html @@ -45,6 +45,7 @@ property: {value: "", validate: validator}, name: {value: "", validate: validator}, propname: {value: ""}, + organization: {value: ""}, defaultname: {value: true} }; @@ -424,6 +425,10 @@ <label for="node-input-connection"><i class="fa fa-random fa-fw"></i><span data-i18n="arduino-iot-cloud.config.node.connection"></span></label> <input type="text" id="node-input-connection"> </div> + <div class="form-row"> + <label for="node-input-organization"><i class="fa fa-tag fa-fw"></i><span data-i18n="arduino-iot-cloud.config.node.organization"></span></label> + <input type="text" id="node-input-organization" data-i18n="[placeholder]arduino-iot-cloud.config.node.placeholders.organization"> + </div> <div class="form-row"> <label for="node-input-thing"><i class="fa fa-cubes fa-fw"></i> <span data-i18n="arduino-iot-cloud.config.node.thing"></span></label> <select id="node-input-thing"> @@ -455,6 +460,10 @@ <label for="node-input-connection"><i class="fa fa-random fa-fw"></i><span data-i18n="arduino-iot-cloud.config.node.connection"></span></label> <input type="text" id="node-input-connection"> </div> + <div class="form-row"> + <label for="node-input-organization"><i class="fa fa-tag fa-fw"></i><span data-i18n="arduino-iot-cloud.config.node.organization"></span></label> + <input type="text" id="node-input-organization" data-i18n="[placeholder]arduino-iot-cloud.config.node.placeholders.organization"> + </div> <div class="form-row"> <label for="node-input-thing"><i class="fa fa-cubes fa-fw"></i><span data-i18n="arduino-iot-cloud.config.node.thing"></span></label> <select id="node-input-thing"> @@ -492,6 +501,10 @@ <label for="node-input-connection"><i class="fa fa-random fa-fw"></i><span data-i18n="arduino-iot-cloud.config.node.connection"></span></label> <input type="text" id="node-input-connection"> </div> + <div class="form-row"> + <label for="node-input-organization"><i class="fa fa-tag fa-fw"></i><span data-i18n="arduino-iot-cloud.config.node.organization"></span></label> + <input type="text" id="node-input-organization" data-i18n="[placeholder]arduino-iot-cloud.config.node.placeholders.organization"> + </div> <div class="form-row"> <label for="node-input-thing"><i class="fa fa-cubes fa-fw"></i> <span data-i18n="arduino-iot-cloud.config.node.thing"></span></label> <select id="node-input-thing"> @@ -527,6 +540,10 @@ <label for="node-input-connection"><i class="fa fa-random fa-fw"></i><span data-i18n="arduino-iot-cloud.config.node.connection"></span></label> <input type="text" id="node-input-connection"> </div> + <div class="form-row"> + <label for="node-input-organization"><i class="fa fa-tag fa-fw"></i><span data-i18n="arduino-iot-cloud.config.node.organization"></span></label> + <input type="text" id="node-input-organization" data-i18n="[placeholder]arduino-iot-cloud.config.node.placeholders.organization"> + </div> <div class="form-row"> <label for="node-input-thing"><i class="fa fa-cubes fa-fw"></i><span data-i18n="arduino-iot-cloud.config.node.thing"></span></label> <select id="node-input-thing"> diff --git a/arduino-iot-cloud.js b/arduino-iot-cloud.js index 0951af2..8202835 100644 --- a/arduino-iot-cloud.js +++ b/arduino-iot-cloud.js @@ -84,10 +84,13 @@ module.exports = function (RED) { this.propertyName = config.name; this.sendasdevice = config.sendasdevice; this.device = config.device - + const opts = {} + if (this.organization) { + opts.xOrganization = this.organization; + } this.on('input', async function (msg) { try { - await this.arduinoRestClient.setProperty(this.thing, this.propertyId, msg.payload, this.sendasdevice ? this.device : undefined); + await this.arduinoRestClient.setProperty(this.thing, this.propertyId, msg.payload, opts, this.sendasdevice ? this.device : undefined); var s; if (typeof msg.payload !== "object") { s = getStatus(msg.payload); @@ -157,6 +160,10 @@ module.exports = function (RED) { this.thing = config.thing; this.propertyId = config.property; this.propertyName = config.name; + const opts = {} + if (this.organization) { + opts.xOrganization = this.organization; + } node.on('input', async function () { try{ const now = moment(); @@ -165,7 +172,7 @@ module.exports = function (RED) { if (count !== null && count !== "" && count !== undefined && Number.isInteger(parseInt(count)) && parseInt(count) !== 0) { const start = now.subtract(count * this.timeWindowUnit, 'second').format(); - const result = await this.arduinoRestClient.getSeries(this.thing, this.propertyId, start, end); + const result = await this.arduinoRestClient.getSeries(this.thing, this.propertyId, start, end, opts); const times = result.responses[0].times; const values = result.responses[0].values; let data = []; @@ -241,19 +248,19 @@ module.exports = function (RED) { this.status({}); this.timeWindowCount = config.timeWindowCount; this.timeWindowUnit = config.timeWindowUnit; + this.organization = config.organization; if (connectionConfig && config.thing !== "" && config.thing !== "0" && config.property !== "" && config.property !== "0") { try { this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig); if (this.arduinoRestClient){ this.arduinoRestClient.openConnections++; if (config.thing !== "" && config.property !== "") { - this.organization = config.organization; this.thing = config.thing; this.propertyId = config.property; this.propertyName = config.name; const pollTime = this.timeWindowCount * this.timeWindowUnit; if (pollTime !== null && pollTime !== "" && pollTime !== undefined && Number.isInteger(parseInt(pollTime)) && parseInt(pollTime) !== 0) { - this.poll(connectionConfig, pollTime); + this.poll(connectionConfig, pollTime, this.organization); this.on('close', function (done) { connectionManager.deleteClientHttp(connectionConfig.credentials.clientid).then(() => { done(); }); if (this.pollTimeoutPoll) @@ -283,9 +290,13 @@ module.exports = function (RED) { realConstructor.apply(this, [config]); } ArduinoIotInputPoll.prototype = { - poll: async function (connectionConfig, pollTime) { + poll: async function (connectionConfig, pollTime, organization) { try { - const property = await this.arduinoRestClient.getProperty(this.thing, this.propertyId); + const opts = {} + if (organization) { + opts.xOrganization = organization; + } + const property = await this.arduinoRestClient.getProperty(this.thing, this.propertyId, opts); this.send( { topic: property.name, @@ -298,7 +309,7 @@ module.exports = function (RED) { this.status({ fill: "grey", shape: "dot", text: s }); else this.status({}); - this.pollTimeoutPoll = setTimeout(() => { this.poll(connectionConfig, pollTime) }, pollTime * 1000); + this.pollTimeoutPoll = setTimeout(() => { this.poll(connectionConfig, pollTime, organization) }, pollTime * 1000); } catch (err) { if(err.response && err.response.res && err.response.request){ console.log('statusCode: '+ err.response.res.statusCode +'\n'+ diff --git a/package-lock.json b/package-lock.json index 3f2b6a6..4c4b437 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.10", "license": "GNU AFFERO GENERAL PUBLIC LICENSE", "dependencies": { - "@arduino/arduino-iot-client": "^1.4.2", + "@arduino/arduino-iot-client": "github:arduino/iot-client-js", "@arduino/cbor-js": "github:arduino/cbor-js", "async-mutex": "^0.1.4", "jws": "^3.2.2", @@ -32,70 +32,79 @@ } }, "node_modules/@arduino/arduino-iot-client": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@arduino/arduino-iot-client/-/arduino-iot-client-1.4.2.tgz", - "integrity": "sha512-mlIBype4l+kSnGnNH8hO3CjmoDHRJpLCpjiySiKmNL0z87Wcz+1b5LLiTT/748UmbB/2FrgPZsDn8RVbB+FAhw==", + "version": "2.0.4", + "resolved": "git+ssh://git@github.com/arduino/iot-client-js.git#18e50fd5a420a26c025b59cd936e7d23e86d9955", + "license": "GPLv3", "dependencies": { "@babel/cli": "^7.0.0", - "superagent": "3.7.0" + "superagent": "^5.3.0" } }, "node_modules/@arduino/arduino-iot-client/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dependencies": { - "ms": "^2.1.1" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@arduino/arduino-iot-client/node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "node_modules/@arduino/arduino-iot-client/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/@arduino/arduino-iot-client/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">= 0.12" + "node": ">= 6" } }, - "node_modules/@arduino/arduino-iot-client/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "node_modules/@arduino/arduino-iot-client/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { - "mime": "cli.js" + "semver": "bin/semver.js" }, "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/@arduino/arduino-iot-client/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/@arduino/arduino-iot-client/node_modules/superagent": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.7.0.tgz", - "integrity": "sha512-/8trxO6NbLx4YXb7IeeFTSmsQ35pQBiTBsLNvobZx7qBzBeHYvKCyIIhW2gNcWbLzYxPAjdgFbiepd8ypwC0Gw==", - "deprecated": "Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at <https://github.com/visionmedia/superagent/releases>.", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-5.3.1.tgz", + "integrity": "sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==", + "deprecated": "Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net", "dependencies": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.1.1", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.0.5" + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" }, "engines": { - "node": ">= 4.0" + "node": ">= 7.0.0" } }, "node_modules/@arduino/cbor-js": { @@ -3312,57 +3321,57 @@ } }, "@arduino/arduino-iot-client": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@arduino/arduino-iot-client/-/arduino-iot-client-1.4.2.tgz", - "integrity": "sha512-mlIBype4l+kSnGnNH8hO3CjmoDHRJpLCpjiySiKmNL0z87Wcz+1b5LLiTT/748UmbB/2FrgPZsDn8RVbB+FAhw==", + "version": "git+ssh://git@github.com/arduino/iot-client-js.git#18e50fd5a420a26c025b59cd936e7d23e86d9955", + "from": "@arduino/arduino-iot-client@arduino/iot-client-js", "requires": { "@babel/cli": "^7.0.0", - "superagent": "3.7.0" + "superagent": "^5.3.0" }, "dependencies": { "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==" }, "superagent": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.7.0.tgz", - "integrity": "sha512-/8trxO6NbLx4YXb7IeeFTSmsQ35pQBiTBsLNvobZx7qBzBeHYvKCyIIhW2gNcWbLzYxPAjdgFbiepd8ypwC0Gw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-5.3.1.tgz", + "integrity": "sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==", "requires": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.1.1", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.0.5" + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" } } } diff --git a/package.json b/package.json index 938bde3..079a5d0 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ } }, "dependencies": { - "@arduino/arduino-iot-client": "^1.4.2", + "@arduino/arduino-iot-client": "github:arduino/iot-client-js", "@arduino/cbor-js": "github:arduino/cbor-js", "async-mutex": "^0.1.4", "jws": "^3.2.2", diff --git a/utils/arduino-connection-manager.js b/utils/arduino-connection-manager.js index f5ca626..e553778 100644 --- a/utils/arduino-connection-manager.js +++ b/utils/arduino-connection-manager.js @@ -115,7 +115,7 @@ function getMqttOptions(clientId,token,RED){ async function getClientMqtt(connectionConfig, RED) { if (!connectionConfig || !connectionConfig.credentials) { - throw new Error("Cannot find cooonection config or credentials."); + throw new Error("Cannot find connection config or credentials."); } const releaseMutex = await getClientMutex.acquire(); try { diff --git a/utils/arduino-iot-cloud-api-wrapper.js b/utils/arduino-iot-cloud-api-wrapper.js index d631ec5..1fcaf10 100644 --- a/utils/arduino-iot-cloud-api-wrapper.js +++ b/utils/arduino-iot-cloud-api-wrapper.js @@ -40,13 +40,13 @@ class ArduinoClientHttp { updateToken(token) { this.token = token; } - setProperty(thing_id, property_id, value, device_id = undefined) { + setProperty(thing_id, property_id, value, opts, device_id = undefined) { const body = JSON.stringify({ value: value, device_id : device_id }); oauth2.accessToken = this.token; - return apiProperties.propertiesV2Publish(thing_id, property_id, body); + return apiProperties.propertiesV2Publish(thing_id, property_id, body, opts); } getThings(opts) { oauth2.accessToken = this.token; @@ -63,11 +63,11 @@ class ArduinoClientHttp { const thing = apiThings.thingsV2Show(thingId, opts); return thing.then(({properties}) => properties); } - getProperty(thingId, propertyId) { + getProperty(thingId, propertyId, opts) { oauth2.accessToken = this.token; - return apiProperties.propertiesV2Show(thingId, propertyId); + return apiProperties.propertiesV2Show(thingId, propertyId, opts); } - getSeries(thingId, propertyId, start, end) { + getSeries(thingId, propertyId, start, end, opts) { const body = JSON.stringify({ requests: [{ @@ -80,7 +80,7 @@ class ArduinoClientHttp { resp_version: 1 }); oauth2.accessToken = this.token; - return apiSeries.seriesV2BatchQueryRaw(body); + return apiSeries.seriesV2BatchQueryRaw(body, opts); } } exports.ArduinoClientHttp = ArduinoClientHttp; From 92f2a51c83254c154a7cb0a6943bc9bd6f4ebdcf Mon Sep 17 00:00:00 2001 From: Mirko Curtolo <mirko.curtolo86@gmail.com> Date: Wed, 24 Jul 2024 15:22:17 +0200 Subject: [PATCH 2/6] Update rest client and fix inject node --- arduino-iot-cloud.js | 6 +++++- package-lock.json | 10 +++++----- package.json | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/arduino-iot-cloud.js b/arduino-iot-cloud.js index 8202835..b039aba 100644 --- a/arduino-iot-cloud.js +++ b/arduino-iot-cloud.js @@ -347,9 +347,13 @@ module.exports = function (RED) { this.thing = config.thing; this.propertyId = config.property; this.propertyName = config.name; + const opts = {} + if (this.organization) { + opts.xOrganization = this.organization; + } node.on('input', async function () { try{ - const property = await this.arduinoRestClient.getProperty(this.thing, this.propertyId); + const property = await this.arduinoRestClient.getProperty(this.thing, this.propertyId, opts); this.send( { topic: property.name, diff --git a/package-lock.json b/package-lock.json index 4c4b437..b550bb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@arduino/node-red-contrib-arduino-iot-cloud", - "version": "1.0.10", + "version": "1.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@arduino/node-red-contrib-arduino-iot-cloud", - "version": "1.0.10", + "version": "1.1.0", "license": "GNU AFFERO GENERAL PUBLIC LICENSE", "dependencies": { "@arduino/arduino-iot-client": "github:arduino/iot-client-js", @@ -32,8 +32,8 @@ } }, "node_modules/@arduino/arduino-iot-client": { - "version": "2.0.4", - "resolved": "git+ssh://git@github.com/arduino/iot-client-js.git#18e50fd5a420a26c025b59cd936e7d23e86d9955", + "version": "2.0.5", + "resolved": "git+ssh://git@github.com/arduino/iot-client-js.git#bc4991edf2bbd64571e2b52f79d16d392c8d01f2", "license": "GPLv3", "dependencies": { "@babel/cli": "^7.0.0", @@ -3321,7 +3321,7 @@ } }, "@arduino/arduino-iot-client": { - "version": "git+ssh://git@github.com/arduino/iot-client-js.git#18e50fd5a420a26c025b59cd936e7d23e86d9955", + "version": "git+ssh://git@github.com/arduino/iot-client-js.git#bc4991edf2bbd64571e2b52f79d16d392c8d01f2", "from": "@arduino/arduino-iot-client@arduino/iot-client-js", "requires": { "@babel/cli": "^7.0.0", diff --git a/package.json b/package.json index 079a5d0..7127cfc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@arduino/node-red-contrib-arduino-iot-cloud", - "version": "1.0.10", + "version": "1.1.0", "description": "Node-RED nodes to talk to Arduino IoT Cloud", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" From c424f9d34035e2afcb6c1bab1cafd3eaa878708a Mon Sep 17 00:00:00 2001 From: mirkokurt <mirko.curtolo86@gmail.com> Date: Thu, 5 Dec 2024 14:04:10 -0100 Subject: [PATCH 3/6] fix: add the organization header to the token request for http nodes --- arduino-iot-cloud.js | 12 ++++++------ utils/arduino-connection-manager.js | 20 ++++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/arduino-iot-cloud.js b/arduino-iot-cloud.js index b039aba..315fe51 100644 --- a/arduino-iot-cloud.js +++ b/arduino-iot-cloud.js @@ -75,7 +75,7 @@ module.exports = function (RED) { try { if (config.thing !== "" && config.property !== "") { - this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig); + this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig, this.organization); if (this.arduinoRestClient){ this.arduinoRestClient.openConnections++; this.organization = config.organization; @@ -152,7 +152,7 @@ module.exports = function (RED) { this.timeWindowUnit = config.timeWindowUnit; if (connectionConfig && config.thing !== "" && config.thing !== "0" && config.property !== "" && config.property !== "0") { try { - this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig); + this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig, this.organization); if (this.arduinoRestClient){ this.arduinoRestClient.openConnections++; if (config.thing !== "" && config.property !== "") { @@ -251,7 +251,7 @@ module.exports = function (RED) { this.organization = config.organization; if (connectionConfig && config.thing !== "" && config.thing !== "0" && config.property !== "" && config.property !== "0") { try { - this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig); + this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig, this.organization); if (this.arduinoRestClient){ this.arduinoRestClient.openConnections++; if (config.thing !== "" && config.property !== "") { @@ -340,7 +340,7 @@ module.exports = function (RED) { try { if (config.thing !== "" && config.property !== "") { - this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig); + this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig, this.organization); if (this.arduinoRestClient){ this.arduinoRestClient.openConnections++; this.organization = config.organization; @@ -432,7 +432,7 @@ module.exports = function (RED) { clientid: req.query.clientid, clientsecret: req.query.clientsecret } - }); + }, this.organization); } else if (req.query.connectionid) { const connectionConfig = RED.nodes.getNode(req.query.connectionid); if (!connectionConfig) { @@ -440,7 +440,7 @@ module.exports = function (RED) { console.log(str); return res.send(JSON.stringify({ error: str })); } - arduinoRestClient = await connectionManager.getClientHttp(connectionConfig); + arduinoRestClient = await connectionManager.getClientHttp(connectionConfig, this.organization); } else { str=RED._("arduino-iot-cloud.connection-error.no-cred-available"); console.log(str); diff --git a/utils/arduino-connection-manager.js b/utils/arduino-connection-manager.js index e553778..0abee24 100644 --- a/utils/arduino-connection-manager.js +++ b/utils/arduino-connection-manager.js @@ -40,7 +40,7 @@ const getClientMutex = new Mutex(); var numRetry=0; -async function getToken(connectionConfig) { +async function getToken(connectionConfig, organizationID) { const dataToSend = { grant_type: 'client_credentials', client_id: connectionConfig.credentials.clientid, @@ -49,12 +49,16 @@ async function getToken(connectionConfig) { }; try { + var req = superagentsuperagent + .post(accessTokenUri) + .set('content-type', 'application/x-www-form-urlencoded') + .set('accept', 'json') - var res = await superagent - .post(accessTokenUri) - .set('content-type', 'application/x-www-form-urlencoded') - .set('accept', 'json') - .send(dataToSend); + if (organizationID) { + req.set('X-Organization', organizationID) + } + + var res = await req.send(dataToSend); var token = res.body.access_token; var expires_in = res.body.expires_in * 0.8; // needed to change the token before it expires if (token !== undefined) { @@ -161,7 +165,7 @@ async function getClientMqtt(connectionConfig, RED) { } -async function getClientHttp(connectionConfig) { +async function getClientHttp(connectionConfig, organizationID) { if (!connectionConfig || !connectionConfig.credentials) { throw new Error("Cannot find cooonection config or credentials."); @@ -172,7 +176,7 @@ async function getClientHttp(connectionConfig) { var clientHttp; if (user === -1) { - var tokenInfo = await getToken(connectionConfig); + var tokenInfo = await getToken(connectionConfig, organizationID); if (tokenInfo !== undefined) { clientHttp = new ArduinoClientHttp.ArduinoClientHttp(tokenInfo.token); From 8b7562c6e1d542678642587cb82ef3e85218b4a0 Mon Sep 17 00:00:00 2001 From: mirkokurt <mirko.curtolo86@gmail.com> Date: Tue, 18 Feb 2025 14:34:33 -0100 Subject: [PATCH 4/6] fix: fix a glitch of the UI when the node configuration is re-opened after the first configuration --- arduino-iot-cloud.html | 6 ++++-- package-lock.json | 10 +++++----- package.json | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/arduino-iot-cloud.html b/arduino-iot-cloud.html index b8025df..1d74d5b 100644 --- a/arduino-iot-cloud.html +++ b/arduino-iot-cloud.html @@ -128,13 +128,15 @@ $("#node-input-organization").change(() => { const connection = $("#node-input-connection").val(); const organization = $("#node-input-organization").val(); - this.organization = organization; + if (connection === "_ADD_") { $("#node-input-organization").empty(); str = this._("arduino-iot-cloud.config.connection.placeholders.no-conn-selected"); $("<option value='" + "" + "' > " + str + "</option>").appendTo("#node-input-thing"); $("#node-input-thing").trigger("change"); - } else { + } + if (this.organization != organization) { + this.organization = organization; $("select#node-input-thing").empty(); initThings(connection, this._, null, organization); } diff --git a/package-lock.json b/package-lock.json index b550bb9..7e78a84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.1.0", "license": "GNU AFFERO GENERAL PUBLIC LICENSE", "dependencies": { - "@arduino/arduino-iot-client": "github:arduino/iot-client-js", + "@arduino/arduino-iot-client": "^3.0.0", "@arduino/cbor-js": "github:arduino/cbor-js", "async-mutex": "^0.1.4", "jws": "^3.2.2", @@ -32,8 +32,8 @@ } }, "node_modules/@arduino/arduino-iot-client": { - "version": "2.0.5", - "resolved": "git+ssh://git@github.com/arduino/iot-client-js.git#bc4991edf2bbd64571e2b52f79d16d392c8d01f2", + "version": "3.0.0", + "resolved": "git+ssh://git@github.com/arduino/iot-client-js.git#ac903b60b4001cf7282144dc26a73d19f57a7cfa", "license": "GPLv3", "dependencies": { "@babel/cli": "^7.0.0", @@ -3321,8 +3321,8 @@ } }, "@arduino/arduino-iot-client": { - "version": "git+ssh://git@github.com/arduino/iot-client-js.git#bc4991edf2bbd64571e2b52f79d16d392c8d01f2", - "from": "@arduino/arduino-iot-client@arduino/iot-client-js", + "version": "git+ssh://git@github.com/arduino/iot-client-js.git#ac903b60b4001cf7282144dc26a73d19f57a7cfa", + "from": "@arduino/arduino-iot-client@^3.0.0", "requires": { "@babel/cli": "^7.0.0", "superagent": "^5.3.0" diff --git a/package.json b/package.json index 7127cfc..a520771 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ } }, "dependencies": { - "@arduino/arduino-iot-client": "github:arduino/iot-client-js", + "@arduino/arduino-iot-client": "^3.0.0", "@arduino/cbor-js": "github:arduino/cbor-js", "async-mutex": "^0.1.4", "jws": "^3.2.2", From 81da80bb3a12c861c0edd9c3f52c1e8fd7b4bb00 Mon Sep 17 00:00:00 2001 From: mirkokurt <mirko.curtolo86@gmail.com> Date: Thu, 20 Feb 2025 08:17:43 -0100 Subject: [PATCH 5/6] fix: typo --- utils/arduino-connection-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/arduino-connection-manager.js b/utils/arduino-connection-manager.js index 0abee24..de718f5 100644 --- a/utils/arduino-connection-manager.js +++ b/utils/arduino-connection-manager.js @@ -49,7 +49,7 @@ async function getToken(connectionConfig, organizationID) { }; try { - var req = superagentsuperagent + var req = superagent .post(accessTokenUri) .set('content-type', 'application/x-www-form-urlencoded') .set('accept', 'json') From fa5401f4e81045e8c541857e82022540d13af751 Mon Sep 17 00:00:00 2001 From: Luca Rinaldi <lucarin@protonmail.com> Date: Fri, 21 Feb 2025 10:05:09 +0100 Subject: [PATCH 6/6] Token refresh only in case of errors (#56) * feat: reduce token refresh * improve refresh token http * fix get properties * fix errors * remove token refresh loop for http client * remove expiration field * make a single refresh token * fix: add organizationID to getToken method --------- Co-authored-by: mirkokurt <mirko.curtolo86@gmail.com> --- .../arduino-iot-client-mqtt.js | 9 +- arduino-iot-cloud.js | 10 +- utils/arduino-connection-manager.js | 317 +++++++----------- utils/arduino-iot-cloud-api-wrapper.js | 85 +++-- 4 files changed, 189 insertions(+), 232 deletions(-) diff --git a/arduino-iot-client-mqtt/arduino-iot-client-mqtt.js b/arduino-iot-client-mqtt/arduino-iot-client-mqtt.js index edd9759..cd92d5e 100644 --- a/arduino-iot-client-mqtt/arduino-iot-client-mqtt.js +++ b/arduino-iot-client-mqtt/arduino-iot-client-mqtt.js @@ -230,7 +230,7 @@ class ArduinoClientMqtt { } async reconnect() { - await this.connection.reconnect(); + this.connection.reconnect(); }; async updateToken(token) { @@ -241,7 +241,7 @@ class ArduinoClientMqtt { try { if (this.connection) { // Disconnect to the connection that is using the old token - await this.connection.end(); + this.connection.end(); // Remove the connection this.connection = null; @@ -627,6 +627,9 @@ class ArduinoClientMqtt { node=nodeId; } const propOutputTopic = `/a/t/${thingId}/e/o`; + if (!this.propertyCallback[propOutputTopic] || !this.propertyCallback[propOutputTopic][name]) { + return Promise.resolve(this.numSubscriptions); + } var pos=-1; for(var i=0; i<this.propertyCallback[propOutputTopic][name].length; i++){ var cbObject=this.propertyCallback[propOutputTopic][name][i]; @@ -656,4 +659,4 @@ function toArrayBuffer(buf) { return ab; } -exports.ArduinoClientMqtt = ArduinoClientMqtt; \ No newline at end of file +exports.ArduinoClientMqtt = ArduinoClientMqtt; diff --git a/arduino-iot-cloud.js b/arduino-iot-cloud.js index 315fe51..4277a73 100644 --- a/arduino-iot-cloud.js +++ b/arduino-iot-cloud.js @@ -75,7 +75,7 @@ module.exports = function (RED) { try { if (config.thing !== "" && config.property !== "") { - this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig, this.organization); + this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig, config.organization); if (this.arduinoRestClient){ this.arduinoRestClient.openConnections++; this.organization = config.organization; @@ -152,7 +152,7 @@ module.exports = function (RED) { this.timeWindowUnit = config.timeWindowUnit; if (connectionConfig && config.thing !== "" && config.thing !== "0" && config.property !== "" && config.property !== "0") { try { - this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig, this.organization); + this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig, config.organization); if (this.arduinoRestClient){ this.arduinoRestClient.openConnections++; if (config.thing !== "" && config.property !== "") { @@ -251,7 +251,7 @@ module.exports = function (RED) { this.organization = config.organization; if (connectionConfig && config.thing !== "" && config.thing !== "0" && config.property !== "" && config.property !== "0") { try { - this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig, this.organization); + this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig, config.organization); if (this.arduinoRestClient){ this.arduinoRestClient.openConnections++; if (config.thing !== "" && config.property !== "") { @@ -340,7 +340,7 @@ module.exports = function (RED) { try { if (config.thing !== "" && config.property !== "") { - this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig, this.organization); + this.arduinoRestClient = await connectionManager.getClientHttp(connectionConfig, config.organization); if (this.arduinoRestClient){ this.arduinoRestClient.openConnections++; this.organization = config.organization; @@ -476,7 +476,7 @@ module.exports = function (RED) { } } catch (err) { str=RED._("arduino-iot-cloud.connection-error.wrong-cred-sys-unvail"); - console.log(`Status: ${err.status}, message: ${err.error}`); + console.log(`getThingsOrProperties status: ${err.status}, message: ${err.error} (error: ${err})`); return res.send(JSON.stringify({ error: str })); } } diff --git a/utils/arduino-connection-manager.js b/utils/arduino-connection-manager.js index de718f5..05ecf60 100644 --- a/utils/arduino-connection-manager.js +++ b/utils/arduino-connection-manager.js @@ -24,67 +24,42 @@ const accessTokenUri = process.env.NODE_RED_ACCESS_TOKEN_URI || 'https://api2.ar const accessTokenAudience = process.env.NODE_RED_ACCESS_TOKEN_AUDIENCE || 'https://api2.arduino.cc/iot'; const arduinoIotCloudHost = process.env.NODE_RED_MQTT_HOST || 'wss.iot.arduino.cc'; const Mutex = require('async-mutex').Mutex; -/** Connections elem struct + +const mqttMutex = new Mutex(); +/** mqttConnections elem struct * { * clientId: clientId, * connectionConfig: connectionConfig, - * token: token, - * expires_token_ts: ts, * clientMqtt: clientMqttobj, + * } + */ +var mqttConnections = []; +const httpMutex = new Mutex(); +/** httpConnections elem struct + * { + * clientId: clientId, + * connectionConfig: connectionConfig, * clientHttp: clientHttpobj, - * timeoutUpdateToken: timeout * } */ -var connections = []; -const getClientMutex = new Mutex(); -var numRetry=0; - - -async function getToken(connectionConfig, organizationID) { - const dataToSend = { - grant_type: 'client_credentials', - client_id: connectionConfig.credentials.clientid, - client_secret: connectionConfig.credentials.clientsecret, - audience: accessTokenAudience - }; - - try { - var req = superagent - .post(accessTokenUri) - .set('content-type', 'application/x-www-form-urlencoded') - .set('accept', 'json') - - if (organizationID) { - req.set('X-Organization', organizationID) - } - - var res = await req.send(dataToSend); - var token = res.body.access_token; - var expires_in = res.body.expires_in * 0.8; // needed to change the token before it expires - if (token !== undefined) { - return { token: token, expires_in: expires_in }; +var httpConnections = []; + +function getMqttOptions(clientId, token, RED){ + async function reconnect() { + console.log('Reconnecting to MQTT'); + const releaseMutex = await mqttMutex.acquire(); + let id = findUser(mqttConnections, clientId); + if (id !== -1) { + let token = await waitForToken(mqttConnections[id].connectionConfig); + await mqttConnections[id].clientMqtt.updateToken(token); } - } catch (err) { - if(err.response && err.response.res && err.response.request){ - console.log('statusCode: '+ err.response.res.statusCode +'\r'+ - 'statusMessage: ' + err.response.res.statusMessage + '\r' + - 'text: ' + err.response.res.text + '\r'+ - 'HTTP method: ' + err.response.request.method + '\r' + - 'URL request: ' + err.response.request.url - ); - }else{ - console.log(err); - } - + releaseMutex(); } -} -function getMqttOptions(clientId,token,RED){ return { host: arduinoIotCloudHost, token: token, onDisconnect: async () => { - console.log(`connection lost for ${clientId}`); RED.nodes.eachNode((n)=>{ if(n.type === "property in"){ const node = RED.nodes.getNode(n.id); @@ -92,17 +67,23 @@ function getMqttOptions(clientId,token,RED){ } }); - await reconnectMqtt(clientId); + console.log('Disconnected from MQTT'); + await new Promise((resolve) => setTimeout(resolve, 1000)); + await reconnect(); }, onOffline: async () => { - console.log(`connection lost for ${clientId}`); RED.nodes.eachNode((n)=>{ if(n.type === "property in"){ const node = RED.nodes.getNode(n.id); node.status({ fill: "red", shape: "dot", text: "arduino-iot-cloud.status.offline" }); } }); + + console.log('Offline from MQTT'); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await reconnect(); }, onConnected: () =>{ RED.nodes.eachNode((n)=>{ @@ -111,211 +92,151 @@ function getMqttOptions(clientId,token,RED){ node.status({}); } }); + + console.log('Connected to MQTT'); }, useCloudProtocolV2: true }; } async function getClientMqtt(connectionConfig, RED) { - if (!connectionConfig || !connectionConfig.credentials) { throw new Error("Cannot find connection config or credentials."); } - const releaseMutex = await getClientMutex.acquire(); + const releaseMutex = await mqttMutex.acquire(); try { - let user = findUser(connectionConfig.credentials.clientid); let clientMqtt; - if (user === -1) { + let id = findUser(mqttConnections, connectionConfig.credentials.clientid); + if (id === -1) { + let token = await waitForToken(connectionConfig); clientMqtt = new ArduinoClientMqtt.ArduinoClientMqtt(); - const tokenInfo = await getToken(connectionConfig); - if (tokenInfo !== undefined) { - const ArduinoIotCloudOptions = getMqttOptions(connectionConfig.credentials.clientid,tokenInfo.token,RED) - const timeout = setTimeout(() => { updateToken(connectionConfig) }, tokenInfo.expires_in * 1000); - connections.push({ - clientId: connectionConfig.credentials.clientid, - connectionConfig: connectionConfig, - token: tokenInfo.token, - expires_token_ts: tokenInfo.expires_in, - clientMqtt: clientMqtt, - clientHttp: null, - timeoutUpdateToken: timeout - }); - await clientMqtt.connect(ArduinoIotCloudOptions); - } else { - clientMqtt = undefined; - } + mqttConnections.push({ + clientId: connectionConfig.credentials.clientid, + connectionConfig: connectionConfig, + clientMqtt: clientMqtt, + }); + await clientMqtt.connect( + getMqttOptions(connectionConfig.credentials.clientid, token, RED), + ); } else { - if (connections[user].clientMqtt !== null) { - clientMqtt = connections[user].clientMqtt; - } else { - clientMqtt = new ArduinoClientMqtt.ArduinoClientMqtt(); - const ArduinoIotCloudOptions = getMqttOptions(connectionConfig.credentials.clientid,connections[user].token,RED) - connections[user].clientMqtt = clientMqtt; - await clientMqtt.connect(ArduinoIotCloudOptions); - - } + clientMqtt = mqttConnections[id].clientMqtt; } - releaseMutex(); - return clientMqtt; } catch (err) { console.log(err); + } finally { releaseMutex(); } - } async function getClientHttp(connectionConfig, organizationID) { - if (!connectionConfig || !connectionConfig.credentials) { throw new Error("Cannot find cooonection config or credentials."); } - const releaseMutex = await getClientMutex.acquire(); + const releaseMutex = await httpMutex.acquire(); try { - var user = findUser(connectionConfig.credentials.clientid); + var id = findUser(httpConnections, connectionConfig.credentials.clientid); var clientHttp; - if (user === -1) { - - var tokenInfo = await getToken(connectionConfig, organizationID); - if (tokenInfo !== undefined) { - clientHttp = new ArduinoClientHttp.ArduinoClientHttp(tokenInfo.token); - - var timeout = setTimeout(() => { updateToken(connectionConfig) }, tokenInfo.expires_in * 1000); - connections.push({ - clientId: connectionConfig.credentials.clientid, - connectionConfig: connectionConfig, - token: tokenInfo.token, - expires_token_ts: tokenInfo.expires_in, - clientMqtt: null, - clientHttp: clientHttp, - timeoutUpdateToken: timeout - }); - - } - + if (id === -1) { + clientHttp = new ArduinoClientHttp.ArduinoClientHttp(async () => await getToken(connectionConfig, organizationID)); + httpConnections.push({ + clientId: connectionConfig.credentials.clientid, + connectionConfig: connectionConfig, + clientHttp: clientHttp, + }); } else { - if (connections[user].clientHttp !== null) { - clientHttp = connections[user].clientHttp; - } else { - clientHttp = new ArduinoClientHttp.ArduinoClientHttp(connections[user].token); - - connections[user].clientHttp = clientHttp; - } + clientHttp = httpConnections[id].clientHttp; } - - releaseMutex(); return clientHttp; } catch (err) { - if(err.response && err.response.res && err.response.request){ - console.log('statusCode: '+ err.response.res.statusCode +'\r'+ - 'statusMessage: ' + err.response.res.statusMessage + '\r' + - 'text: ' + err.response.res.text + '\r'+ - 'HTTP method: ' + err.response.request.method + '\r' + - 'URL request: ' + err.response.request.url - ); - }else{ - console.log(err); - } - + console.log(err); + } finally { releaseMutex(); + } +} +async function deleteClientMqtt(clientId, thing, propertyName, nodeId) { + const releaseMutex = await mqttMutex.acquire(); + var id = findUser(mqttConnections, clientId); + if (id !== -1) { + var ret = await mqttConnections[id].clientMqtt.removePropertyValueCallback(thing, propertyName, nodeId); + if (ret === 0) { + await mqttConnections[id].clientMqtt.disconnect(); + delete mqttConnections[id].clientMqtt; + mqttConnections[id].clientMqtt = null; + mqttConnections.splice(id, 1); + } } + releaseMutex(); +} +async function deleteClientHttp(clientId) { + const releaseMutex = await httpMutex.acquire(); + var id = findUser(httpConnections, clientId); + if (id !== -1) { + if (httpConnections[id].clientHttp !== null) { + httpConnections[id].clientHttp.openConnections--; + if (httpConnections[id].clientHttp.openConnections === 0) { + httpConnections.splice(id, 1); + } + } + } + releaseMutex(); } -function findUser(clientId) { +function findUser(connections, clientId) { for (var i = 0; i < connections.length; i++) { if (connections[i].clientId === clientId) { return i; } } return -1; - } -async function updateToken(connectionConfig) { - try { - var user = findUser(connectionConfig.credentials.clientid); - if (user !== -1) { - var tokenInfo = await getToken(connectionConfig); - if (tokenInfo !== undefined) { - numRetry=0; - connections[user].token = tokenInfo.token; - connections[user].expires_token_ts = tokenInfo.expires_in; - if(connections[user].clientMqtt){ - connections[user].clientMqtt.updateToken(tokenInfo.token); - } - if(connections[user].clientHttp){ - connections[user].clientHttp.updateToken(tokenInfo.token); - } - connections[user].timeoutUpdateToken = setTimeout(() => { updateToken(connectionConfig) }, tokenInfo.expires_in * 1000); - } else { - /*Avoid too many requests addressed to server*/ - if(numRetry < 3){ - connections[user].timeoutUpdateToken = setTimeout(() => { updateToken(connectionConfig) }, 5000); - } - else{ - connections[user].timeoutUpdateToken = setTimeout(() => { updateToken(connectionConfig) }, 60000); - } - - numRetry++; - } +async function waitForToken(connectionConfig, organizationID) { + let delay = 200; + while (true) { + let token = await getToken(connectionConfig, organizationID); + if (token) { + return token; } - } catch (err) { - console.log(err); + await new Promise((resolve) => setTimeout(resolve, delay)); + delay = Math.min(delay * 2, 5000); } } -async function deleteClientMqtt(clientId, thing, propertyName, nodeId) { - const releaseMutex = await getClientMutex.acquire(); - var user = findUser(clientId); - if (user !== -1) { - if (connections[user].clientMqtt !== null) { - var ret = await connections[user].clientMqtt.removePropertyValueCallback(thing, propertyName,nodeId); +async function getToken(connectionConfig, organizationID) { + const dataToSend = { + grant_type: 'client_credentials', + client_id: connectionConfig.credentials.clientid, + client_secret: connectionConfig.credentials.clientsecret, + audience: accessTokenAudience + }; - if (ret === 0) { - await connections[user].clientMqtt.disconnect(); - delete connections[user].clientMqtt; - connections[user].clientMqtt = null; - if (connections[user].clientHttp === null) { - if (connections[user].timeoutUpdateToken) - clearTimeout(connections[user].timeoutUpdateToken); - connections.splice(user, 1); - } - } - } - } - releaseMutex(); -} + try { + var req = superagent + .post(accessTokenUri) + .set('content-type', 'application/x-www-form-urlencoded') + .set('accept', 'json') -async function deleteClientHttp(clientId) { - const releaseMutex = await getClientMutex.acquire(); - var user = findUser(clientId); - if (user !== -1) { - if (connections[user].clientHttp !== null) { - connections[user].clientHttp.openConnections--; - if (connections[user].clientHttp.openConnections === 0) { - connections[user].clientHttp = null; - } - } - if (connections[user].clientMqtt === null) { - if (connections[user].timeoutUpdateToken) - clearTimeout(connections[user].timeoutUpdateToken); - connections.splice(user, 1); + if (organizationID) { + req.set('X-Organization', organizationID) } - } - releaseMutex(); -} -async function reconnectMqtt(clientId) { - var user = findUser(clientId); - if (user !== -1) { - if(connections[user].clientMqtt){ - await connections[user].clientMqtt.reconnect(); + var res = await req.send(dataToSend); + var token = res.body.access_token; + if (token !== undefined) { + return token; + } + } catch (err) { + if(err.response && err.response.res){ + console.log("cannot get token: " + err.response.res.statusCode + ' ' + err.response.res.statusMessage); + }else{ + console.log(err); } } } - + exports.getClientMqtt = getClientMqtt; exports.getClientHttp = getClientHttp; exports.deleteClientMqtt = deleteClientMqtt; diff --git a/utils/arduino-iot-cloud-api-wrapper.js b/utils/arduino-iot-cloud-api-wrapper.js index 1fcaf10..f4b8624 100644 --- a/utils/arduino-iot-cloud-api-wrapper.js +++ b/utils/arduino-iot-cloud-api-wrapper.js @@ -30,46 +30,79 @@ const apiSeries = new ArduinoIotClient.SeriesV2Api(client); const apiThings = new ArduinoIotClient.ThingsV2Api(client); class ArduinoClientHttp { - constructor(token) { - this.token = token; + constructor(getToken) { this.openConnections=0; + oauth2.accessToken = ""; if(process.env.API_BASE_PATH){ client.basePath = process.env.API_BASE_PATH; } + + // wrap the functions with refresh token logic + let refreshingToken = null; + function withTokenRefresh(fn) { + return async (...args) => { + try { + return await fn(...args); + } catch (e) { + if (e.status === 401) { + // make sure only one refresh token is in progress + if (!refreshingToken) { + refreshingToken = (async () => { + try { + oauth2.accessToken = await getToken(); + } finally { + refreshingToken = null; + } + })(); + } + await refreshingToken; + + // eagerly retry the request + if (oauth2.accessToken) { + return await fn(...args); + } + } + throw e; + } + }; + } + this.wrappedPropertiesV2Publish = withTokenRefresh(apiProperties.propertiesV2Publish.bind(apiProperties)); + this.wrappedThingsV2List = withTokenRefresh(apiThings.thingsV2List.bind(apiThings)); + this.wrappedThingsV2Show = withTokenRefresh(apiThings.thingsV2Show.bind(apiThings)); + this.wrappedPropertiesV2Show = withTokenRefresh(apiProperties.propertiesV2Show.bind(apiProperties)); + this.wrappedSeriesV2BatchQueryRaw = withTokenRefresh(apiSeries.seriesV2BatchQueryRaw.bind(apiSeries)); } - updateToken(token) { - this.token = token; - } - setProperty(thing_id, property_id, value, opts, device_id = undefined) { + + + async setProperty(thing_id, property_id, value, opts = {}, device_id = undefined) { const body = JSON.stringify({ value: value, - device_id : device_id + device_id: device_id }); - oauth2.accessToken = this.token; - return apiProperties.propertiesV2Publish(thing_id, property_id, body, opts); + return await this.wrappedPropertiesV2Publish(thing_id, property_id, body, opts); } - getThings(opts) { - oauth2.accessToken = this.token; - return apiThings.thingsV2List(opts); + + async getThings(opts = {}) { + return await this.wrappedThingsV2List(opts); } - getThing(thingId, opts) { - oauth2.accessToken = this.token; + + async getThing(thingId, opts = {}) { opts.showDeleted = false; - return apiThings.thingsV2Show(thingId, opts); + return await this.wrappedThingsV2Show(thingId, opts); } - getProperties(thingId, opts) { - oauth2.accessToken = this.token; + + async getProperties(thingId, opts = {}) { opts.showProperties = true; - const thing = apiThings.thingsV2Show(thingId, opts); - return thing.then(({properties}) => properties); + const { properties } = await this.wrappedThingsV2Show(thingId, opts); + return properties; } - getProperty(thingId, propertyId, opts) { - oauth2.accessToken = this.token; - return apiProperties.propertiesV2Show(thingId, propertyId, opts); + + async getProperty(thingId, propertyId, opts = {}) { + return await this.wrappedPropertiesV2Show(thingId, propertyId, opts); } - getSeries(thingId, propertyId, start, end, opts) { - const body = JSON.stringify({ + async getSeries(_thingId, propertyId, start, end, opts = {}) { + const body = JSON.stringify({ requests: [{ q: "property." + propertyId, from: start, @@ -79,8 +112,8 @@ class ArduinoClientHttp { }], resp_version: 1 }); - oauth2.accessToken = this.token; - return apiSeries.seriesV2BatchQueryRaw(body, opts); + return await this.wrappedSeriesV2BatchQueryRaw(body, opts); } } + exports.ArduinoClientHttp = ArduinoClientHttp;