UNPKG

wingbot

Version:

Enterprise Messaging Bot Conversation Engine

175 lines (134 loc) 4.73 kB
/** * @author David Menger */ 'use strict'; const jwt = require('jsonwebtoken'); const { default: fetch, Headers } = require('node-fetch'); const FormData = require('form-data'); const crypto = require('crypto'); const { Agent } = require('https'); const { promisify } = require('util'); const ReturnSender = require('./ReturnSender'); /** @typedef {import('./ReturnSender').ReturnSenderOptions} ReturnSenderOptions */ /** @typedef {import('./ReturnSender').ChatLogStorage} ChatLogStorage */ /** @typedef {import('./ReturnSender').UploadResult} UploadResult */ /** * @typedef {object} TlsOptions * @prop {string|Promise<string>} key * @prop {string|Promise<string>} cert */ /** * @typedef {object} BotAppSenderOptions * @prop {string} apiUrl * @prop {string} pageId * @prop {string} appId * @prop {TlsOptions} [tls] * @prop {string} [mid] * @prop {Function} [fetch] * @prop {string|Promise<string>} secret * * @typedef {BotAppSenderOptions & ReturnSenderOptions} SenderOptions */ const sign = promisify(jwt.sign); class BotAppSender extends ReturnSender { /** * * @param {SenderOptions} options * @param {string} senderId * @param {object} incommingMessage * @param {ChatLogStorage} logger - console like logger */ constructor (options, senderId, incommingMessage, logger = null) { super(options, senderId, incommingMessage, logger); this.waits = true; this._apiUrl = options.apiUrl; this._pageId = options.pageId; this._appId = options.appId; this._mid = options.mid; this._secret = Promise.resolve(options.secret); this._tls = options.tls; this._agent = null; this._fetch = options.fetch || fetch; } static async signBody (body, secret, appId) { const goodSecret = await Promise.resolve(secret); const sha1 = crypto.createHash('sha1') .update(body) .digest('hex'); return sign({ appId, sha1, iss: 'apiapp', t: 'at' }, goodSecret); } async _getAgent () { if (!this._tls) { return null; } if (!this._agent) { const [key, cert] = await Promise.all([ Promise.resolve(this._tls.key), Promise.resolve(this._tls.cert) ]); this._agent = new Agent({ cert, key }); } return this._agent; } /** * * @param {Buffer} data * @param {string} contentType * @param {string} fileName * @param {string} [senderId] * @returns {Promise<UploadResult>} */ async upload (data, contentType, fileName, senderId = this._senderId) { const formData = new FormData(); const nonce = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36).padEnd(11, '0'); formData.append('nonce', nonce); formData.append('senderId', senderId || ''); formData.append('f0', data, { filename: fileName, contentType }); const [token, agent] = await Promise.all([ BotAppSender.signBody(nonce, this._secret, this._appId), this._getAgent() ]); const headers = new Headers(); headers.set('Authorization', token); const response = await this._fetch(`${this._apiUrl}/${this._pageId}`, { headers, body: formData, agent, method: 'POST' }) .then((r) => r.json()); return response; } async _send (payload) { // attach sender if (typeof payload.sender === 'undefined') { Object.assign(payload, { sender: { id: this._pageId } }); } // attach response_to_mid if (typeof payload.response_to_mid === 'undefined' && this._mid) { Object.assign(payload, { response_to_mid: this._mid }); } const body = JSON.stringify(payload); const [token, agent] = await Promise.all([ BotAppSender.signBody(body, this._secret, this._appId), this._getAgent() ]); const headers = new Headers(); headers.set('Authorization', token); headers.set('Content-Type', 'application/json'); const response = await this._fetch(this._apiUrl, { headers, body, agent, method: 'POST' }) .then((r) => r.json()); const { request, errors = null } = response; if (errors) { const [{ error, description = '', code = 500 }] = errors; throw new Error(`[${code}] ${error} ${description}`); } return { message_id: request.mid }; } } module.exports = BotAppSender;