From d30a7414132628b4c26d9da68789c691bb11664f Mon Sep 17 00:00:00 2001 From: Nils Witt Date: Sat, 16 Oct 2021 21:30:19 +0200 Subject: [PATCH 01/11] added mqtt client service and sample --- nodecg-io-mqtt-client/extension/index.ts | 98 ++++++++++++++++++++++++ nodecg-io-mqtt-client/mqtt-schema.json | 28 +++++++ nodecg-io-mqtt-client/package.json | 47 ++++++++++++ nodecg-io-mqtt-client/tsconfig.json | 3 + samples/mqtt-client/extension/index.ts | 25 ++++++ samples/mqtt-client/package.json | 24 ++++++ samples/mqtt-client/tsconfig.json | 3 + 7 files changed, 228 insertions(+) create mode 100644 nodecg-io-mqtt-client/extension/index.ts create mode 100644 nodecg-io-mqtt-client/mqtt-schema.json create mode 100644 nodecg-io-mqtt-client/package.json create mode 100644 nodecg-io-mqtt-client/tsconfig.json create mode 100644 samples/mqtt-client/extension/index.ts create mode 100644 samples/mqtt-client/package.json create mode 100644 samples/mqtt-client/tsconfig.json diff --git a/nodecg-io-mqtt-client/extension/index.ts b/nodecg-io-mqtt-client/extension/index.ts new file mode 100644 index 000000000..6a8c8eae6 --- /dev/null +++ b/nodecg-io-mqtt-client/extension/index.ts @@ -0,0 +1,98 @@ +import { NodeCG } from "nodecg-types/types/server"; +import { Result, emptySuccess, success, ServiceBundle, error } from "nodecg-io-core"; +import { MqttClient, connect } from "mqtt"; + +interface MQTTClientServiceConfig { + address: string; + channels: [string]; + username?: string; + password?: string; +} + +export class MQTTClientServiceClient { + client: MqttClient; + once: (event: string, cb: any) => void; + close: () => void; + on: (event: string, cb: any) => void; + off: (event: string | symbol, listener: (...args: any[]) => void) => void; + + connect(url: string, username: string | undefined, password: string | undefined): Promise { + return new Promise((resolve, reject) => { + this.client = connect(url, { + username: username, + password: password, + }); + this.client.on("error", (err: Error) => { + this.client.end(); + reject(err.message); + }); + this.client.on("connect", () => { + resolve(); + }); + + this.once = this.client.once; + this.on = this.client.on; + this.close = this.client.end; + this.off = this.client.off; + }); + } + + subscribe(channels: string[]): void { + channels.forEach((channel: string) => { + this.client.subscribe(channel); + }); + } + + onClose(func: () => void): void { + this.client.on("close", () => { + return func(); + }); + } + + onMessage(func: (topic: string, message: any) => void): void { + this.client.on("message", (topic, message) => { + return func(topic, message); + }); + } + + onError(func: (error: Error) => void): void { + this.client.on("error", (error: Error) => { + return func(error); + }); + } +} + +module.exports = (nodecg: NodeCG) => { + new MQTTClientService(nodecg, "mqtt-client", __dirname, "../mqtt-schema.json").register(); +}; + +class MQTTClientService extends ServiceBundle { + async validateConfig(config: MQTTClientServiceConfig): Promise> { + this.nodecg.log.info("validation"); + const client = new MQTTClientServiceClient(); + try { + await client.connect(config.address, config.username, config.password); + return emptySuccess(); + } catch (e) { + return error(e); + } + } + + async createClient(config: MQTTClientServiceConfig): Promise> { + const client = new MQTTClientServiceClient(); + await client.connect(config.address, config.username, config.password); + client.subscribe(config.channels); + this.nodecg.log.info("Successfully connected to the MQTT server."); + return success(client); + } + + stopClient(client: MQTTClientServiceClient): void { + if (client.client.connected) { + client.close(); + } + } + + removeHandlers(client: MQTTClientServiceClient): void { + client.client.removeAllListeners(); + } +} diff --git a/nodecg-io-mqtt-client/mqtt-schema.json b/nodecg-io-mqtt-client/mqtt-schema.json new file mode 100644 index 000000000..7d700bd4f --- /dev/null +++ b/nodecg-io-mqtt-client/mqtt-schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "address": { + "type": "string", + "description": "The IP address of the bridge that you want to connect to, not required/used if you specify discover as true" + }, + "port": { + "type": "number", + "description": "The port that you want to use for connecting to your Hue bridge, not required/used if you specify discover as true" + }, + "password": { + "type": "string", + "description": "The api key you want to use for the client" + }, + "username": { + "type": "string", + "description": "The username that you want to use" + }, + "channels": { + "type": "array", + "description": "" + } + }, + "required": ["address", "channels"] +} diff --git a/nodecg-io-mqtt-client/package.json b/nodecg-io-mqtt-client/package.json new file mode 100644 index 000000000..31070c564 --- /dev/null +++ b/nodecg-io-mqtt-client/package.json @@ -0,0 +1,47 @@ +{ + "name": "nodecg-io-mqtt-client", + "version": "0.2.0", + "description": "Allows you to connect with your Philips Hue bridge. This allows you to control your lights etc.", + "homepage": "https://nodecg.io/RELEASE", + "author": { + "name": "TheCrether", + "url": "https://thecrether.at" + }, + "repository": { + "type": "git", + "url": "https://github.com/codeoverflow-org/nodecg-io.git", + "directory": "nodecg-io-mqtt" + }, + "files": [ + "**/*.js", + "**/*.js.map", + "**/*.d.ts", + "*.json" + ], + "main": "extension/index", + "scripts": { + "build": "tsc -b", + "watch": "tsc -b -w", + "clean": "tsc -b --clean" + }, + "keywords": [ + "nodecg-io", + "nodecg-bundle" + ], + "nodecg": { + "compatibleRange": "^1.1.1", + "bundleDependencies": { + "nodecg-io-core": "^0.2.0" + } + }, + "license": "MIT", + "devDependencies": { + "@types/node": "^15.0.2", + "nodecg-types": "^1.8.2", + "typescript": "^4.2.4" + }, + "dependencies": { + "nodecg-io-core": "^0.2.0", + "mqtt": "^4.2.8" + } +} diff --git a/nodecg-io-mqtt-client/tsconfig.json b/nodecg-io-mqtt-client/tsconfig.json new file mode 100644 index 000000000..1c8405620 --- /dev/null +++ b/nodecg-io-mqtt-client/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.common.json" +} diff --git a/samples/mqtt-client/extension/index.ts b/samples/mqtt-client/extension/index.ts new file mode 100644 index 000000000..fa3e0bd7b --- /dev/null +++ b/samples/mqtt-client/extension/index.ts @@ -0,0 +1,25 @@ +import { NodeCG } from "nodecg-types/types/server"; +import { requireService } from "nodecg-io-core"; +import { MQTTClientServiceClient } from "nodecg-io-mqtt-client"; + +module.exports = function (nodecg: NodeCG) { + nodecg.log.info("Sample bundle for websocket-client started"); + + const service = requireService(nodecg, "mqtt-client"); + let interval: NodeJS.Timeout | undefined; + + service?.onAvailable((client) => { + nodecg.log.info("Client has been updated, waiting for messages."); + + client.onMessage((topic: string, message: any) => { + nodecg.log.info(`recieved message "${message.toString()}" "${topic}"`); + }); + }); + + service?.onUnavailable(() => { + nodecg.log.info("Client has been unset."); + if (interval) { + clearInterval(interval); + } + }); +}; diff --git a/samples/mqtt-client/package.json b/samples/mqtt-client/package.json new file mode 100644 index 000000000..d28383509 --- /dev/null +++ b/samples/mqtt-client/package.json @@ -0,0 +1,24 @@ +{ + "name": "mqtt-client", + "version": "0.2.0", + "private": true, + "nodecg": { + "compatibleRange": "^1.1.1", + "bundleDependencies": { + "nodecg-io-mqtt-client": "^0.2.0" + } + }, + "scripts": { + "build": "tsc -b", + "watch": "tsc -b -w", + "clean": "tsc -b --clean" + }, + "license": "MIT", + "dependencies": { + "@types/node": "^15.0.2", + "nodecg-types": "^1.8.2", + "nodecg-io-core": "^0.2.0", + "nodecg-io-mqtt-client": "^0.2.0", + "typescript": "^4.2.4" + } +} diff --git a/samples/mqtt-client/tsconfig.json b/samples/mqtt-client/tsconfig.json new file mode 100644 index 000000000..c8bb01bee --- /dev/null +++ b/samples/mqtt-client/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.common.json" +} From fbd5ddd959b268daa85b27e68a5951b6db88f8d6 Mon Sep 17 00:00:00 2001 From: Nils Witt Date: Sat, 16 Oct 2021 21:39:35 +0200 Subject: [PATCH 02/11] fixed lint problems --- nodecg-io-mqtt-client/extension/index.ts | 8 ++++---- samples/mqtt-client/extension/index.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nodecg-io-mqtt-client/extension/index.ts b/nodecg-io-mqtt-client/extension/index.ts index 6a8c8eae6..16e6775cf 100644 --- a/nodecg-io-mqtt-client/extension/index.ts +++ b/nodecg-io-mqtt-client/extension/index.ts @@ -11,10 +11,10 @@ interface MQTTClientServiceConfig { export class MQTTClientServiceClient { client: MqttClient; - once: (event: string, cb: any) => void; + once: (event: string, cb: () => void) => void; close: () => void; - on: (event: string, cb: any) => void; - off: (event: string | symbol, listener: (...args: any[]) => void) => void; + on: (event: string, cb: () => void) => void; + off: (event: string | symbol, listener: (...args: unknown[]) => void) => void; connect(url: string, username: string | undefined, password: string | undefined): Promise { return new Promise((resolve, reject) => { @@ -49,7 +49,7 @@ export class MQTTClientServiceClient { }); } - onMessage(func: (topic: string, message: any) => void): void { + onMessage(func: (topic: string, message: Buffer) => void): void { this.client.on("message", (topic, message) => { return func(topic, message); }); diff --git a/samples/mqtt-client/extension/index.ts b/samples/mqtt-client/extension/index.ts index fa3e0bd7b..3af23185c 100644 --- a/samples/mqtt-client/extension/index.ts +++ b/samples/mqtt-client/extension/index.ts @@ -11,7 +11,7 @@ module.exports = function (nodecg: NodeCG) { service?.onAvailable((client) => { nodecg.log.info("Client has been updated, waiting for messages."); - client.onMessage((topic: string, message: any) => { + client.onMessage((topic: string, message: Buffer) => { nodecg.log.info(`recieved message "${message.toString()}" "${topic}"`); }); }); From f4590ad0c524b43dd68775761d0cf984672a8615 Mon Sep 17 00:00:00 2001 From: Nils Witt Date: Sat, 16 Oct 2021 21:42:58 +0200 Subject: [PATCH 03/11] updated schema --- nodecg-io-mqtt-client/extension/index.ts | 10 +++++----- nodecg-io-mqtt-client/mqtt-schema.json | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/nodecg-io-mqtt-client/extension/index.ts b/nodecg-io-mqtt-client/extension/index.ts index 16e6775cf..fa06b1221 100644 --- a/nodecg-io-mqtt-client/extension/index.ts +++ b/nodecg-io-mqtt-client/extension/index.ts @@ -4,7 +4,7 @@ import { MqttClient, connect } from "mqtt"; interface MQTTClientServiceConfig { address: string; - channels: [string]; + topics: [string]; username?: string; password?: string; } @@ -37,9 +37,9 @@ export class MQTTClientServiceClient { }); } - subscribe(channels: string[]): void { - channels.forEach((channel: string) => { - this.client.subscribe(channel); + subscribe(topics: string[]): void { + topics.forEach((topic: string) => { + this.client.subscribe(topic); }); } @@ -81,7 +81,7 @@ class MQTTClientService extends ServiceBundle> { const client = new MQTTClientServiceClient(); await client.connect(config.address, config.username, config.password); - client.subscribe(config.channels); + client.subscribe(config.topics); this.nodecg.log.info("Successfully connected to the MQTT server."); return success(client); } diff --git a/nodecg-io-mqtt-client/mqtt-schema.json b/nodecg-io-mqtt-client/mqtt-schema.json index 7d700bd4f..45e65366b 100644 --- a/nodecg-io-mqtt-client/mqtt-schema.json +++ b/nodecg-io-mqtt-client/mqtt-schema.json @@ -19,10 +19,10 @@ "type": "string", "description": "The username that you want to use" }, - "channels": { + "topics": { "type": "array", - "description": "" + "description": "Array of topic you want to subscribe to" } }, - "required": ["address", "channels"] + "required": ["address", "topics"] } From 14fcc191fceea93352978b504f0affb5aece7452 Mon Sep 17 00:00:00 2001 From: Nils Witt Date: Sat, 16 Oct 2021 21:46:58 +0200 Subject: [PATCH 04/11] updated schema --- nodecg-io-mqtt-client/mqtt-schema.json | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/nodecg-io-mqtt-client/mqtt-schema.json b/nodecg-io-mqtt-client/mqtt-schema.json index 45e65366b..f4969615d 100644 --- a/nodecg-io-mqtt-client/mqtt-schema.json +++ b/nodecg-io-mqtt-client/mqtt-schema.json @@ -5,19 +5,15 @@ "properties": { "address": { "type": "string", - "description": "The IP address of the bridge that you want to connect to, not required/used if you specify discover as true" - }, - "port": { - "type": "number", - "description": "The port that you want to use for connecting to your Hue bridge, not required/used if you specify discover as true" + "description": "The address of the server" }, "password": { "type": "string", - "description": "The api key you want to use for the client" + "description": "The password to connect to the server" }, "username": { "type": "string", - "description": "The username that you want to use" + "description": "The username to connect to the server" }, "topics": { "type": "array", From ca6b57d3fceed7db727a04f5ad41fbdd36e10674 Mon Sep 17 00:00:00 2001 From: Nils Witt Date: Sat, 16 Oct 2021 21:48:48 +0200 Subject: [PATCH 05/11] updated package.json --- nodecg-io-mqtt-client/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodecg-io-mqtt-client/package.json b/nodecg-io-mqtt-client/package.json index 31070c564..67b609032 100644 --- a/nodecg-io-mqtt-client/package.json +++ b/nodecg-io-mqtt-client/package.json @@ -4,8 +4,8 @@ "description": "Allows you to connect with your Philips Hue bridge. This allows you to control your lights etc.", "homepage": "https://nodecg.io/RELEASE", "author": { - "name": "TheCrether", - "url": "https://thecrether.at" + "name": "Nils Witt", + "url": "https://github.com/Nils-witt" }, "repository": { "type": "git", From c304f00493c40d7e59a1b7fb01c0ef2404d9b795 Mon Sep 17 00:00:00 2001 From: Nils Witt Date: Sat, 16 Oct 2021 21:53:42 +0200 Subject: [PATCH 06/11] updated package.json --- nodecg-io-mqtt-client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodecg-io-mqtt-client/package.json b/nodecg-io-mqtt-client/package.json index 67b609032..f9fd91f95 100644 --- a/nodecg-io-mqtt-client/package.json +++ b/nodecg-io-mqtt-client/package.json @@ -1,7 +1,7 @@ { "name": "nodecg-io-mqtt-client", "version": "0.2.0", - "description": "Allows you to connect with your Philips Hue bridge. This allows you to control your lights etc.", + "description": "Allows you to connect with an MQTT server.", "homepage": "https://nodecg.io/RELEASE", "author": { "name": "Nils Witt", From a0103f1139bc3335e7853ec6d54ffca690d737c3 Mon Sep 17 00:00:00 2001 From: Nils Witt Date: Sun, 17 Oct 2021 09:10:28 +0200 Subject: [PATCH 07/11] Fixed to close the client connection after config validation --- nodecg-io-mqtt-client/extension/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/nodecg-io-mqtt-client/extension/index.ts b/nodecg-io-mqtt-client/extension/index.ts index fa06b1221..ab0408f82 100644 --- a/nodecg-io-mqtt-client/extension/index.ts +++ b/nodecg-io-mqtt-client/extension/index.ts @@ -72,6 +72,7 @@ class MQTTClientService extends ServiceBundle Date: Sun, 17 Oct 2021 09:13:38 +0200 Subject: [PATCH 08/11] Removed unnecessary lines --- samples/mqtt-client/extension/index.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/samples/mqtt-client/extension/index.ts b/samples/mqtt-client/extension/index.ts index 3af23185c..1baca21a2 100644 --- a/samples/mqtt-client/extension/index.ts +++ b/samples/mqtt-client/extension/index.ts @@ -3,10 +3,9 @@ import { requireService } from "nodecg-io-core"; import { MQTTClientServiceClient } from "nodecg-io-mqtt-client"; module.exports = function (nodecg: NodeCG) { - nodecg.log.info("Sample bundle for websocket-client started"); + nodecg.log.info("Sample bundle for mqtt-client started"); const service = requireService(nodecg, "mqtt-client"); - let interval: NodeJS.Timeout | undefined; service?.onAvailable((client) => { nodecg.log.info("Client has been updated, waiting for messages."); @@ -18,8 +17,5 @@ module.exports = function (nodecg: NodeCG) { service?.onUnavailable(() => { nodecg.log.info("Client has been unset."); - if (interval) { - clearInterval(interval); - } }); }; From 86c2b0f30ae91c1a6313809b76614bf8c20b9bb9 Mon Sep 17 00:00:00 2001 From: Nils Witt Date: Sun, 17 Oct 2021 13:19:57 +0200 Subject: [PATCH 09/11] Apply suggestions from code review Co-authored-by: Daniel Huber --- nodecg-io-mqtt-client/extension/index.ts | 4 +--- nodecg-io-mqtt-client/package.json | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/nodecg-io-mqtt-client/extension/index.ts b/nodecg-io-mqtt-client/extension/index.ts index ab0408f82..15296f154 100644 --- a/nodecg-io-mqtt-client/extension/index.ts +++ b/nodecg-io-mqtt-client/extension/index.ts @@ -44,9 +44,7 @@ export class MQTTClientServiceClient { } onClose(func: () => void): void { - this.client.on("close", () => { - return func(); - }); + this.client.on("close", func); } onMessage(func: (topic: string, message: Buffer) => void): void { diff --git a/nodecg-io-mqtt-client/package.json b/nodecg-io-mqtt-client/package.json index f9fd91f95..300155a66 100644 --- a/nodecg-io-mqtt-client/package.json +++ b/nodecg-io-mqtt-client/package.json @@ -2,7 +2,7 @@ "name": "nodecg-io-mqtt-client", "version": "0.2.0", "description": "Allows you to connect with an MQTT server.", - "homepage": "https://nodecg.io/RELEASE", + "homepage": "https://nodecg.io/RELEASE/samples/mqtt-client", "author": { "name": "Nils Witt", "url": "https://github.com/Nils-witt" From 8f134a4a8f41c1f6ce83e8adb9e81ec78b470845 Mon Sep 17 00:00:00 2001 From: Nils Witt Date: Sun, 17 Oct 2021 15:46:06 +0200 Subject: [PATCH 10/11] Removed try catch and shortened callbacks --- nodecg-io-mqtt-client/extension/index.ts | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/nodecg-io-mqtt-client/extension/index.ts b/nodecg-io-mqtt-client/extension/index.ts index 15296f154..0ac78664c 100644 --- a/nodecg-io-mqtt-client/extension/index.ts +++ b/nodecg-io-mqtt-client/extension/index.ts @@ -26,9 +26,7 @@ export class MQTTClientServiceClient { this.client.end(); reject(err.message); }); - this.client.on("connect", () => { - resolve(); - }); + this.client.on("connect", resolve); this.once = this.client.once; this.on = this.client.on; @@ -47,10 +45,8 @@ export class MQTTClientServiceClient { this.client.on("close", func); } - onMessage(func: (topic: string, message: Buffer) => void): void { - this.client.on("message", (topic, message) => { - return func(topic, message); - }); + onMessage(func: (topic: string, message: Buffer) => void): MqttClient { + return this.client.on("message", func); } onError(func: (error: Error) => void): void { @@ -66,15 +62,11 @@ module.exports = (nodecg: NodeCG) => { class MQTTClientService extends ServiceBundle { async validateConfig(config: MQTTClientServiceConfig): Promise> { - this.nodecg.log.info("validation"); const client = new MQTTClientServiceClient(); - try { - await client.connect(config.address, config.username, config.password); - client.close(); - return emptySuccess(); - } catch (e) { - return error(e); - } + + await client.connect(config.address, config.username, config.password); + client.close(); + return emptySuccess(); } async createClient(config: MQTTClientServiceConfig): Promise> { From 240e33ca3ae60e7aa6a2d06c9a51609ffd4c0054 Mon Sep 17 00:00:00 2001 From: Daniel Huber Date: Sun, 17 Oct 2021 16:33:40 +0200 Subject: [PATCH 11/11] Harmonize event handler shortcuts in mqtt service --- nodecg-io-mqtt-client/extension/index.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/nodecg-io-mqtt-client/extension/index.ts b/nodecg-io-mqtt-client/extension/index.ts index 0ac78664c..ed2b57402 100644 --- a/nodecg-io-mqtt-client/extension/index.ts +++ b/nodecg-io-mqtt-client/extension/index.ts @@ -41,18 +41,16 @@ export class MQTTClientServiceClient { }); } - onClose(func: () => void): void { - this.client.on("close", func); + onClose(func: () => void): MqttClient { + return this.client.on("close", func); } onMessage(func: (topic: string, message: Buffer) => void): MqttClient { return this.client.on("message", func); } - onError(func: (error: Error) => void): void { - this.client.on("error", (error: Error) => { - return func(error); - }); + onError(func: (error: Error) => void): MqttClient { + return this.client.on("error", func); } }