diff --git a/README.md b/README.md index 47702a1..8566f05 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ User ⇄ XMPP client ⇄ XMPP Server ⇄ **XMPP Bot** ⇄ REST A ## Key features - Call outgoing webhook on XMPP incoming messages from user chat or group chat (Multi-user chat "MUC"), - - Send message templates (with values to apply to variables in that template) to user or room (MUC) on incoming authorized (basic or bearer) webhook. ## Installation @@ -86,29 +85,24 @@ User ⇄ XMPP client ⇄ XMPP Server ⇄ **XMPP Bot** ⇄ REST A ### Logger - `level` log4js level (all < trace < debug < info < warn < error < fatal < mark < off) - - `file`, `console` and `stdout` define log appenders (see [log4js doc](https://log4js-node.github.io/log4js-node/appenders.html)) ### Webhooks listener - `path` and `port` define the listening endpoint - - `ssl` define key and certificat location and port used for exposing in https, make sure that user of the process is allowed to read cert - - `users` is an array of user/password for basic authentication - - `accessLog` define the listener logger ### XMPP Server -- `host` and `port` define XMPP server -- `jid` and `password` define XMPP "bot" user credentials +- `service` and `domain` define XMPP server +- `username` and `password` define XMPP "bot" user credentials - `rooms` list rooms (and optionnal password) where bot will listen ### Incoming webhooks (list) - `path` is the webhook key:a POST request on this path will trigger corresponding `action` - - `action` among enumeration: - `send_xmpp_message` will send message (`message` in request body) to `destination` (from request body) ; if `destination` is found in `config.xmppServer.rooms` array, message will send as a groupchat). Request exemple: @@ -125,12 +119,11 @@ User ⇄ XMPP client ⇄ XMPP Server ⇄ **XMPP Bot** ⇄ REST A } ``` - - `send_xmpp_template` will send template with merged variables (using JMESPath) to `destination` (user or room if `sendToGroup` set to true) + - `send_xmpp_template` will send template with merged variables (using JMESPath) to `destination` (user or room if `type` set to `chat` or `groupchat`) ### XMPP hooks (list) - `room` is the XMPP hook key: an incoming groupchat (or chat) from this room (or this user) will trigger corresponding `action` - - `action` among enumeration: - `outgoing_webhook` will execute a request to corresponding webhook with `args` as webhook code diff --git a/lib/config/config.json.dist b/lib/config/config.json.dist index d67b61e..8fe638e 100644 --- a/lib/config/config.json.dist +++ b/lib/config/config.json.dist @@ -41,9 +41,9 @@ } }, "xmppServer": { - "host": "domain-xmpp.ltd", - "port": 5222, - "jid": "bot@domain-xmpp.ltd", + "service": "xmpps://domain-xmpp.ltd:5223", + "domain": "domain-xmpp.ltd", + "username": "bot@domain-xmpp.ltd", "password": "botPass", "rooms": [ { @@ -62,14 +62,14 @@ "action": "send_xmpp_template", "args": { "destination": "grafana@conference.domain-xmpp.ltd", - "sendToGroup": true + "type": "groupchat" }, "template": "${title}\r\n${message}\r\n${evalMatches[].metric}: ${evalMatches[].value}\r\n${imageUrl}" } ], "xmppHooks": [ { - "room": "bot", + "room": "bot@domain-xmpp.ltd", "action": "outgoing_webhook", "args": ["w1"] }, @@ -91,4 +91,4 @@ "bearer": null } ] -} \ No newline at end of file +} diff --git a/lib/error/index.js b/lib/error/index.js index 3f41e3f..474dc32 100644 --- a/lib/error/index.js +++ b/lib/error/index.js @@ -14,7 +14,11 @@ module.exports = (logger, xmpp) => { nodeCleanup(function (exitCode, signal) { logger.warn(`Received ${exitCode}/${signal} (application is closing), disconnect from XMPP server`) try { - xmpp.disconnect() + xmpp.close() + .then(logger.debug('Connection successfully closed')) + .catch((error) => { + logger.error('Error during XMPP disconnection', error) + }) } catch (error) { logger.error('Error during XMPP disconnection: ' + error.message) } diff --git a/lib/outgoing/index.js b/lib/outgoing/index.js index 38cd535..814468b 100644 --- a/lib/outgoing/index.js +++ b/lib/outgoing/index.js @@ -9,7 +9,7 @@ * @license AGPL-3.0+ */ -module.exports = (logger, config, xmpp, user, destination, message, sendToGroup, code, callback = () => {}) => { +module.exports = (logger, config, xmpp, user, destination, message, type, code, callback = () => {}) => { let webhook = config.getOutgoingWebhook(code) if (!webhook) { logger.warn(`There is no webhook with code "${code}"`) @@ -79,7 +79,7 @@ module.exports = (logger, config, xmpp, user, destination, message, sendToGroup, logger.trace('Response:', body) if (body && typeof (body) === 'object' && 'reply' in body === true) { logger.debug(`There is a reply to send back in chat ${destination}: ${body.reply}`) - xmpp.send(destination, body.reply, sendToGroup) + xmpp.send(destination, body.reply, type) callback(null, `Message sent. There is a reply to send back in chat ${destination}: ${body.reply}`, null) return } diff --git a/lib/webhook/index.js b/lib/webhook/index.js index 9acfd56..dcf57c9 100644 --- a/lib/webhook/index.js +++ b/lib/webhook/index.js @@ -82,11 +82,11 @@ module.exports = (logger, config, xmpp) => { let message = req.body.message // check if destination is a group chat - const sendToGroup = config.xmpp.rooms.some((room) => room.id === destination) + const type = config.xmpp.rooms.some((room) => room.id === destination) ? 'groupchat' : 'chat' // send message - logger.trace(`Send to ${destination} (group:${sendToGroup}) following message :\r\n${message}`) - xmpp.send(destination, message, sendToGroup) + logger.trace(`Send to ${destination} (group:${type}) following message :\r\n${message}`) + xmpp.send(destination, message, type) return res.status(200).send({ 'status': 'ok', destination }) case 'send_xmpp_template': @@ -99,8 +99,8 @@ module.exports = (logger, config, xmpp) => { logger.trace(`Arguments: ${webhook.args}`) // send message - logger.trace(`Send to ${webhook.args.destination} (group:${webhook.args.sendToGroup}) following message :\r\n${msg}`) - xmpp.send(webhook.args.destination, msg, webhook.args.sendToGroup) + logger.trace(`Send to ${webhook.args.destination} (group:${webhook.args.type}) following message :\r\n${msg}`) + xmpp.send(webhook.args.destination, msg, webhook.args.type) return res.status(200).send('ok') default: diff --git a/lib/xmpp/index.js b/lib/xmpp/index.js index 69207bf..3325c90 100644 --- a/lib/xmpp/index.js +++ b/lib/xmpp/index.js @@ -6,81 +6,140 @@ * @exports xmpp * @file This files defines the XMPP bot * @author nioc - * @since 1.0.0 + * @since 2.0.0 * @license AGPL-3.0+ */ module.exports = (logger, config) => { - const xmpp = require('simple-xmpp') + const { client, xml, jid } = require('@xmpp/client') const outgoing = require('../outgoing') this.jid = null + // declare send chat/groupchat function + this.send = async (to, message, type) => { + logger.debug(`Send ${type} message to ${to}: '${message}'`) + const stanza = xml( + 'message', { + to, + type + }, + xml( + 'body', { + }, + message) + ) + await xmppClient.send(stanza) + } + + // declare close function + this.close = async () => { + await xmppClient.stop() + } + + // create XMPP client + const xmppClient = client(config.xmpp) + // handle connection - xmpp.on('online', function (data) { - logger.info(`XMPP connected on ${config.xmpp.host}:${config.xmpp.port} with JID: ${data.jid.user}`) - this.jid = data.jid.user + xmppClient.on('online', (address) => { + logger.info(`XMPP connected on ${config.xmpp.service} with JID: ${address.toString()}`) + this.jid = address + // send presence + xmppClient.send(xml('presence')) + .then(logger.debug('presence sent')) + .catch((error) => { + logger.warn('presence returned following error:', error) + }) // join rooms config.xmpp.rooms.forEach(function (room) { - logger.debug(`Join room: ${room.id} ('${room.id}/${data.jid.user}')`) - xmpp.join(room.id + '/' + data.jid.user, room.password) + let occupantJid = room.id + '/' + address.local + logger.debug(`Join room: ${room.id} ('${occupantJid}')`) + const stanza = xml( + 'presence', { + to: occupantJid + }, + xml( + 'x', { + xmlns: 'http://jabber.org/protocol/muc' + } + ) + ) + xmppClient.send(stanza) logger.info(`Joined room: ${room.id}`) }) }) - // handle direct message - xmpp.on('chat', function (from, message) { - logger.info(`Incoming chat message from ${from}`) - logger.debug(`Message: "${message}"`) - let xmppHook = config.getXmppHookAction(this.jid) - if (!xmppHook) { - logger.error(`There is no action for incoming chat message to ${this.jid}`) + // handle stanzas + xmppClient.on('stanza', stanza => { + if (!stanza.is('message')) { + // not a message, do nothing return } - switch (xmppHook.action) { - case 'outgoing_webhook': - logger.debug(`Call outgoing webhook: ${xmppHook.args[0]}`) - outgoing(logger, config, xmpp, from, from, message, false, xmppHook.args[0]) - break - default: + let type = stanza.attrs.type + switch (type) { + case 'chat': + case 'groupchat': + let body = stanza.getChild('body') + if (!body) { + // empty body, do nothing + return + } + let fromJid = jid(stanza.attrs.from) + // for chat, "to" and "replyTo" must be something like "user@domain.ltd", "from" is local part "user" + let to = this.jid.bare() + let from = fromJid.local + let replyTo = fromJid.bare() + if (type === 'groupchat') { + // for groupchat, "to" and "replyTo" is conference name, "from" is nickname + to = fromJid.bare() + from = fromJid.getResource() + replyTo = to + if (from === this.jid.local || stanza.getChild('delay')) { + // message from bot or old message, do nothing + return + } + } + let message = body.text() + logger.info(`Incoming ${type} message from ${from} (${fromJid.toString()}) to ${to}`) + logger.debug(`Message: "${message}"`) + let xmppHook = config.getXmppHookAction(to.toString()) + if (!xmppHook) { + logger.error(`There is no action for incoming ${type} message to: "${to}"`) + return + } + switch (xmppHook.action) { + case 'outgoing_webhook': + logger.debug(`Call outgoing webhook: ${xmppHook.args[0]}`) + outgoing(logger, config, this, from.toString(), replyTo.toString(), message, type, xmppHook.args[0]) + break + default: + break + } break } }) - // handle group message - xmpp.on('groupchat', function (conference, from, message, stamp, delay) { - // logger.trace(`Get following group message: "${message}" in ${conference} from ${from}. stamp = ${stamp} - delay = `, delay) - if (from === this.jid || stamp !== null) { - // message from bot, do nothing - return - } - logger.info(`Incoming groupchat message from ${from} in ${conference}`) - logger.debug(`Message: "${message}"`) - let xmppHook = config.getXmppHookAction(conference) - if (!xmppHook) { - logger.error(`There is no action for incoming groupchat message from conference: "${conference}"`) - return - } - switch (xmppHook.action) { - case 'outgoing_webhook': - logger.debug(`Call outgoing webhook: ${xmppHook.args[0]}`) - outgoing(logger, config, xmpp, from, conference, message, true, xmppHook.args[0]) - break - default: - break - } + // handle status + xmppClient.on('status', (status) => { + logger.trace(`Status changed to ${status}`) }) + // trace input/output + // xmppClient.on('input', (input) => { + // logger.trace('<<<<', input) + // }) + // xmppClient.on('output', (output) => { + // logger.trace('>>>', output) + // }) + // handle error - xmpp.on('error', function (err) { - logger.error(err) + xmppClient.on('error', (err) => { + logger.error('XMPP client encountered following error:', err.message) process.exit(99) }) // connect - xmpp.connect(config.xmpp) + xmppClient.start() + .catch(logger.error) - // get roster - xmpp.getRoster() - - return xmpp + return this } diff --git a/package-lock.json b/package-lock.json index 0cd53bc..41a0d17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -163,28 +163,219 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "@xmpp/jid": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@xmpp/jid/-/jid-0.0.2.tgz", - "integrity": "sha1-DVKMqdWNr8gzZlVk/+YvMyoxZ/I=" - }, - "@xmpp/streamparser": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@xmpp/streamparser/-/streamparser-0.0.6.tgz", - "integrity": "sha1-EYAz6p23yGoctGED8mnr/3n28eo=", + "@xmpp/client": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/client/-/client-0.8.0.tgz", + "integrity": "sha512-PaQt1D18fmpLk6UGqpCvuy0eTsWASMgowS8IAsUbDZV0bTz0fV0CjVe1tHVEy45O2myIadk14UX88onSmsRvtQ==", "requires": { - "@xmpp/xml": "^0.1.3", - "inherits": "^2.0.3", - "ltx": "^2.5.0" + "@xmpp/client-core": "^0.8.0", + "@xmpp/iq": "^0.8.0", + "@xmpp/middleware": "^0.8.0", + "@xmpp/reconnect": "^0.8.0", + "@xmpp/resolve": "^0.8.0", + "@xmpp/resource-binding": "^0.8.0", + "@xmpp/sasl": "^0.8.0", + "@xmpp/sasl-anonymous": "^0.8.0", + "@xmpp/sasl-plain": "^0.8.0", + "@xmpp/sasl-scram-sha-1": "^0.8.0", + "@xmpp/session-establishment": "^0.8.0", + "@xmpp/starttls": "^0.8.0", + "@xmpp/stream-features": "^0.8.0", + "@xmpp/tcp": "^0.8.0", + "@xmpp/tls": "^0.8.0", + "@xmpp/websocket": "^0.8.0" + } + }, + "@xmpp/client-core": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/client-core/-/client-core-0.8.0.tgz", + "integrity": "sha512-RL7EDhQPLkWtX75p4UyAuyxsnBsOzYg+vq5Lkyo0CAaO6D5Zk7sqGlpYUHFsUp2akDDOjgsDZZuhdzJPtV4mCg==", + "requires": { + "@xmpp/connection": "^0.8.0", + "@xmpp/jid": "^0.8.0", + "@xmpp/xml": "^0.8.0" + } + }, + "@xmpp/connection": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/connection/-/connection-0.8.0.tgz", + "integrity": "sha512-8KqdSP0jYnnugvZeEydO8L/Geygj/tEJAFSrBqWX0aNjgMDvoKhZsdvY0gk4o8pvXZCzQN7K8r8jvPoPpxAnPA==", + "requires": { + "@xmpp/error": "^0.8.0", + "@xmpp/events": "^0.8.0", + "@xmpp/jid": "^0.8.0", + "@xmpp/xml": "^0.8.0" + } + }, + "@xmpp/connection-tcp": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/connection-tcp/-/connection-tcp-0.8.0.tgz", + "integrity": "sha512-gyT05MmrynqRe98G1WKYmHLginIAXPlU0atuvHJ/kultccQazZ2sXV7Ci43Qpv/UBNLCTCYu5Fq1UXWs4jwUfA==", + "requires": { + "@xmpp/connection": "^0.8.0", + "@xmpp/xml": "^0.8.0" + } + }, + "@xmpp/error": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/error/-/error-0.8.0.tgz", + "integrity": "sha512-xs5eZg5gDckLFlfnIxzFdlUAAAMnWdGHNQkBGar+W93Z4BCVeFh6Z64xtJW6tv0ip+8oY4HB1mqiCerump5ZSA==" + }, + "@xmpp/events": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/events/-/events-0.8.0.tgz", + "integrity": "sha512-ZEdIOUVsDqfCVG2Y3crUDYLAedBW+6ND6LCkwRyaWlOtDAPQiPzJGGFx9upcoyWtIoe1byh/JOjv11RcqQhKYQ==", + "requires": { + "events": "^3.0.0" + } + }, + "@xmpp/id": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/id/-/id-0.8.0.tgz", + "integrity": "sha512-kar4sAz3tFu5oSChjAhSX7TI6YoSIH9VWqIJLn93qtq/4hZc+nuIkNfYhdKS9ABesnFmqEE/ncZ1Kh2QTNEzOQ==" + }, + "@xmpp/iq": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/iq/-/iq-0.8.0.tgz", + "integrity": "sha512-vZzb7LvgGL4AvalBmBl0iNZuoO7pGUqir7gbueSDytNpPMwLI1dyziuzVwh9/RStEtF1BYQuC3lW/rvGB9Itog==", + "requires": { + "@xmpp/events": "^0.8.0", + "@xmpp/id": "^0.8.0", + "@xmpp/middleware": "^0.8.0", + "@xmpp/xml": "^0.8.0" + } + }, + "@xmpp/jid": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/jid/-/jid-0.8.0.tgz", + "integrity": "sha512-Pwwqmz3zG/MMPLuWkVlNk54Wrh4kVa651//uQWkzyPYdzlXFt/nwC14PvSP16kGypI+J7xBaQEzqbzyu8Dve5g==" + }, + "@xmpp/middleware": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/middleware/-/middleware-0.8.0.tgz", + "integrity": "sha512-7IlFXCcya9SDKobDtDLgAvtnPJZYoGOgzW9lEIknz9sDMKqh2mwl+64G3Vs8h7hSBOp+MFVrQV+2q23ETKEnMw==", + "requires": { + "@xmpp/error": "^0.8.0", + "@xmpp/jid": "^0.8.0", + "@xmpp/xml": "^0.8.0", + "koa-compose": "^4.1.0" + } + }, + "@xmpp/reconnect": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/reconnect/-/reconnect-0.8.0.tgz", + "integrity": "sha512-nDam7dUk+wYOuFIj8logdL6vBmmVJBCY7DczIhg3YXtBy8VaceMvPJl13EEG/OoNzm7ZmxzDjfp3gIul8fY+xQ==", + "requires": { + "@xmpp/events": "^0.8.0" + } + }, + "@xmpp/resolve": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/resolve/-/resolve-0.8.0.tgz", + "integrity": "sha512-L2jX6gOtAh8hGF19f6VI3HaRHrQ4Vy4+8YUWO8sIUpx7YdIg4L9dt2ZSj6CuFHwok1h/Kbtil0v8bHnCEuZDCQ==", + "requires": { + "@xmpp/connection": "^0.8.0", + "@xmpp/xml": "^0.8.0", + "node-fetch": "^2.3.0" + } + }, + "@xmpp/resource-binding": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/resource-binding/-/resource-binding-0.8.0.tgz", + "integrity": "sha512-4sbmBIuIAC5mOJoHjiOpsuEyro23LB4I5u6F8n9OE1sRW0l1LdiwtaSq3Qzuc0YIWF1CWF9CjCboxWdyjD5EVA==", + "requires": { + "@xmpp/xml": "^0.8.0" + } + }, + "@xmpp/sasl": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/sasl/-/sasl-0.8.0.tgz", + "integrity": "sha512-fVlzuTJnC+0rBnJ7HnlqYayl0ZvfR+q2yxh6VfqUPZw0eV4zwKy15eoU8txiLbWT/ilm4bo2zid7CCOYGPd5zw==", + "requires": { + "@xmpp/error": "^0.8.0", + "@xmpp/xml": "^0.8.0", + "js-base64": "^2.5.1", + "saslmechanisms": "^0.1.1" + } + }, + "@xmpp/sasl-anonymous": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/sasl-anonymous/-/sasl-anonymous-0.8.0.tgz", + "integrity": "sha512-2V+pmreLdFvY0dX8XTliSqC/CyToYyDstKUAD637juf0Mjqy7UmGgCLQQ2hH/t5J1wWxfeHgxk16zdjj7WyUkg==", + "requires": { + "sasl-anonymous": "^0.1.0" + } + }, + "@xmpp/sasl-plain": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/sasl-plain/-/sasl-plain-0.8.0.tgz", + "integrity": "sha512-IJFCf6YcVCp+ZWVIFzpM0wJGnbjTwbjY+CO+5KSBsIrh94uNT1cNlqs+9aiT3jqf8UbWy1D08+6mi7IWxytR4g==", + "requires": { + "sasl-plain": "^0.1.0" + } + }, + "@xmpp/sasl-scram-sha-1": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/sasl-scram-sha-1/-/sasl-scram-sha-1-0.8.0.tgz", + "integrity": "sha512-PrpMEdnIPAjSmEOukB2oOksoWrbV/zfwPNIDKvTW8a73rcRxGVvx0032q1IXQYjxrogSL8DyVmuKvpFwUpvEnw==", + "requires": { + "sasl-scram-sha-1": "^1.2.0" + } + }, + "@xmpp/session-establishment": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/session-establishment/-/session-establishment-0.8.0.tgz", + "integrity": "sha512-lvkcszV22/xZp4ZDTGiHf67xXJJ0TQMKpgUO2Amzyufc3qXJ4gP5nfGTNslI+LS3DjrgisRWiK8OTvjLGdIL5A==", + "requires": { + "@xmpp/xml": "^0.8.0" + } + }, + "@xmpp/starttls": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/starttls/-/starttls-0.8.0.tgz", + "integrity": "sha512-mj4Es4wugcDtfTaGjOZey3eI1JI5WMedjP7ahVewfKUOaxbpeojzCjqazXf9/La3rVTamn5qa1MJ2kEo4FxWMw==", + "requires": { + "@xmpp/xml": "^0.8.0" + } + }, + "@xmpp/stream-features": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/stream-features/-/stream-features-0.8.0.tgz", + "integrity": "sha512-3lzWxlPejgFMSjFzSKhAIsY/ub1j5tLPFMvLFTGiXlrArRg/rx4kYITuyOxaQDA9Dty5xP4sBqA4vf4jezKt9w==" + }, + "@xmpp/tcp": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/tcp/-/tcp-0.8.0.tgz", + "integrity": "sha512-ZGkAHrFUEbq66dtSSYl9L2xppn/1qFrUM4fGpLwWaHQwnNxwIZwJGlDTMowszkZ3/J6ZK1YNViLq1htWWv80Ig==", + "requires": { + "@xmpp/connection-tcp": "^0.8.0" + } + }, + "@xmpp/tls": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/tls/-/tls-0.8.0.tgz", + "integrity": "sha512-Vn3YJCclOBWeNjZZPb0Bl0SjbamK8N0UiLic1kdHh8MS7zC+kbbglFKa/ZppF4tqpP5VoycRrEeYHieyz+UMLA==", + "requires": { + "@xmpp/connection": "^0.8.0", + "@xmpp/connection-tcp": "^0.8.0" + } + }, + "@xmpp/websocket": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/websocket/-/websocket-0.8.0.tgz", + "integrity": "sha512-SQMS0IdxNsv/6ywP9G9EZa9lLraJd2KaNnPgjK4yZ/BMavfr38+gvOaX3KIhQC5uV87bV91E9p+T97p5BW0Glw==", + "requires": { + "@xmpp/connection": "^0.8.0", + "@xmpp/xml": "^0.8.0", + "ws": "^7.0.0" } }, "@xmpp/xml": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@xmpp/xml/-/xml-0.1.3.tgz", - "integrity": "sha1-HxQ5nlPkGWiFWGmPbGLnHjmoam4=", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xmpp/xml/-/xml-0.8.0.tgz", + "integrity": "sha512-W0bW5TCMPi2dtndZhzeTzExtz1skXt/fPQFZ9aXg4Y/pniUVo//vfUY4092C8smT7AqiI8jYVEIYh4xqmd2LDQ==", "requires": { - "inherits": "^2.0.3", - "ltx": "^2.6.2" + "ltx": "^2.8.1" } }, "abbrev": { @@ -426,6 +617,11 @@ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", "dev": true }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -447,11 +643,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, - "backoff": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.3.0.tgz", - "integrity": "sha1-7nx+OAk/kuRyhZ22NedlJFT8Ieo=" - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -535,6 +726,11 @@ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "dev": true }, + "bitwise-xor": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/bitwise-xor/-/bitwise-xor-0.0.0.tgz", + "integrity": "sha1-BAqBcrW7jMVisLcRnyMLKhp4Dj0=" + }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -633,11 +829,6 @@ } } }, - "browser-request": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/browser-request/-/browser-request-0.3.3.tgz", - "integrity": "sha1-ns5bWsqJopkyJC4Yv5M975h2zBc=" - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -811,6 +1002,15 @@ "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", "dev": true }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -1060,6 +1260,31 @@ "capture-stack-trace": "^1.0.0" } }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -1574,6 +1799,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" + }, "execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", @@ -3381,6 +3611,11 @@ "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" }, + "js-base64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3466,6 +3701,11 @@ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" + }, "latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", @@ -3518,11 +3758,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, - "lodash.assign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" - }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -3594,11 +3829,18 @@ } }, "ltx": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/ltx/-/ltx-2.8.1.tgz", - "integrity": "sha512-l4H1FS9I6IVqwvIpUHsSgyxE6t2jP7qd/2MeVG1UhmVK6vlHsQpfm2KNUcbdImeE0ai04vl1qTCF4CPCJqhknQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/ltx/-/ltx-2.9.2.tgz", + "integrity": "sha512-llB7HflFhlfsYYT1SAe80elCBO5C20ryLdwPB/A/BZk38hhVeZztDlWQ9uTyvKNPX4aK6sA+JfS1f/mfzp5cxA==", "requires": { - "inherits": "^2.0.1" + "inherits": "^2.0.4" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + } } }, "make-dir": { @@ -3732,7 +3974,8 @@ "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true }, "mixin-deep": { "version": "1.3.2", @@ -4081,39 +4324,10 @@ } } }, - "node-xmpp-client": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/node-xmpp-client/-/node-xmpp-client-3.2.0.tgz", - "integrity": "sha1-r0Un3wzFq9JpDLohOcwezcgeoYk=", - "requires": { - "browser-request": "^0.3.3", - "debug": "^2.2.0", - "md5.js": "^1.3.3", - "minimist": "^1.2.0", - "node-xmpp-core": "^5.0.9", - "request": "^2.65.0", - "ws": "^1.1.1" - } - }, - "node-xmpp-core": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/node-xmpp-core/-/node-xmpp-core-5.0.9.tgz", - "integrity": "sha1-XCjCjtsfs/i+uixnYHd2E/SPNCo=", - "requires": { - "@xmpp/jid": "^0.0.2", - "@xmpp/streamparser": "^0.0.6", - "@xmpp/xml": "^0.1.3", - "debug": "^2.2.0", - "inherits": "^2.0.1", - "lodash.assign": "^4.0.0", - "node-xmpp-tls-connect": "^1.0.1", - "reconnect-core": "https://github.com/dodo/reconnect-core/tarball/merged" - } - }, - "node-xmpp-tls-connect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/node-xmpp-tls-connect/-/node-xmpp-tls-connect-1.0.1.tgz", - "integrity": "sha1-kazkOsJrE4hhsr5HjfnfGdYdxcM=" + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" }, "nodemon": { "version": "1.19.4", @@ -4469,11 +4683,6 @@ "word-wrap": "~1.2.3" } }, - "options": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", - "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" - }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -4720,16 +4929,19 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, - "qbox": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/qbox/-/qbox-0.1.7.tgz", - "integrity": "sha1-6A8NxdCfhp2IghaMP2asjdKEDwI=" - }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -4813,13 +5025,6 @@ "readable-stream": "^2.0.2" } }, - "reconnect-core": { - "version": "https://github.com/dodo/reconnect-core/tarball/merged", - "integrity": "sha512-wZK/v5ZaNaSUs2Wnwh2YSX/Jqv6bQHKNEwojdzV11tByKziR9ikOssf5tvUhx+8/oCBz6AakOFAjZuqPoiRHJQ==", - "requires": { - "backoff": "~2.3.0" - } - }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -4979,6 +5184,15 @@ "glob": "^7.1.3" } }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -5016,6 +5230,32 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sasl-anonymous": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/sasl-anonymous/-/sasl-anonymous-0.1.0.tgz", + "integrity": "sha1-9UTH6CTfKkDZrUczgpVyzI2e1aU=" + }, + "sasl-plain": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/sasl-plain/-/sasl-plain-0.1.0.tgz", + "integrity": "sha1-zxRefAIiK2TWDAgG2c0q5TgEJsw=" + }, + "sasl-scram-sha-1": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sasl-scram-sha-1/-/sasl-scram-sha-1-1.2.1.tgz", + "integrity": "sha1-2I1R/qoP8yDY6x1vx1ZXZT+dzUs=", + "requires": { + "bitwise-xor": "0.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.3", + "randombytes": "^2.0.1" + } + }, + "saslmechanisms": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/saslmechanisms/-/saslmechanisms-0.1.1.tgz", + "integrity": "sha1-R4vhQpUA/PqngL6IszQ87X0qkYI=" + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -5111,6 +5351,15 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -5132,15 +5381,6 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, - "simple-xmpp": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/simple-xmpp/-/simple-xmpp-1.3.1.tgz", - "integrity": "sha512-o0wGVlI8Q4o0qTz6Kylbo1QPOMVn+DA/vyHHZecqcQ+LK4ZWGe3wtRON9QjHAkSyxB36PoagmiUz4pHADau8Mw==", - "requires": { - "node-xmpp-client": "^3.0.0", - "qbox": "0.1.x" - } - }, "sinon": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", @@ -5883,11 +6123,6 @@ } } }, - "ultron": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", - "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" - }, "undefsafe": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", @@ -6224,12 +6459,11 @@ } }, "ws": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", - "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.0.tgz", + "integrity": "sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg==", "requires": { - "options": ">=0.0.5", - "ultron": "1.0.x" + "async-limiter": "^1.0.0" } }, "xdg-basedir": { diff --git a/package.json b/package.json index be3ca1e..0993f13 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "private": true, "dependencies": { + "@xmpp/client": "^0.8.0", "body-parser": "^1.19.0", "express": "^4.17.1", "express-basic-auth": "^1.2.0", @@ -26,8 +27,7 @@ "log4js": "^4.5.1", "morgan": "^1.9.1", "node-cleanup": "^2.1.2", - "request": "^2.88.0", - "simple-xmpp": "^1.3.1" + "request": "^2.88.0" }, "devDependencies": { "chai": "^4.2.0", diff --git a/test/config.json b/test/config.json index eb86d8b..987b767 100644 --- a/test/config.json +++ b/test/config.json @@ -41,9 +41,9 @@ } }, "xmppServer": { - "host": "domain-xmpp.ltd", - "port": 5222, - "jid": "bot@domain-xmpp.ltd", + "service": "xmpps://domain-xmpp.ltd:5223", + "domain": "domain-xmpp.ltd", + "username": "bot@domain-xmpp.ltd", "password": "botPass", "rooms": [ { @@ -62,7 +62,7 @@ "action": "send_xmpp_template", "args": { "destination": "grafana@conference.domain-xmpp.ltd", - "sendToGroup": true + "type": "groupchat" }, "template": "${title}\r\n${message}\r\n${evalMatches[].metric}: ${evalMatches[].value}\r\n${imageUrl}" }, @@ -73,7 +73,7 @@ ], "xmppHooks": [ { - "room": "bot", + "room": "bot@domain-xmpp.ltd", "action": "outgoing_webhook", "args": ["w1"] }, diff --git a/test/outgoing.js b/test/outgoing.js index 0c2c57a..ad06a46 100644 --- a/test/outgoing.js +++ b/test/outgoing.js @@ -54,7 +54,7 @@ describe('Outgoing webhook component', () => { describe('Unkwnow outgoing webhook', () => { it('Should not execute request', (done) => { - Outgoing(logger, config, xmpp, 'user', 'destination', 'message', true, 'code', (error, response, body) => { + Outgoing(logger, config, xmpp, 'user', 'destination', 'message', 'type', 'code', (error, response, body) => { should.not.equal(error, null) sinon.assert.notCalled(reqSpy) done() @@ -64,7 +64,7 @@ describe('Outgoing webhook component', () => { describe('POST with basic authorization and JSON content-type and reply message to XMPP', () => { it('Should send basic authentication and JSON content-type in header and send an XMPP message', (done) => { - Outgoing(logger, config, xmpp, 'user', 'destination', 'This a first message', true, 'w1', (error, response, body) => { + Outgoing(logger, config, xmpp, 'user', 'destination', 'This a first message', 'type', 'w1', (error, response, body) => { should.equal(error, null) sinon.assert.calledOnce(reqSpy) const req = reqSpy.args[0][0] @@ -78,7 +78,7 @@ describe('Outgoing webhook component', () => { const xmppSendArgs = xmppSendStub.args[0] xmppSendArgs[0].should.equal('destination') xmppSendArgs[1].should.equal('This is a reply') - xmppSendArgs[2].should.equal(true) + xmppSendArgs[2].should.equal('type') done() }) }) @@ -86,7 +86,7 @@ describe('Outgoing webhook component', () => { describe('POST with bearer authorization and JSON content-type', () => { it('Should send basic authentication in header', (done) => { - Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', true, 'w2', (error, response, body) => { + Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', 'type', 'w2', (error, response, body) => { should.equal(error, null) sinon.assert.calledOnce(reqSpy) const req = reqSpy.args[0][0] @@ -101,7 +101,7 @@ describe('Outgoing webhook component', () => { describe('POST without authorization', () => { it('Should not send authorization in header and handle 401', (done) => { - Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', true, 'w3', (error, response, body) => { + Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', 'type', 'w3', (error, response, body) => { should.not.equal(error, null) sinon.assert.calledOnce(reqSpy) done() @@ -111,7 +111,7 @@ describe('Outgoing webhook component', () => { describe('POST with HTTP error', () => { it('Should handle error', (done) => { - Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', true, 'w4', (error, response, body) => { + Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', 'type', 'w4', (error, response, body) => { should.not.equal(error, null) sinon.assert.calledOnce(reqSpy) done() diff --git a/test/server.js b/test/server.js index 77493ee..7ff8ccf 100644 --- a/test/server.js +++ b/test/server.js @@ -12,9 +12,10 @@ describe('Server', () => { xmppStub = sinon.stub() webhookStub = sinon.stub() mock('./../lib/xmpp', () => { - let xmpp = {} + this.send = () => {} + this.close = () => {} xmppStub() - return xmpp + return this }) // mock webhook component diff --git a/test/webhook.js b/test/webhook.js index 194c342..4e3bd2f 100644 --- a/test/webhook.js +++ b/test/webhook.js @@ -138,7 +138,7 @@ describe('Webhook component', () => { args.should.have.length(3) args[0].should.equal(options.json.destination) args[1].should.equal(options.json.message) - args[2].should.equal(false) + args[2].should.equal('chat') done() }) }) @@ -165,7 +165,7 @@ describe('Webhook component', () => { args.should.have.length(3) args[0].should.equal('grafana@conference.domain-xmpp.ltd') args[1].should.equal('This is a title\r\nThis is a message\r\nmetric: value\r\nhttps://domain.ltd:port/path/image') - args[2].should.equal(true) + args[2].should.equal('groupchat') done() }) }) diff --git a/test/xmpp.js b/test/xmpp.js index 1f21189..25936e2 100644 --- a/test/xmpp.js +++ b/test/xmpp.js @@ -1,14 +1,15 @@ 'use strict' process.env.NODE_ENV = 'production' -const should = require('chai').should() const sinon = require('sinon') const EventEmitter = require('events').EventEmitter const mock = require('mock-require') +const xml = require('@xmpp/xml') +const jid = require('@xmpp/jid') describe('XMPP component', () => { const simpleXmppEvents = new EventEmitter() - let logger, config, outgoingStub, xmppJoinStub + let logger, config, outgoingStub, xmppSendStub before('Setup', (done) => { // create default logger @@ -20,15 +21,20 @@ describe('XMPP component', () => { // update logger with configuration logger.updateConfig(config.logger) - // mock simple-xmpp module - xmppJoinStub = sinon.stub() - mock('simple-xmpp', { - connect: () => {}, - join: xmppJoinStub, - on: (eventName, callback) => { - simpleXmppEvents.on(eventName, callback) + // mock @xmpp/client module + xmppSendStub = sinon.stub().resolves() + mock('@xmpp/client', { + client: () => { + this.start = async () => {} + this.stop = async () => {} + this.send = xmppSendStub + this.on = (eventName, callback) => { + simpleXmppEvents.on(eventName, callback) + } + return this }, - getRoster: () => {} + xml: require('@xmpp/xml'), + jid: require('@xmpp/jid') }) // mock outgoing @@ -50,19 +56,26 @@ describe('XMPP component', () => { describe('Connect to XMPP server', () => { it('Should connect to XMPP server and join rooms when application start', (done) => { require('./../lib/xmpp')(logger, config) - simpleXmppEvents.emit('online', { jid: { user: 'bot' } }) - sinon.assert.called(xmppJoinStub) + simpleXmppEvents.emit('online', jid('bot@domain-xmpp.ltd/resource')) + sinon.assert.called(xmppSendStub) + // 1 "send" call for presence and n "send" calls for joining rooms let roomsLength = config.xmpp.rooms.length - sinon.assert.callCount(xmppJoinStub, roomsLength) - for (let index = 0; index < roomsLength; index++) { - const args = xmppJoinStub.args[index] - args.should.have.length(2) - args[0].should.equal(config.xmpp.rooms[index].id + '/' + 'bot') - if (config.xmpp.rooms[index].password === null) { - should.equal(args[1], null) - } else { - args[1].should.equal(config.xmpp.rooms[index].password) - } + sinon.assert.callCount(xmppSendStub, roomsLength + 1) + for (let index = 1; index < roomsLength + 1; index++) { + const args = xmppSendStub.args[index] + args.should.have.length(1) + let occupantJid = config.xmpp.rooms[index - 1].id + '/' + 'bot' + const stanza = xml( + 'presence', { + to: occupantJid + }, + xml( + 'x', { + xmlns: 'http://jabber.org/protocol/muc' + } + ) + ) + args[0].should.deep.equal(stanza) } done() }) @@ -70,14 +83,24 @@ describe('XMPP component', () => { describe('Bot receive a message from someone', () => { it('Should trigger outgoing webhook with valid arguments', (done) => { - simpleXmppEvents.emit('chat', 'someone', 'This is the message text') + simpleXmppEvents.emit('stanza', xml( + 'message', { + from: 'someone@domain-xmpp.ltd', + to: 'bot@domain-xmpp.ltd', + type: 'chat' + }, + xml( + 'body', { + }, + 'This is the message text') + )) sinon.assert.calledOnce(outgoingStub) const args = outgoingStub.args[0] args.should.have.length(8) args[3].should.equal('someone') - args[4].should.equal('someone') + args[4].should.equal('someone@domain-xmpp.ltd') args[5].should.equal('This is the message text') - args[6].should.equal(false) + args[6].should.equal('chat') args[7].should.equal('w1') done() }) @@ -85,7 +108,17 @@ describe('XMPP component', () => { describe('Bot receive a message from himself in a room', () => { it('Should not trigger outgoing webhook', (done) => { - simpleXmppEvents.emit('groupchat', 'roomname@conference.domain-xmpp.ltd', 'bot', 'This is the message text', null) + simpleXmppEvents.emit('stanza', xml( + 'message', { + from: 'roomname@conference.domain-xmpp.ltd/bot', + to: 'roomname@conference.domain-xmpp.ltd', + type: 'groupchat' + }, + xml( + 'body', { + }, + 'This is the message text') + )) sinon.assert.notCalled(outgoingStub) done() }) @@ -93,7 +126,17 @@ describe('XMPP component', () => { describe('Bot receive a message in an unknown room', () => { it('Should not trigger outgoing webhook', (done) => { - simpleXmppEvents.emit('groupchat', 'unknownroomname@conference.domain-xmpp.ltd', 'someone', 'This is the message text', null) + simpleXmppEvents.emit('stanza', xml( + 'message', { + from: 'unknownroomname@conference.domain-xmpp.ltd/someone', + to: 'unknownroomname@conference.domain-xmpp.ltd', + type: 'groupchat' + }, + xml( + 'body', { + }, + 'This is the message text') + )) sinon.assert.notCalled(outgoingStub) done() }) @@ -102,6 +145,23 @@ describe('XMPP component', () => { describe('Bot receive an old message in a room', () => { it('Should not trigger outgoing webhook', (done) => { simpleXmppEvents.emit('groupchat', 'roomname@conference.domain-xmpp.ltd', 'someone', 'This is the message text', 'stamp') + simpleXmppEvents.emit('stanza', xml( + 'message', { + from: 'roomname@conference.domain-xmpp.ltd/someone', + to: 'roomname@conference.domain-xmpp.ltd', + type: 'groupchat' + }, + xml( + 'body', { + }, + 'This is the message text'), + xml( + 'delay', { + xmlns: 'urn:xmpp:delay', + from: 'roomname@conference.domain-xmpp.ltd' + }, + 'This is the message text') + )) sinon.assert.notCalled(outgoingStub) done() }) @@ -109,14 +169,24 @@ describe('XMPP component', () => { describe('Bot receive a message in a room', () => { it('Should trigger outgoing webhook with valid arguments', (done) => { - simpleXmppEvents.emit('groupchat', 'roomname@conference.domain-xmpp.ltd', 'someone', 'This is the message text', null) + simpleXmppEvents.emit('stanza', xml( + 'message', { + from: 'roomname@conference.domain-xmpp.ltd/someone', + to: 'roomname@conference.domain-xmpp.ltd', + type: 'groupchat' + }, + xml( + 'body', { + }, + 'This is the message text') + )) sinon.assert.calledOnce(outgoingStub) const args = outgoingStub.args[0] args.should.have.length(8) args[3].should.equal('someone') args[4].should.equal('roomname@conference.domain-xmpp.ltd') args[5].should.equal('This is the message text') - args[6].should.equal(true) + args[6].should.equal('groupchat') args[7].should.equal('w1') done() }) @@ -131,12 +201,12 @@ describe('XMPP component', () => { }) it('Should log error and exit with 99 code', (done) => { let error = 'This the error text' - simpleXmppEvents.emit('error', error) + simpleXmppEvents.emit('error', new Error(error)) require('fs').readFile(config.logger.file.path + config.logger.file.filename, 'utf8', (err, data) => { if (err) { throw err } - data.should.match(new RegExp(error + '\n$')) + data.should.match(new RegExp('XMPP client encountered following error: ' + error + '\n$')) sinon.assert.calledWith(process.exit, 99) done() })