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
61 changes: 42 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,22 +306,36 @@ 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
const dialAnswerHandler = (
/**
* This event implies that dial_state === "answered",
* which implies `call` to be defined.
*/
payload: Required<CallingCallDialEvent['params']>
) => {
// @ts-expect-error
this.off(SYNTHETIC_CALL_DIAL_FAILED_EVENT, dialFailHandler)
this.callId = payload.call.call_id
this.nodeId = payload.node_id

resolve(this)
})

// this.once(SYNTHETIC_CALL_STATE_FAILED_EVENT, () => {
// reject(new Error('Failed to establish the call.'))
// })
}
const dialFailHandler = () => {
// @ts-expect-error
this.off(SYNTHETIC_CALL_DIAL_ANSWERED_EVENT, dialAnswerHandler)
reject(new Error('Failed to establish the call.'))
}
this.once(
// @ts-expect-error
SYNTHETIC_CALL_DIAL_ANSWERED_EVENT,
dialAnswerHandler
)
// @ts-expect-error
this.once(SYNTHETIC_CALL_DIAL_FAILED_EVENT, dialFailHandler)

this.execute({
method: 'calling.dial',
Expand All @@ -333,9 +361,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 +391,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'
)
60 changes: 60 additions & 0 deletions packages/realtime-api/src/voice/workers/voiceCallDialWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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: CallingCallDialEvent['params']['dial_state'][] = [
'answered',
'failed',
]

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)
) {
return instance.tag === action.payload.tag
}
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