UNPKG

botium-core

Version:
247 lines (218 loc) 7.77 kB
const util = require('util') const async = require('async') const rimraf = require('rimraf') const Bottleneck = require('bottleneck') const _ = require('lodash') const debug = require('debug')('botium-connector-BaseContainer') const Events = require('../Events') const Capabilities = require('../Capabilities') const Queue = require('../helpers/Queue') const { executeHook, getHook } = require('../helpers/HookUtils') const BotiumMockMessage = require('../mocks/BotiumMockMessage') module.exports = class BaseContainer { constructor (eventEmitter, tempDirectory, repo, caps, envs) { this.eventEmitter = eventEmitter this.repo = repo this.caps = Object.assign({}, caps) this.envs = Object.assign({}, envs) this.tempDirectory = tempDirectory this.cleanupTasks = [] this.queues = {} this.bottleneck = null } Validate () { this.onBuildHook = getHook(this.caps, this.caps[Capabilities.CUSTOMHOOK_ONBUILD]) this.onStartHook = getHook(this.caps, this.caps[Capabilities.CUSTOMHOOK_ONSTART]) this.onUserSaysHook = getHook(this.caps, this.caps[Capabilities.CUSTOMHOOK_ONUSERSAYS]) this.onBotResponseHook = getHook(this.caps, this.caps[Capabilities.CUSTOMHOOK_ONBOTRESPONSE]) this.onStopHook = getHook(this.caps, this.caps[Capabilities.CUSTOMHOOK_ONSTOP]) this.onCleanHook = getHook(this.caps, this.caps[Capabilities.CUSTOMHOOK_ONCLEAN]) if (this.caps[Capabilities.RATELIMIT_BOTTLENECK_FN]) { if (_.isFunction(this.caps[Capabilities.RATELIMIT_BOTTLENECK_FN])) { this.bottleneck = this.caps[Capabilities.RATELIMIT_BOTTLENECK_FN] debug('Validate: Applying userSays rate limits from capability') } else { const limiter = new Bottleneck(this.caps[Capabilities.RATELIMIT_BOTTLENECK_FN]) this.bottleneck = (fn) => limiter.schedule(fn) debug(`Validate: Applying userSays rate limits ${util.inspect(this.caps[Capabilities.RATELIMIT_BOTTLENECK_FN])}`) } } else if (this.caps[Capabilities.RATELIMIT_USERSAYS_MAXCONCURRENT] || this.caps[Capabilities.RATELIMIT_USERSAYS_MINTIME]) { const opts = {} if (this.caps[Capabilities.RATELIMIT_USERSAYS_MAXCONCURRENT]) opts.maxConcurrent = this.caps[Capabilities.RATELIMIT_USERSAYS_MAXCONCURRENT] if (this.caps[Capabilities.RATELIMIT_USERSAYS_MINTIME]) opts.minTime = this.caps[Capabilities.RATELIMIT_USERSAYS_MINTIME] const limiter = new Bottleneck(opts) this.bottleneck = (fn) => limiter.schedule(fn) debug(`Validate: Applying userSays rate limits ${util.inspect(opts)}`) } return Promise.resolve() } Build () { return new Promise((resolve, reject) => { this._RunCustomHook('onBuild', this.onBuildHook) .then(() => resolve(this)) .catch((err) => reject(err)) }) } Start () { this.queues = {} return new Promise((resolve, reject) => { this._RunCustomHook('onStart', this.onStartHook) .then(() => resolve(this)) .catch((err) => reject(err)) }) } UserSaysText (text) { const meMsg = new BotiumMockMessage({ sender: 'me', messageText: text }) return this.UserSays(meMsg) } UserSays (meMsg) { const run = () => this._RunCustomHook('onUserSays', this.onUserSaysHook, { meMsg }) .then(() => this.UserSaysImpl(meMsg)) if (this.bottleneck) { return this.bottleneck(run) } else { return run() } } UserSaysImpl (meMsg) { return Promise.resolve(this) } WaitBotSays (channel = null, timeoutMillis = null) { if (!channel) channel = 'default' if (!timeoutMillis) timeoutMillis = this.caps[Capabilities.WAITFORBOTTIMEOUT] if (!this.queues[channel]) { this.queues[channel] = new Queue() } return new Promise((resolve, reject) => { this.queues[channel].pop(timeoutMillis) .then((botMsg) => { if (_.isError(botMsg)) { reject(botMsg) } else { resolve(botMsg) } }) .catch((err) => { debug(`WaitBotSays error ${err.message || err}`) reject(err) }) }) } WaitBotSaysText (channel = null, timeoutMillis = null) { return this.WaitBotSays(channel, timeoutMillis) .then((botMsg) => { if (botMsg) { return botMsg.messageText } }) } Restart () { return new Promise((resolve, reject) => { this.Stop() .then(() => this.Start()) .then(() => resolve()) .catch((err) => reject(err)) }) } Stop () { return new Promise((resolve, reject) => { this._RunCustomHook('onStop', this.onStopHook) .then(() => resolve(this)) .catch((err) => reject(err)) }) } Clean () { this.userSaysLimiter = null return new Promise((resolve, reject) => { async.series([ (hookExecuted) => { this._RunCustomHook('onClean', this.onCleanHook) .then(() => hookExecuted()) .catch(() => hookExecuted()) }, (cleanupTasksDone) => { if (this.cleanupTasks) { async.series( this.cleanupTasks.map((task) => { return (cb) => { task((err) => { if (err) debug(`Cleanup failed: ${util.inspect(err)}`) cb() }) } }), () => { cleanupTasksDone() } ) } else { cleanupTasksDone() } }, (rimraffed) => { if (this.caps[Capabilities.CLEANUPTEMPDIR]) { debug(`Cleanup rimrafing temp dir ${this.tempDirectory}`) try { rimraf.sync(this.tempDirectory) rimraffed() } catch (err) { rimraffed(new Error(`Cleanup temp directory ${this.tempDirectory} failed: ${util.inspect(err)}`)) } } else { rimraffed() } } ], (err) => { if (err) { return reject(new Error(`Cleanup failed ${util.inspect(err)}`)) } resolve() }) }) } _AssertCapabilityExists (cap) { if (!Object.prototype.hasOwnProperty.call(this.caps, cap)) { throw new Error(`Capability property ${cap} not set`) } } _AssertOneCapabilityExists () { const checkCaps = [...arguments] const found = checkCaps.find((cap) => this.caps[cap]) if (!found) { throw new Error(`Capability property of ${checkCaps.join()} not set`) } } _QueueLength (channel = 'default') { return (this.queues[channel] && this.queues[channel].length()) || 0 } _EmptyQueue (channel = 'default') { if (this.queues[channel]) { this.queues[channel].empty() } } async _QueueBotSays (botMsg) { if (_.isError(botMsg)) { if (!this.queues.default) { this.queues.default = new Queue() } this.queues.default.push(botMsg) this.eventEmitter.emit(Events.MESSAGE_RECEIVEFROMBOT_ERROR, this, botMsg) } else { if (!botMsg.channel) botMsg.channel = 'default' if (!botMsg.sender) botMsg.sender = 'bot' if (!this.queues[botMsg.channel]) { this.queues[botMsg.channel] = new Queue() } await this._RunCustomHook('onBotResponse', this.onBotResponseHook, { botMsg }) this.queues[botMsg.channel].push(botMsg) this.eventEmitter.emit(Events.MESSAGE_RECEIVEDFROMBOT, this, botMsg) } } async _RunCustomHook (name, hook, args) { try { await executeHook(this.caps, hook, Object.assign({ container: this, fetch }, args)) } catch (err) { debug(`_RunCustomHook ${name} finished with error: ${err.message || util.inspect(err)}`) } } }