diff --git a/nodecg-io-mqtt-client/extension/index.ts b/nodecg-io-mqtt-client/extension/index.ts new file mode 100644 index 000000000..ed2b57402 --- /dev/null +++ b/nodecg-io-mqtt-client/extension/index.ts @@ -0,0 +1,87 @@ +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; + topics: [string]; + username?: string; + password?: string; +} + +export class MQTTClientServiceClient { + client: MqttClient; + once: (event: string, cb: () => void) => void; + close: () => 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) => { + 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(topics: string[]): void { + topics.forEach((topic: string) => { + this.client.subscribe(topic); + }); + } + + 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): MqttClient { + return this.client.on("error", func); + } +} + +module.exports = (nodecg: NodeCG) => { + new MQTTClientService(nodecg, "mqtt-client", __dirname, "../mqtt-schema.json").register(); +}; + +class MQTTClientService extends ServiceBundle { + async validateConfig(config: MQTTClientServiceConfig): Promise> { + const client = new MQTTClientServiceClient(); + + await client.connect(config.address, config.username, config.password); + client.close(); + return emptySuccess(); + } + + async createClient(config: MQTTClientServiceConfig): Promise> { + const client = new MQTTClientServiceClient(); + await client.connect(config.address, config.username, config.password); + client.subscribe(config.topics); + 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..f4969615d --- /dev/null +++ b/nodecg-io-mqtt-client/mqtt-schema.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "address": { + "type": "string", + "description": "The address of the server" + }, + "password": { + "type": "string", + "description": "The password to connect to the server" + }, + "username": { + "type": "string", + "description": "The username to connect to the server" + }, + "topics": { + "type": "array", + "description": "Array of topic you want to subscribe to" + } + }, + "required": ["address", "topics"] +} diff --git a/nodecg-io-mqtt-client/package.json b/nodecg-io-mqtt-client/package.json new file mode 100644 index 000000000..300155a66 --- /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 an MQTT server.", + "homepage": "https://nodecg.io/RELEASE/samples/mqtt-client", + "author": { + "name": "Nils Witt", + "url": "https://github.com/Nils-witt" + }, + "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..1baca21a2 --- /dev/null +++ b/samples/mqtt-client/extension/index.ts @@ -0,0 +1,21 @@ +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 mqtt-client started"); + + const service = requireService(nodecg, "mqtt-client"); + + service?.onAvailable((client) => { + nodecg.log.info("Client has been updated, waiting for messages."); + + client.onMessage((topic: string, message: Buffer) => { + nodecg.log.info(`recieved message "${message.toString()}" "${topic}"`); + }); + }); + + service?.onUnavailable(() => { + nodecg.log.info("Client has been unset."); + }); +}; 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" +}