@onereach/step-voice 5.0.5 → 5.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -53,7 +53,7 @@ const alterRecognitionPhrases = (phrases, interpretation) => {
53
53
  class CustomVoiceInput extends voice_1.default {
54
54
  async runStep() {
55
55
  const call = await this.fetchData();
56
- const { textType, asr, tts, sensitiveData, noReplyDelay, usePromptsTriggers, recognitionModel } = this.data;
56
+ const { textType, tts, sensitiveData, noReplyDelay, usePromptsTriggers, recognitionModel } = this.data;
57
57
  const exitExists = (exitId) => {
58
58
  return lodash_1.default.some(choices, (choice) => choice.exitId === exitId);
59
59
  };
@@ -7,21 +7,26 @@ const nanoid_1 = require("nanoid");
7
7
  const voice_1 = tslib_1.__importDefault(require("./voice"));
8
8
  class GlobalCommand extends voice_1.default {
9
9
  get isGlobal() {
10
- return this.thread.id === `G:${this.conversationName}`;
10
+ return this.thread.id === this.globalThreadId;
11
11
  }
12
12
  get useQueue() {
13
13
  return this.isGlobal;
14
14
  }
15
15
  async runStep() {
16
- const gcThreadId = `G:${this.conversationName}`;
17
- await this.process.runThread({
18
- id: gcThreadId,
19
- state: {
16
+ const globThread = this.process.newThread(this.globalThreadId, thread => {
17
+ thread.state = {
20
18
  name: 'globThread',
21
19
  step: this.step.id,
22
20
  thread: this.dataThreadId
23
- }
21
+ };
24
22
  });
23
+ if (globThread.isNewThread) {
24
+ globThread.activate();
25
+ await globThread.run();
26
+ }
27
+ else {
28
+ globThread.state.step = this.step.id;
29
+ }
25
30
  // refresh cache after global thread was started
26
31
  this._clearCache();
27
32
  await this.initGrammar();
@@ -71,6 +76,10 @@ class GlobalCommand extends voice_1.default {
71
76
  }
72
77
  this.end();
73
78
  });
79
+ // TODO remove this hack that prevents this.triggers.once execution below
80
+ // currently it skips logic to override triggers (is there better way to do it?)
81
+ if (this.event.name.startsWith('@end'))
82
+ return;
74
83
  }
75
84
  this.triggers.once(`in/voice/${call.id}/event`, async (event) => {
76
85
  event.processed = undefined;
@@ -2,6 +2,8 @@ import VoiceStep, { CallStartEvent, TODO } from './voice';
2
2
  interface INPUT {
3
3
  callId?: string;
4
4
  sessionTimeout?: number | string;
5
+ otherCallRef?: string;
6
+ otherCallRefThread?: string;
5
7
  asr: TODO;
6
8
  tts: TODO;
7
9
  from: string;
@@ -47,7 +47,7 @@ class InitiateCall extends voice_1.default {
47
47
  this.exitStep('cancel');
48
48
  }
49
49
  async waitForCall() {
50
- const { asr, tts, from: botNumber, endUserNumber, sipHost, sipUser, sipPassword, timeout, headers, enableSpoofCallerId, spoofCallerId, isAMD } = this.data;
50
+ const { asr, tts, from: botNumber, endUserNumber, sipHost, sipUser, sipPassword, timeout, headers, enableSpoofCallerId, spoofCallerId, isAMD, otherCallRef, otherCallRefThread } = this.data;
51
51
  const call = await this.fetchData();
52
52
  this.triggers.once(`in/voice/${call.id}/event`, async (event) => {
53
53
  switch (event.params.type) {
@@ -140,55 +140,48 @@ class InitiateCall extends voice_1.default {
140
140
  memo[header.name] = `${header.value}`;
141
141
  return memo;
142
142
  }, {});
143
- if (endUserNumber.startsWith('user:')) {
143
+ const params = {
144
+ id: call.id,
145
+ from: botNumber,
146
+ to: endUserNumber,
147
+ headers: customHeaders,
148
+ spoofCallerId: {
149
+ enableSpoofCallerId,
150
+ spoofCallerId
151
+ },
152
+ gateway: sipHost
153
+ ? {
154
+ host: sipHost,
155
+ username: sipUser,
156
+ password: sipPassword
157
+ }
158
+ : undefined,
159
+ timeout: originateTimeout,
160
+ version: 2,
161
+ sessionExpireTime: this.session.expireTime
162
+ };
163
+ if (otherCallRef) {
164
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
165
+ const otherThread = this.process.getSafeThread(otherCallRefThread || this.thread.id);
166
+ const otherCall = await otherThread.get(otherCallRef);
167
+ if (otherCall == null)
168
+ throw new Error(`otherCall not found: ${otherCallRef}`);
169
+ await this.sendCommands(otherCall, [
170
+ { name: 'originate', params }
171
+ ]);
172
+ }
173
+ else if (endUserNumber.startsWith('user:')) {
144
174
  await this.thread.emitAsync({
145
175
  target: 'provider',
146
176
  name: 'out/voice/originate',
147
- params: {
148
- id: call.id,
149
- from: botNumber,
150
- to: endUserNumber,
151
- headers: customHeaders,
152
- spoofCallerId: {
153
- enableSpoofCallerId,
154
- spoofCallerId
155
- },
156
- gateway: sipHost
157
- ? {
158
- host: sipHost,
159
- username: sipUser,
160
- password: sipPassword
161
- }
162
- : undefined,
163
- timeout: originateTimeout,
164
- version: 2
165
- }
177
+ params
166
178
  });
167
179
  }
168
180
  else {
169
181
  await this.thread.emitAsync({
170
182
  target: 'provider',
171
183
  name: 'out/voice/originate/v2',
172
- params: {
173
- id: call.id,
174
- from: botNumber,
175
- to: endUserNumber,
176
- headers: customHeaders,
177
- spoofCallerId: {
178
- enableSpoofCallerId,
179
- spoofCallerId
180
- },
181
- gateway: sipHost
182
- ? {
183
- host: sipHost,
184
- username: sipUser,
185
- password: sipPassword
186
- }
187
- : undefined,
188
- timeout: originateTimeout,
189
- version: 2,
190
- sessionExpireTime: this.session.expireTime
191
- }
184
+ params
192
185
  });
193
186
  }
194
187
  });
package/dst/Transfer.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import VoiceStep, { EventType } from './voice';
1
+ import VoiceStep, { VoiceEvent } from './voice';
2
2
  interface INPUT {
3
3
  destination: string;
4
4
  phoneNumber: string;
@@ -14,9 +14,7 @@ interface INPUT {
14
14
  sipUser?: string;
15
15
  sipPassword?: string;
16
16
  }
17
- interface EVENT {
18
- type: EventType;
19
- error?: string | Error;
17
+ interface EVENT extends VoiceEvent {
20
18
  originateDisposition?: string;
21
19
  }
22
20
  export default class Transfer extends VoiceStep<INPUT, {}, EVENT> {
package/dst/Transfer.js CHANGED
@@ -23,8 +23,8 @@ class Transfer extends voice_1.default {
23
23
  }
24
24
  case 'bridge/ended': {
25
25
  const { error, originateDisposition } = event.params;
26
- const isSuccess = error === 'NORMAL_CLEARING'
27
- && (lodash_1.default.isUndefined(originateDisposition) || originateDisposition === 'SUCCESS');
26
+ const isSuccess = error === 'NORMAL_CLEARING' &&
27
+ (lodash_1.default.isUndefined(originateDisposition) || originateDisposition === 'SUCCESS');
28
28
  if (isSuccess) {
29
29
  await this.transcript(call, {
30
30
  action: 'Transfer Ended - Answered',
package/dst/step.d.ts CHANGED
@@ -16,7 +16,8 @@ export default class ConvStep<TData = unknown, TIn = unknown, TOut = unknown, TP
16
16
  private convDataCache?;
17
17
  get cache(): TData & IConversationData | undefined;
18
18
  get conversation(): string | IMergeField;
19
- get conversationName(): string;
19
+ get conversationId(): string;
20
+ get globalThreadId(): string;
20
21
  /** id of the thread where conversation data is stored */
21
22
  get dataThreadId(): IThreadId;
22
23
  get isGlobal(): boolean;
@@ -28,7 +29,6 @@ export default class ConvStep<TData = unknown, TIn = unknown, TOut = unknown, TP
28
29
  runBefore(): Promise<void>;
29
30
  runAfter(): Promise<void>;
30
31
  hasActiveGlobal(): Promise<boolean>;
31
- setLocalTriggers(): Promise<void>;
32
32
  waitForConversation(): Promise<void>;
33
33
  protected onSkipEvent(): Promise<void>;
34
34
  cancel(): Promise<void>;
package/dst/step.js CHANGED
@@ -12,7 +12,7 @@ class ConvStep extends step_1.default {
12
12
  throw new Error('missing data.conversation');
13
13
  return this.data.conversation;
14
14
  }
15
- get conversationName() {
15
+ get conversationId() {
16
16
  const convOrName = this.conversation;
17
17
  const conv = (typeof convOrName === 'string')
18
18
  ? this.config.mergeFields[convOrName] ?? convOrName
@@ -25,6 +25,9 @@ class ConvStep extends step_1.default {
25
25
  return convOrName;
26
26
  throw new Error(`invalid conversation ${JSON.stringify(conv)}`);
27
27
  }
28
+ get globalThreadId() {
29
+ return `G:${this.conversationId}`;
30
+ }
28
31
  /** id of the thread where conversation data is stored */
29
32
  get dataThreadId() {
30
33
  const dataThreadId = this.state.thread ?? this.data.conversationThread;
@@ -76,21 +79,19 @@ class ConvStep extends step_1.default {
76
79
  if (this.useQueue && !await this.pushConvStep()) {
77
80
  return await this.waitForConversation();
78
81
  }
79
- if (this.event.action == null && !this.event.processed) {
80
- if (this.isGlobal)
81
- return;
82
- const hasGlobal = await this.sendEventToStep({ event: { ...this.event, action: 'global' }, toGlobal: true }); // redirect to global (1)
83
- if (hasGlobal != null) {
84
- this.log.debug('conv.sendEventToStep toGlobal', { hasGlobal });
85
- this.state.prevSkipName = this.state.name;
86
- this.state.name = 'onSkipEvent';
87
- return;
88
- }
89
- }
90
- if (this.isGlobal && this.event.action === 'global') {
91
- return; // it was redirected from (1)
92
- }
93
- await this.setLocalTriggers();
82
+ // if (this.event.action == null && !this.event.processed) {
83
+ // if (this.isGlobal) return
84
+ // const hasGlobal = await this.sendEventToStep({ event: { ...this.event, action: 'global' }, toGlobal: true }) // redirect to global (1)
85
+ // if (hasGlobal != null) {
86
+ // this.log.debug('conv.sendEventToStep toGlobal', { hasGlobal })
87
+ // this.state.prevSkipName = this.state.name
88
+ // this.state.name = 'onSkipEvent'
89
+ // }
90
+ // }
91
+ // if (this.isGlobal && this.event.action === 'global') {
92
+ // // it was redirected from (1)
93
+ // }
94
+ // await this.setLocalTriggers()
94
95
  }
95
96
  async runAfter() {
96
97
  if (this.thread.ending)
@@ -112,11 +113,11 @@ class ConvStep extends step_1.default {
112
113
  const globalLength = (await this.getConversation())?.glb?.length ?? 0;
113
114
  return globalLength > 0;
114
115
  }
115
- async setLocalTriggers() {
116
- if (!this.isGlobal && await this.hasActiveGlobal()) {
117
- this.triggers.config({ target: 'session' });
118
- }
119
- }
116
+ // public async setLocalTriggers (): Promise<void> {
117
+ // if (!this.isGlobal && await this.hasActiveGlobal()) {
118
+ // this.triggers.config({ target: 'session' })
119
+ // }
120
+ // }
120
121
  async waitForConversation() {
121
122
  if (this.state.name !== 'waitForConversation') {
122
123
  this.log.debug(this.state, 'conv.begin waitForConversation');
@@ -200,9 +201,9 @@ class ConvStep extends step_1.default {
200
201
  }
201
202
  }
202
203
  async waitGlobEnd() {
203
- const gcThreadId = `G:${this.thread.id}`;
204
+ const gcThreadId = this.globalThreadId;
204
205
  // wait for glob thread end (if exists)
205
- this.triggers.local(`thread/end/${gcThreadId}`, async () => await this.onConvEnd());
206
+ this.triggers.hook({ name: 'end', thread: gcThreadId }, async () => await this.onConvEnd());
206
207
  if (!this.process.getThread(gcThreadId))
207
208
  await this.onConvEnd();
208
209
  }
package/dst/voice.d.ts CHANGED
@@ -28,19 +28,24 @@ export interface IVoiceCall {
28
28
  export type EventType = 'hangup' | 'error' | 'cancel' | 'avm-detected' | 'recognition' | 'digit' | 'digits' | 'conference-start' | 'conference-end' | 'playback' | 'timeout' | 'record' | 'bridge' | 'bridge/ended' | 'is_flow_ready' | 'call' | 'dtmf-sent';
29
29
  export declare class VoiceStepError extends BasicError {
30
30
  }
31
+ export type VoicerError = Error | string & {
32
+ message?: never;
33
+ name?: never;
34
+ };
31
35
  export interface VoiceEvent {
32
36
  type: EventType;
33
- error?: Error;
37
+ global?: boolean;
38
+ error?: VoicerError;
34
39
  }
35
40
  export interface CallStartEvent extends VoiceEvent {
36
41
  channel: IVoiceCall;
37
42
  headers?: Record<string, string>;
38
43
  }
39
- export default class VoiceStep<TIn = unknown, TOut = unknown, TParams = VoiceEvent> extends ConvStep<IVoiceCall, TIn & {
44
+ export default class VoiceStep<TIn = unknown, TOut = unknown, TParams extends VoiceEvent = VoiceEvent> extends ConvStep<IVoiceCall, TIn & {
40
45
  handleCancel?: boolean;
41
46
  }, TOut, TParams> {
42
47
  runBefore(): Promise<void>;
43
- sendCommands({ id, type, callback }: IVoiceCall, commands: TODO[]): Promise<unknown>;
48
+ sendCommands({ id, type, callback }: IVoiceCall, commands: TODO[]): Promise<void>;
44
49
  handleHeartbeat(call: IVoiceCall): Promise<void>;
45
50
  extractSectionMessages(sections: IPromtpSection[]): string;
46
51
  extractSectionFiles(sections: IPromtpSection[]): Array<{
@@ -50,10 +55,7 @@ export default class VoiceStep<TIn = unknown, TOut = unknown, TParams = VoiceEve
50
55
  pauseRecording(call: IVoiceCall, command: any, sensitiveData?: SensitiveData): Promise<void>;
51
56
  resumeRecording(call: IVoiceCall, sensitiveData?: SensitiveData): Promise<void>;
52
57
  transcript(call: IVoiceCall, data?: Partial<IVoiceReporterTranscriptEventArgs>): Promise<string>;
53
- throwError(error?: {
54
- name: string;
55
- message: string;
56
- }): never;
58
+ throwError(error?: VoicerError): never;
57
59
  handleHangup(call: IVoiceCall): Promise<void>;
58
60
  buildSections({ sections, textType, ttsSettings, allowKeypadBargeIn }: TODO): TODO;
59
61
  buildReprompts({ prompts, allowKeypadBargeIn }: TODO): TODO;
package/dst/voice.js CHANGED
@@ -15,13 +15,22 @@ class VoiceStep extends step_1.default {
15
15
  // static Error = VoiceStepError
16
16
  async runBefore() {
17
17
  await super.runBefore();
18
- if (this.cache != null)
18
+ if (this.cache != null) {
19
+ if (!this.isGlobal && this.event.params.global && this.event.action == null) {
20
+ const hasGlobal = await this.sendEventToStep({ event: { ...this.event, action: 'global' }, toGlobal: true }); // redirect to global (1)
21
+ if (hasGlobal != null) {
22
+ this.log.debug('conv.sendEventToStep toGlobal', { hasGlobal });
23
+ this.state.prevSkipName = this.state.name;
24
+ this.state.name = 'onSkipEvent';
25
+ }
26
+ }
19
27
  await this.handleHeartbeat(this.cache);
28
+ }
20
29
  }
21
30
  async sendCommands({ id, type, callback }, commands) {
22
31
  if (lodash_1.default.isEmpty(commands))
23
32
  return;
24
- return await this.thread.eventManager.emit({
33
+ const result = await this.thread.eventManager.emit({
25
34
  target: 'provider',
26
35
  name: `out/voice/${type}`,
27
36
  params: {
@@ -37,6 +46,8 @@ class VoiceStep extends step_1.default {
37
46
  invocationType: 'async',
38
47
  timeout: 5000
39
48
  });
49
+ if (result == null)
50
+ throw new Error(`failed to send command to call: ${id}`);
40
51
  }
41
52
  async handleHeartbeat(call) {
42
53
  if (call.vv >= 1) {
@@ -146,7 +157,7 @@ class VoiceStep extends step_1.default {
146
157
  return eventId;
147
158
  }
148
159
  throwError(error = new Error('unknown')) {
149
- throw new VoiceStepError(error.message, null, { name: error.name });
160
+ throw new VoiceStepError(error.message ?? error, null, { name: error?.name });
150
161
  }
151
162
  async handleHangup(call) {
152
163
  if (call.ended)
@@ -166,6 +177,7 @@ class VoiceStep extends step_1.default {
166
177
  textType,
167
178
  bargeInVoice: section.allowVoiceBargeIn,
168
179
  bargeInKeypad: section.allowKeypadBargeIn ?? allowKeypadBargeIn,
180
+ bargeInBeforeSpeechEndTime: section.bargeInBeforeSpeechEndTime,
169
181
  ...ttsSettings
170
182
  }));
171
183
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onereach/step-voice",
3
- "version": "5.0.5",
3
+ "version": "5.0.6",
4
4
  "author": "Roman Zolotarov <roman.zolotarov@onereach.com>",
5
5
  "contributors": [
6
6
  "Roman Zolotarov",
@@ -19,20 +19,22 @@
19
19
  "uuid": "^9.0.0"
20
20
  },
21
21
  "devDependencies": {
22
- "@onereach/flow-sdk": "^3.1.101",
23
- "@swc/cli": "^0.1.60",
24
- "@swc/core": "^1.3.32",
22
+ "@onereach/flow-sdk": "^3.1.114",
23
+ "@swc/cli": "^0.1.62",
24
+ "@swc/core": "^1.3.36",
25
+ "@swc/helpers": "^0.4.14",
25
26
  "@swc/jest": "^0.2.24",
26
27
  "@types/jest": "^29.4.0",
27
28
  "@types/lodash": "^4.14.191",
28
29
  "@types/timestring": "^6.0.2",
29
- "@types/uuid": "^9.0.0",
30
- "aws-sdk": "^2.1306.0",
30
+ "@types/uuid": "^9.0.1",
31
+ "aws-sdk": "^2.1320.0",
31
32
  "babel-runtime": "^6.26.0",
32
33
  "docdash": "^2.0.1",
33
34
  "husky": "^8.0.3",
34
- "jest": "^29.4.1",
35
+ "jest": "^29.4.3",
35
36
  "pinst": "^3.0.0",
37
+ "source-map-support": "^0.5.21",
36
38
  "ts-standard": "^12.0.2",
37
39
  "typescript": "^4.9.5"
38
40
  },