@onereach/step-voice
Version:
Onereach.ai Voice Steps
224 lines (223 loc) • 9.79 kB
JavaScript
"use strict";
/* eslint-disable
@typescript-eslint/strict-boolean-expressions,
@typescript-eslint/explicit-function-return-type,
@typescript-eslint/no-misused-promises
*/
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const voice_1 = tslib_1.__importDefault(require("./voice"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const timestring_1 = tslib_1.__importDefault(require("timestring"));
const uuid = tslib_1.__importStar(require("uuid"));
const defaultSessionTimeoutMin = 5;
class InitiateCall extends voice_1.default {
get conversation() {
if (this.step.dataOut == null)
throw new Error('missing step.dataOut');
return this.step.dataOut;
}
async runStep() {
const { callId = uuid.v4() } = this.data;
if (!this.session.key) {
const sessionTimeoutMs = (0, timestring_1.default)(`${this.data.sessionTimeout ?? defaultSessionTimeoutMin} min`, 'ms');
await this.session.start({
key: callId,
timeout: sessionTimeoutMs,
reporting: {
settingsKey: 'session',
startedBy: 'Bot',
sessionType: 'Phone'
}
});
}
const convData = { id: callId, type: 'voicer', vv: 0 }; // vv - voicer version
await this.startConversation(convData);
// await this.pushConvStep()
this.gotoState('waitForCall');
}
async onAwake() {
const { handleCancel } = this.data;
await super.onAwake();
const call = await this.fetchData();
if (!call.ended)
throw new Error('unpexpected awake');
if (handleCancel !== true)
throw new Error('hangup cancel');
this.exitStep('cancel');
}
async waitForCall() {
const { asr, tts, from: botNumber, endUserNumber, gatewaySettingsMode, sipHost, sipUser, sipPassword, sipProfile, timeout, headers, enableSpoofCallerId, spoofCallerId, isAMD, otherCallRef, otherCallRefThread, handleCancel, } = this.data;
const call = await this.fetchData();
this.triggers.once(`in/voice/${call.id}/event`, async (event) => {
switch (event.params.type) {
case 'is_flow_ready': {
// TODO this.exitFlow({is_ready : true}) should be enough
await this.thread.eventManager.callbackAction(this.thread.takeCallback(), async () => {
return {
result: {
is_ready: true
}
};
});
this.log.debug({ call, conv: this.conversation, data: this.get(this.conversation) }, 'Confirming that the OB call can be started');
lodash_1.default.merge(call, event.params.channel);
await this.updateData();
return this.exitFlow();
}
case 'call': {
const newCall = {
headers: event.params.headers,
...event.params.channel,
asr: asr.getSettings(),
tts: tts.getVoice(),
botNumber: event.params.channel.to ?? 'unknown',
endUserNumber
};
delete newCall.from;
delete newCall.to;
newCall._conv = call._conv;
await this.startConversation(newCall);
// await this.popConvStep()
await this.transcript(newCall, {
action: 'Call Start',
reportingSettingsKey: 'transcript',
actionFromBot: true
});
const commands = [
...isAMD
? [{
name: 'start-avmd',
params: {
type: 'avmd_detect'
}
}]
: [],
...asr.serverSettings.enabled
? [{
name: 'start-recognition',
params: {
type: asr.serverSettings.engine
}
}]
: [],
];
await this.sendCommands(newCall, commands);
return this.exitStep('success');
}
case 'hangup': {
await this.handleHangup(call);
if (handleCancel && event.params.error?.name === 'CancelError') {
return this.exitStep('cancel');
}
return this.throwError(event.params.error ?? {
name: 'HangupError',
message: 'unpexpected hangup during init call'
});
}
case 'error': {
const error = event.params.error;
const errorStatus = error?.originateStatus ?? 'UNKNOWN';
const errorCall = {
botNumber,
endUserNumber
};
switch (errorStatus) {
case 'USER_BUSY':
await this.transcript(errorCall, {
action: 'Call Busy',
actionFromBot: true
});
await this.updateData();
return this.exitStep('busy', errorCall);
case 'NO_ANSWER':
case 'NO_USER_RESPONSE':
await this.transcript(errorCall, {
action: 'Call No Answer',
actionFromBot: true
});
await this.updateData();
return this.exitStep('no answer', errorCall);
default:
break;
}
await this.transcript(errorCall, {
action: 'Call Error',
actionFromBot: true
});
return this.throwError({
name: error?.name ?? 'VoiceError',
message: `${String(error?.date)}: ${errorStatus}`
});
}
default:
return this.exitFlow();
}
});
this.triggers.otherwise(async () => {
await this.triggers.flush();
const originateTimeout = (0, timestring_1.default)(`${timeout ?? 30} sec`, 'ms'); // timeout or 30 seconds
const customHeaders = lodash_1.default.reduce(headers, (memo, header) => {
memo[header.name] = `${header.value}`;
return memo;
}, {});
// GET SIP PROFILE SETTINGS
let gateway;
const profile = sipProfile !== "default" /* SIP_PROFILE.DEFAULT */ ? sipProfile : undefined;
switch (gatewaySettingsMode) {
case "custom" /* GATEWAY_SETTINGS_MODE.CUSTOM */:
gateway = {
host: sipHost,
user: sipUser,
username: sipUser,
password: sipPassword,
profile
};
break;
case "profile" /* GATEWAY_SETTINGS_MODE.PROFILE */:
gateway = gateway = profile != null ? { profile } : undefined;
break;
}
const params = {
id: call.id,
from: botNumber,
to: endUserNumber,
headers: customHeaders,
spoofCallerId: {
enableSpoofCallerId,
spoofCallerId
},
timeout: originateTimeout,
version: 2,
sessionExpireTime: this.session.expireTime,
gateway,
maxLoops: this.session.data?.loopPrevention?.enabled && this.session.data.loopPrevention.maxLoops,
};
if (otherCallRef) {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const otherThread = this.process.getSafeThread(otherCallRefThread || this.thread.id);
const otherCall = await otherThread.get(otherCallRef);
if (otherCall == null)
throw new Error(`otherCall not found: ${otherCallRef}`);
await this.sendCommands(otherCall, [
{ name: 'originate', params }
]);
}
else if (endUserNumber.startsWith('user:')) {
await this.thread.emitAsync({
target: 'provider',
name: 'out/voice/originate',
params
});
}
else {
await this.thread.emitAsync({
target: 'provider',
name: 'out/voice/originate/v2',
params
});
}
});
}
}
exports.default = InitiateCall;