mirror of
https://github.com/nioc/xmpp-bot.git
synced 2024-12-04 14:23:35 +01:00
Migrate from simple-xmpp to xmpp/client
This commit is contained in:
parent
36d3c30a0e
commit
48046dbae2
13 changed files with 588 additions and 227 deletions
13
README.md
13
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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
446
package-lock.json
generated
446
package-lock.json
generated
|
@ -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": {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"]
|
||||
},
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
132
test/xmpp.js
132
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()
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue