@mangar2/mqttservice
Version:
communicates with a MQTT-Style HTTP broker
188 lines (164 loc) • 6.2 kB
JavaScript
/**
* @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