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

Split state and Dial events #493

Merged
merged 10 commits into from
Apr 11, 2022
55 changes: 36 additions & 19 deletions packages/realtime-api/src/voice/Call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
CallingCallStateEventParams,
VoiceCallConnectMethodParams,
CallingCallConnectEventParams,
CallingCallDialEvent,
} from '@signalwire/core'
import { RealTimeCallApiEvents } from '../types'
import { AutoApplyTransformsConsumer } from '../AutoApplyTransformsConsumer'
Expand All @@ -45,6 +46,9 @@ import {
voiceCallPromptWorker,
voiceCallTapWorker,
voiceCallConnectWorker,
voiceCallDialWorker,
SYNTHETIC_CALL_DIAL_ANSWERED_EVENT,
SYNTHETIC_CALL_DIAL_FAILED_EVENT,
} from './workers'
import { createCallPlaybackObject } from './CallPlayback'
import { CallRecording, createCallRecordingObject } from './CallRecording'
Expand Down Expand Up @@ -126,6 +130,16 @@ export class CallConsumer extends AutoApplyTransformsConsumer<RealTimeCallApiEve
* and apply the "peer" property to the Proxy.
*/
})

/**
* It will take care of keeping instances of this class
* up-to-date with the latest changes sent from the
* server. Changes will be available to the consumer via
* our Proxy API.
*/
this.setWorker('voiceCallStateWorker', {
worker: voiceCallStateWorker,
})
edolix marked this conversation as resolved.
Show resolved Hide resolved
}

get id() {
Expand Down Expand Up @@ -292,23 +306,31 @@ export class CallConsumer extends AutoApplyTransformsConsumer<RealTimeCallApiEve
dial(params: VoiceCallDialMethodParams) {
return new Promise((resolve, reject) => {
// TODO: pass resolve/reject to the worker instead of use synthetic events?
this.setWorker('voiceCallStateWorker', {
worker: voiceCallStateWorker,
this.setWorker('voiceCallDialWorker', {
worker: voiceCallDialWorker,
})
this.attachWorkers()

// @ts-expect-error
this.once(SYNTHETIC_CALL_STATE_ANSWERED_EVENT, (payload: any) => {
this.callId = payload.call_id
this.nodeId = payload.node_id
this.once(
// @ts-expect-error
SYNTHETIC_CALL_DIAL_ANSWERED_EVENT,
/**
* This event implies that dial_state === "answered",
* which implies `call` to be defined.
*/
(payload: Required<CallingCallDialEvent['params']>) => {
this.callId = payload.call.call_id
this.nodeId = payload.node_id

resolve(this)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remove the listener for SYNTHETIC_CALL_DIAL_FAILED_EVENT?

this.off(SYNTHETIC_CALL_DIAL_FAILED_EVENT, ..)

}
)

resolve(this)
// @ts-expect-error
this.once(SYNTHETIC_CALL_DIAL_FAILED_EVENT, () => {
reject(new Error('Failed to establish the call.'))
})

// this.once(SYNTHETIC_CALL_STATE_FAILED_EVENT, () => {
// reject(new Error('Failed to establish the call.'))
// })

this.execute({
method: 'calling.dial',
params: {
Expand All @@ -333,9 +355,6 @@ export class CallConsumer extends AutoApplyTransformsConsumer<RealTimeCallApiEve
}

// TODO: pass resolve/reject to the worker instead of use synthetic events?
edolix marked this conversation as resolved.
Show resolved Hide resolved
this.setWorker('voiceCallStateWorker', {
worker: voiceCallStateWorker,
})
this.attachWorkers()

// @ts-expect-error
Expand Down Expand Up @@ -366,11 +385,9 @@ export class CallConsumer extends AutoApplyTransformsConsumer<RealTimeCallApiEve
reject(new Error('Failed to answer the call.'))
}

// TODO: pass resolve/reject to the worker instead of use synthetic events?
this.setWorker('voiceCallStateWorker', {
worker: voiceCallStateWorker,
})
this.attachWorkers({ payload: 1 })
// TODO: pass resolve/reject to the worker instead of
// use synthetic events?
this.attachWorkers()

// @ts-expect-error
this.once(SYNTHETIC_CALL_STATE_ANSWERED_EVENT, () => {
Expand Down
13 changes: 9 additions & 4 deletions packages/realtime-api/src/voice/workers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ export * from './voiceCallRecordWorker'
export * from './voiceCallPromptWorker'
export * from './voiceCallTapWorker'
export * from './voiceCallConnectWorker'
export * from './voiceCallDialWorker'

export const SYNTHETIC_CALL_STATE_ANSWERED_EVENT = toSyntheticEvent(
'calling.call.answered'
)

// export const SYNTHETIC_CALL_STATE_FAILED_EVENT = toSyntheticEvent(
// 'calling.call.failed'
// )

export const SYNTHETIC_CALL_STATE_ENDED_EVENT =
toSyntheticEvent('calling.call.ended')

export const SYNTHETIC_CALL_DIAL_ANSWERED_EVENT = toSyntheticEvent(
'calling.call.dial.answered'
)

export const SYNTHETIC_CALL_DIAL_FAILED_EVENT = toSyntheticEvent(
'calling.call.dial.failed'
)
62 changes: 62 additions & 0 deletions packages/realtime-api/src/voice/workers/voiceCallDialWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
getLogger,
sagaEffects,
SagaIterator,
SDKWorker,
SDKActions,
MapToPubSubShape,
CallingCallDialEvent,
} from '@signalwire/core'
import type { Call } from '../Call'
import {
SYNTHETIC_CALL_DIAL_ANSWERED_EVENT,
SYNTHETIC_CALL_DIAL_FAILED_EVENT,
} from './'

const TARGET_DIAL_STATES = ['answered', 'failed']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: can we type this array to be valid "CallingCallDial" states? I think we don't have that union type yet.


export const voiceCallDialWorker: SDKWorker<Call> = function* (
options
): SagaIterator {
const { channels, instance } = options
const { swEventChannel, pubSubChannel } = channels
getLogger().trace('voiceCallDialWorker started')

const action: MapToPubSubShape<CallingCallDialEvent> = yield sagaEffects.take(
swEventChannel,
(action: SDKActions) => {
if (
action.type === 'calling.call.dial' &&
TARGET_DIAL_STATES.includes(action.payload.dial_state)
) {
// To avoid mixing events on `connect` we check for `instance.id`
// if there's already a callId value.
if (instance.id) {
return instance.id === action.payload.call?.call_id
}
return instance.tag === action.payload.tag
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the call.dial events, i think we could just check for tag.

The value of action.payload.call?.call_id will be the "answered" leg so the remote peer

}
return false
}
)

if (action.payload.dial_state === 'answered') {
yield sagaEffects.put(pubSubChannel, {
// @ts-expect-error
type: SYNTHETIC_CALL_DIAL_ANSWERED_EVENT,
// @ts-expect-error
payload: action.payload,
})
} else if (action.payload.dial_state === 'failed') {
yield sagaEffects.put(pubSubChannel, {
// @ts-expect-error
type: SYNTHETIC_CALL_DIAL_FAILED_EVENT,
// @ts-expect-error
payload: action.payload,
})
} else {
throw new Error('[voiceCallDialWorker] unhandled call_state')
}

getLogger().trace('voiceCallDialWorker ended')
}
18 changes: 7 additions & 11 deletions packages/realtime-api/src/voice/workers/voiceCallStateWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,25 @@ import {
CallingCallStateEvent,
MapToPubSubShape,
} from '@signalwire/core'
import { Call } from '../Call'
import type { Call } from '../Call'
import {
SYNTHETIC_CALL_STATE_ANSWERED_EVENT,
SYNTHETIC_CALL_STATE_ENDED_EVENT,
} from './'

const TARGET_CALL_STATES = ['answered', 'failed', 'ended']

export const voiceCallStateWorker: SDKWorker<Call> = function* (
options
): SagaIterator {
const { channels, instance } = options
const { swEventChannel, pubSubChannel } = channels
getLogger().trace('voiceCallStateWorker started')

let isDone = false
while (!isDone) {
let run = true
while (run) {
const action: MapToPubSubShape<CallingCallStateEvent> =
yield sagaEffects.take(swEventChannel, (action: SDKActions) => {
if (
action.type === 'calling.call.state' &&
TARGET_CALL_STATES.includes(action.payload.call_state)
edolix marked this conversation as resolved.
Show resolved Hide resolved
action.type === 'calling.call.state'
) {
// To avoid mixing events on `connect` we check for `instance.id`
// if there's already a callId value.
Expand All @@ -40,7 +37,8 @@ export const voiceCallStateWorker: SDKWorker<Call> = function* (
return false
})

// Inject `tag` to have our EE to work because inbound calls don't have tags.
// Inject `tag` to have our EE to work because inbound
// calls don't have tags.
const newPayload = {
tag: instance.tag,
...action.payload,
Expand All @@ -62,16 +60,14 @@ export const voiceCallStateWorker: SDKWorker<Call> = function* (
payload: newPayload,
})
} else if (action.payload.call_state === 'ended') {
isDone = true
run = false

yield sagaEffects.put(pubSubChannel, {
// @ts-expect-error
type: SYNTHETIC_CALL_STATE_ENDED_EVENT,
// @ts-expect-error
payload: newPayload,
})
} else {
throw new Error('[voiceCallStateWorker] unhandled call_state')
}
}
getLogger().trace('voiceCallStateWorker ended')
Expand Down