UNPKG

@mangar2/mqttservice

Version:

communicates with a MQTT-Style HTTP broker

188 lines (164 loc) 6.2 kB
/** * @license * This software is licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. It is furnished * "as is", without any support, and with no warranty, express or implied, as to its usefulness for * any purpose. * * @author Volker Böhm * @copyright Copyright (c) 2020 Volker Böhm */ 'use strict' const LogFilter = require('@mangar2/logfilter') const Message = require('@mangar2/message') const Server = require('@mangar2/httpservice').HttpServer const mqttVersion = require('@mangar2/mqttversion') const Callbacks = require('@mangar2/callbacks') /** * @callback ProcessMessage * @param {Object} message the message received * @param {0|1|2} qos the quality of service information * @param {0|1} dup flag signaling duplicates */ /** * Creates a new server receiving messages * Supports registering a callback with on('publish', (payload, qos) => {} ) called for each received message * @param {number} listenerPort port to listen on * @param {Object} logSettings logging settings * @param {number} qos2PubrelTimeoutInSeconds time to delete qos2 related packageid´s from the qos2 queue. * This will happen, if a pubrel does not follow the publish call for a this amount of seconds */ class OnPublish { constructor (listenerPort, logSettings, qos2PubrelTimeoutInSeconds = 7200) { this._logFilter = new LogFilter() this._logFilter.changePattern(logSettings) this._server = new Server(listenerPort) this._callbacks = new Callbacks(['publish']) this._qos2Queue = {} this._qos2PubrelTimeoutInMilliseconds = qos2PubrelTimeoutInSeconds * 1000 let result this._server.on('PUT', (payload, headers, path, res) => { if (path === '/publish') { result = this.onPublish(JSON.parse(payload), headers, res) } else if (path === '/pubrel') { result = this.onPubrel(JSON.parse(payload), headers, res) } else if (path === '/log') { result = this.onLog(JSON.parse(payload), headers, res) } else { throw ('illegal interface ' + path) } }) return result } /** * Port the server uses, only defined after listen has been called! * @type {number} */ get port() { return this._server.address ? this._server.address.port : undefined } /** * Logging filter * @type {LogFilter} */ get logFilter () { return this._logFilter } /** * Starts to listen and wait for input */ listen () { this._server.listen() } /** * Sets a callback. Usually, there is no need to set anything here * @param {string} event event name, supported 'publish' * @param {ProcessMessage} callback callback to be called, to process messages received */ on (event, callback) { this._callbacks.on(event, callback) } /** * @private * @description * Deletes entries in the "wait for pubrel queue" that are too old */ deleteOldQos2QueueEntries () { const now = new Date().getTime() for (const entry in this._qos2Queue) { if (this._qos2Queue[entry].time + this._qos2PubrelTimeoutInMilliseconds < now) { delete this._qos2Queue[entry] } else { break } } } /** * @private * @description * Receives a pubrel message * @param {Object} payload payload of the message * @param {Object} headers headers of the message * @param {Object} res http result structure */ onPubrel (payload, headers, res) { const result = mqttVersion.onPubrel(headers) delete this._qos2Queue[result.packetid] this.deleteOldQos2QueueEntries() res.writeHead(result.statusCode, result.headers) res.end(result.payload) } /** * @private * @description * Stores a qos2 publish message to check for a pubrel call * @param {string} topic topic of the message * @param {number} packetid id of the packet */ rememberMessage (topic, packetid) { this._qos2Queue[packetid] = { time: new Date().getTime(), topic } } /** * @private * @description * Receives a published message * @param {Object} payload received {token, message} * @param {Object} headers headers of the message * @param {Object} res http result structure */ onPublish (payload, headers, res) { const message = payload.message ? payload.message : payload Message.validate(message) const dup = headers.dup === '1' || headers.dup === 'true' const packetid = headers.version === '1.0' ? headers.packetid : headers.id this.logFilter.condLogMessage('received', message, Number(headers.qos), dup) const qos2PacketPublishedBefore = dup && this._qos2Queue[packetid] !== undefined if (!qos2PacketPublishedBefore) { if (Number(headers.qos) === 2) { this.rememberMessage(message.topic, packetid) } const cleanMessage = new Message(message.topic, message.value, message.reason) this._callbacks.invokeCallback('publish', cleanMessage, headers.qos, dup) } const result = mqttVersion.receive(headers) res.writeHead(result.statusCode, result.headers) res.end(result.payload) } /** * @private * @description * Receives a logging request * @param {Object} payload content received * @param {Object} headers headers of the message * @param {Object} res http result structure */ onLog (payload, headers, res) { this._logFilter.changePattern(payload) res.writeHead(204, { 'Content-Type': 'application/json' }) res.end('') } /** * Closes all connections */ close () { this._server.close() } } module.exports = OnPublish