import { Observable, Subject } from 'rxjs'; import SenML from '../senML'; import Utils from '../utils'; import { IMqttClient, MqttConnect } from '../mqtt/IMqttClient'; import { CloudMessageValue } from '../client/ICloudClient'; import { IConnection, CloudMessage, ConnectionOptions } from './IConnection'; const BaseConnectionOptions: Partial = { clean: true, keepalive: 30, properties: {}, protocolVersion: 4, connectTimeout: 30000, }; export class Connection implements IConnection { public token: string; public messages: Observable; private _client: IMqttClient; private get client(): IMqttClient { return this._client; } private set client(client: IMqttClient) { this._client = client; const messages = (this.messages = new Subject()); this._client.on('message', (topic, msg) => { if (topic.indexOf('/s/o') > -1) messages.next({ topic, value: msg.toString() }); else this.messagesFrom(topic, msg).forEach((m) => messages.next(m)); }); } public static async From( host: string, port: string | number, token: string, mqttConnect: MqttConnect ): 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)'); const userId = Utils.decode(token)['http://arduino.cc/id']; const options = { clientId: `${userId}:${new Date().getTime()}`, username: userId, password: token, host, port: Number(port), }; const connection = new Connection(); connection.client = await mqttConnect(`wss://${host}:${port}/mqtt`, { ...BaseConnectionOptions, ...options, }); connection.token = token; return connection; } public on(event: any, cb: any): IConnection { this.client.on(event, cb); return this; } public end(force?: boolean, opts?: Record, cb?: Function): IConnection { this.client.end(force, opts, cb); return this; } public reconnect(opts?: object): IConnection { this.client.reconnect(opts); return this; } public unsubscribe(topic: string | string[], opts?: any, callback?: any): IConnection { this.client.subscribe(topic, opts, callback); return this; } public publish(topic: any, message: any, opts?: any, callback?: any): IConnection { this.client.publish(topic, message, opts, callback); return this; } public subscribe(topic: any, callback?: any): IConnection { this.client.subscribe(topic, callback); return this; } private messagesFrom(topic: string, msg: Buffer): CloudMessage[] { let current = ''; let attribute = ''; let previous = ''; let valueToSend: CloudMessageValue = {}; const messages: CloudMessage[] = []; const properties = SenML.CBOR.decode(Utils.toArrayBuffer(msg)); properties.forEach((p) => { const value = SenML.valueFrom(p); [current, attribute] = SenML.nameFrom(p).split(':'); if (previous === '') previous = current; if (previous !== current) { messages.push({ topic, propertyName: previous, value: valueToSend }); previous = current; valueToSend = {}; } if (attribute) valueToSend[attribute] = value; else valueToSend = value; }); // Checking if valueToSend is NOT {} if (Utils.isNotAnEmptyObject(valueToSend)) { messages.push({ topic, propertyName: current, value: valueToSend }); } return messages; } }