diff --git a/lib/session/call-session.js b/lib/session/call-session.js index 714b3b1bd..bef398597 100644 --- a/lib/session/call-session.js +++ b/lib/session/call-session.js @@ -13,7 +13,7 @@ const moment = require('moment'); const assert = require('assert'); const sessionTracker = require('./session-tracker'); const makeTask = require('../tasks/make_task'); -const { normalizeJambones } = require('@jambonz/verb-specifications'); +const { normalizeJambones, validate } = require('@jambonz/verb-specifications'); const listTaskNames = require('../utils/summarize-tasks'); const HttpRequestor = require('../utils/http-requestor'); const WsRequestor = require('../utils/ws-requestor'); @@ -21,6 +21,7 @@ const { JAMBONES_INJECT_CONTENT, AWS_REGION, } = require('../config'); +const bent = require('bent'); const BackgroundTaskManager = require('../utils/background-task-manager'); const BADPRECONDITIONS = 'preconditions not met'; const CALLER_CANCELLED_ERR_MSG = 'Response not sent due to unknown transaction'; @@ -969,6 +970,91 @@ class CallSession extends Emitter { } } + /** + * perform live call control - create rest:dial + * @param {obj} opts create call options + */ + async _lccCallDial(opts) { + try { + const restDialUrl = `${this.srf.locals.serviceUrl}/v1/createCall`; + await this._validateCreateCall(opts); + const resp = bent('POST', 201)(restDialUrl, opts); + this.logger.info(resp.body, 'successfully create outbound call'); + return resp.body; + } catch (err) { + this.logger.error(err, 'failed to create outbound call from ' + this.callSid); + } + } + + async _validateCreateCall(opts) { + const { + lookupAppBySid + } = this.srf.locals.dbHelpers; + opts.account_sid = this.accountSid; + if (!opts.from) { + throw new Error('Missing from parameter'); + } + + if (opts.application_sid) { + this.logger.debug(`Callsession:_validateCreateCall retrieving application ${opts.application_sid}`); + const application = await lookupAppBySid(opts.application_sid); + Object.assign(opts, { + call_hook: application.call_hook, + app_json: application.app_json, + call_status_hook: application.call_status_hook, + speech_synthesis_vendor: application.speech_synthesis_vendor, + speech_synthesis_language: application.speech_synthesis_language, + speech_synthesis_voice: application.speech_synthesis_voice, + speech_recognizer_vendor: application.speech_recognizer_vendor, + speech_recognizer_language: application.speech_recognizer_language + }); + this.logger.debug({opts, application}, 'Callsession:_validateCreateCall augmented with application settings'); + } else { + delete opts.application_sid; + if (!opts.speech_synthesis_vendor || + !opts.speech_synthesis_language || + !opts.speech_synthesis_voice || + !opts.speech_recognizer_vendor || + !opts.speech_recognizer_language) + throw new Error('either application_sid or set of' + + ' speech_synthesis_vendor, speech_synthesis_language, speech_synthesis_voice,' + + ' speech_recognizer_vendor, speech_recognizer_language required'); + } + + if (!opts.call_hook && !opts.application_sid) { + throw new Error('either call_hook or application_sid required'); + } + + if (typeof opts.call_hook === 'string') { + const url = opts.call_hook; + opts.call_hook = { + url, + method: 'POST' + }; + } + if (typeof opts.call_status_hook === 'string') { + const url = opts.call_status_hook; + opts.call_status_hook = { + url, + method: 'POST' + }; + } + if (typeof opts.call_hook === 'object' && typeof opts.call_hook.url != 'string') { + throw new Error('call_hook must be string or an object containing a url property'); + } + if (typeof opts.call_status_hook === 'object' && typeof opts.call_status_hook.url != 'string') { + throw new Error('call_status_hook must be string or an object containing a url property'); + } + if (opts.call_hook && !/^https?:/.test(opts.call_hook.url) && !/^wss?:/.test(opts.call_hook.url)) { + throw new Error('call_hook url be an absolute url'); + } + if (opts.call_status_hook && + !/^https?:/.test(opts.call_status_hook.url) && + !/^wss?:/.test(opts.call_status_hook.url)) { + throw new Error('call_status_hook url be an absolute url'); + } + } + /** * perform live call control -- set a new call_hook * @param {object} opts @@ -1403,6 +1489,10 @@ Duration=${duration} ` this._lccCallStatus(data); break; + case 'dial': + this._lccCallDial(data); + break; + case 'mute:status': this._lccMuteStatus(call_sid, data); break;