Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Voice CreateDialer Interface #507

Merged
merged 7 commits into from
Apr 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/forty-moons-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@sw-internal/playground-realtime-api': minor
'@signalwire/core': minor
'@signalwire/realtime-api': minor
---

Expose the `Voice.createDialer()` method to simplify dialing devices on a Voice Call.
24 changes: 13 additions & 11 deletions internal/playground-realtime-api/src/voice/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,19 @@ async function run() {
})

try {
const call = await client.dial({
devices: [
[
{
type: 'phone',
to: process.env.TO_NUMBER as string,
from: process.env.FROM_NUMBER as string,
timeout: 30,
},
],
],
// Using createDialer util
// const dialer = Voice.createDialer().addPhone({
// to: process.env.TO_NUMBER as string,
// from: process.env.FROM_NUMBER as string,
// timeout: 30,
// })
// const call = await client.dial(dialer)

// Using dialPhone Alias
const call = await client.dialPhone({
to: process.env.TO_NUMBER as string,
from: process.env.FROM_NUMBER as string,
timeout: 30,
})

console.log('Dial resolved!', call.id)
Expand Down
55 changes: 35 additions & 20 deletions packages/core/src/types/voiceCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ export interface VoiceCallPhoneParams {
timeout?: number
}

export type OmitType<T> = Omit<T, 'type'>

export interface VoiceCallSipParams {
type: 'sip'
from: string
Expand Down Expand Up @@ -186,19 +188,19 @@ export interface VoiceCallPlayMethodParams {
}

export interface VoiceCallPlayAudioMethodParams
extends Omit<VoiceCallPlayAudioParams, 'type'> {
extends OmitType<VoiceCallPlayAudioParams> {
volume?: number
}

export interface VoiceCallPlaySilenceMethodParams
extends Omit<VoiceCallPlaySilenceParams, 'type'> {}
extends OmitType<VoiceCallPlaySilenceParams> {}

export interface VoiceCallPlayRingtoneMethodParams
extends Omit<VoiceCallPlayRingtoneParams, 'type'> {
extends OmitType<VoiceCallPlayRingtoneParams> {
volume?: number
}
export interface VoiceCallPlayTTSMethodParams
extends Omit<VoiceCallPlayTTSParams, 'type'> {
extends OmitType<VoiceCallPlayTTSParams> {
volume?: number
}

Expand Down Expand Up @@ -239,19 +241,19 @@ export type VoiceCallPromptMethodParams = SpeechOrDigits & {
partialResults?: boolean
}
export type VoiceCallPromptAudioMethodParams = SpeechOrDigits &
Omit<VoiceCallPlayAudioParams, 'type'> & {
OmitType<VoiceCallPlayAudioParams> & {
volume?: number
initialTimeout?: number
partialResults?: boolean
}
export type VoiceCallPromptRingtoneMethodParams = SpeechOrDigits &
Omit<VoiceCallPlayRingtoneParams, 'type'> & {
OmitType<VoiceCallPlayRingtoneParams> & {
volume?: number
initialTimeout?: number
partialResults?: boolean
}
export type VoiceCallPromptTTSMethodParams = SpeechOrDigits &
Omit<VoiceCallPlayTTSParams, 'type'> & {
OmitType<VoiceCallPlayTTSParams> & {
volume?: number
initialTimeout?: number
partialResults?: boolean
Expand Down Expand Up @@ -299,6 +301,19 @@ export type VoiceCallDisconnectReason =
| 'decline'
| 'error'

export type VoiceCallDialPhoneMethodParams = OmitType<VoiceCallPhoneParams>
export type VoiceCallDialSipMethodParams = OmitType<VoiceCallSipParams>
export interface CreateVoiceDialerParams {
region?: string
}

export interface VoiceDialer extends CreateVoiceDialerParams {
devices: VoiceCallDialMethodParams['devices']
addPhone(params: VoiceCallDialPhoneMethodParams): this
addSip(params: VoiceCallDialSipMethodParams): this
inParallel(dialer: VoiceDialer): this
}

/**
* Public Contract for a VoiceCall
*/
Expand Down Expand Up @@ -451,7 +466,7 @@ export interface VoiceCallContract<T = any> {
direction: 'inbound' | 'outbound'
headers?: SipHeader[]

dial(params?: VoiceCallDialMethodParams): Promise<T>
dial(params: VoiceDialer): Promise<T>
hangup(reason?: VoiceCallDisconnectReason): Promise<void>
answer(): Promise<T>
play(params: VoiceCallPlayMethodParams): Promise<VoiceCallPlaybackContract>
Expand Down Expand Up @@ -758,18 +773,18 @@ export interface CallingCallConnectEvent extends SwEvent {
* 'calling.call.send_digits
*/

export type CallingCallSendDigitsState = 'finished'
export interface CallingCallSendDigitsEventParams {
node_id: string
call_id: string
control_id: string
state: CallingCallSendDigitsState
}

export interface CallingCallSendDigitsEvent extends SwEvent {
event_type: ToInternalVoiceEvent<CallSendDigits>
params: CallingCallSendDigitsEventParams
}
export type CallingCallSendDigitsState = 'finished'
export interface CallingCallSendDigitsEventParams {
node_id: string
call_id: string
control_id: string
state: CallingCallSendDigitsState
}

export interface CallingCallSendDigitsEvent extends SwEvent {
event_type: ToInternalVoiceEvent<CallSendDigits>
params: CallingCallSendDigitsEventParams
}

/**
* ==========
Expand Down
9 changes: 5 additions & 4 deletions packages/realtime-api/src/voice/Call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
extendComponent,
VoiceCallMethods,
VoiceCallContract,
VoiceCallDialMethodParams,
VoiceDialer,
VoiceCallDisconnectReason,
VoiceCallPlayMethodParams,
VoiceCallPlayAudioMethodParams,
Expand Down Expand Up @@ -299,20 +299,21 @@ export class CallConsumer extends AutoApplyTransformsConsumer<RealTimeCallApiEve
])
}

dial(params: VoiceCallDialMethodParams) {
dial(params: VoiceDialer) {
return new Promise((resolve, reject) => {
this.runWorker('voiceCallDialWorker', {
worker: voiceCallDialWorker,
onDone: resolve,
onFail: reject,
})

const { region, devices } = params
this.execute({
method: 'calling.dial',
params: {
...params,
tag: this.__uuid,
devices: toInternalDevices(params.devices),
region,
devices: toInternalDevices(devices),
},
}).catch((e) => {
reject(e)
Expand Down
30 changes: 23 additions & 7 deletions packages/realtime-api/src/voice/Voice.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import {
connect,
BaseComponentOptions,
toExternalJSON,
VoiceCallDialMethodParams,
} from '@signalwire/core'
import { connect, BaseComponentOptions, toExternalJSON } from '@signalwire/core'
import type {
EmitterContract,
EventTransform,
CallingCallReceiveEventParams,
VoiceDialer,
VoiceCallDialPhoneMethodParams,
VoiceCallDialSipMethodParams,
} from '@signalwire/core'
import { RealtimeClient } from '../client/index'
import { createCallObject, Call } from './Call'
import { voiceCallReceiveWorker } from './workers'
import { createDialer } from './utils'
import type { RealTimeCallApiEvents } from '../types'
import { AutoApplyTransformsConsumer } from '../AutoApplyTransformsConsumer'

export * from './VoiceClient'
export { createDialer }

/**
* List of events for {@link Voice.Call}.
Expand All @@ -27,7 +27,9 @@ type EmitterTransformsEvents = 'calling.call.received'
export interface Voice extends EmitterContract<RealTimeVoiceApiEvents> {
/** @internal */
_session: RealtimeClient
dial(params: VoiceCallDialMethodParams): Promise<Call>
dial(dialer: VoiceDialer): Promise<Call>
dialPhone(params: VoiceCallDialPhoneMethodParams): Promise<Call>
dialSip(params: VoiceCallDialSipMethodParams): Promise<Call>
}

/** @internal */
Expand Down Expand Up @@ -70,6 +72,20 @@ class VoiceAPI extends AutoApplyTransformsConsumer<RealTimeVoiceApiEvents> {
],
])
}

dialPhone(params: VoiceCallDialPhoneMethodParams) {
const dialer = createDialer().addPhone(params)
// dial is available through the VoiceClient Proxy
// @ts-expect-error
return this.dial(dialer)
}

dialSip(params: VoiceCallDialSipMethodParams) {
const dialer = createDialer().addSip(params)
// dial is available through the VoiceClient Proxy
// @ts-expect-error
return this.dial(dialer)
}
}

/** @internal */
Expand Down
4 changes: 2 additions & 2 deletions packages/realtime-api/src/voice/VoiceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ const VoiceClient = function (options?: VoiceClientOptions) {
return voice.once(...args)
}

const callDial: Call['dial'] = async (...args) => {
const callDial: Call['dial'] = async (dialer) => {
await clientConnect(client)

const call = createCallObject({
store,
emitter,
})

await call.dial(...args)
await call.dial(dialer)

return call
}
Expand Down
88 changes: 87 additions & 1 deletion packages/realtime-api/src/voice/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { toInternalDevices } from './utils'
import { toInternalDevices, createDialer } from './utils'

describe('toInternalDevices', () => {
it('should convert the user facing interface to the internal one', () => {
Expand Down Expand Up @@ -127,3 +127,89 @@ describe('toInternalDevices', () => {
])
})
})

describe('createDialer', () => {
it('should build a list of devices to dial', () => {
const dialer = createDialer()

dialer
.addPhone({ from: '+1', to: '+2', timeout: 30 })
.addSip({
from: 'sip:one',
to: 'sip:two',
headers: [{ name: 'foo', value: 'bar' }],
})
.inParallel(
createDialer()
.addPhone({ from: '+3', to: '+4' })
.addSip({
from: 'sip:three',
to: 'sip:four',
headers: [{ name: 'baz', value: 'qux' }],
})
.addPhone({ from: '+5', to: '+6' })
)

expect(dialer.devices).toStrictEqual([
[
{
type: 'phone',
from: '+1',
to: '+2',
timeout: 30,
},
],
[
{
type: 'sip',
from: 'sip:one',
to: 'sip:two',
headers: [{ name: 'foo', value: 'bar' }],
},
],
[
{
type: 'phone',
from: '+3',
to: '+4',
},
{
type: 'sip',
from: 'sip:three',
to: 'sip:four',
headers: [{ name: 'baz', value: 'qux' }],
},
{
type: 'phone',
from: '+5',
to: '+6',
},
],
])
})

it('should build a list of devices to dial including region', () => {
const dialer = createDialer({ region: 'us' })
dialer.inParallel(
createDialer()
.addPhone({ from: '+3', to: '+4' })
.addPhone({ from: '+5', to: '+6' })
)

expect(dialer.region).toBe('us')
expect(dialer.devices).toStrictEqual([
[
{
type: 'phone',
from: '+3',
to: '+4',
},
{
type: 'phone',
from: '+5',
to: '+6',
},
],
])
})
})
Loading