Skip to content

Commit

Permalink
Allow consumer to pass the call without hanging up (#827)
Browse files Browse the repository at this point in the history
* Allow consumer to pass the call without hanging up

* e2e test case for call pass

* include changeset
  • Loading branch information
iAmmar7 committed Jul 19, 2023
1 parent b44bd6f commit 6a35f0a
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .changeset/shaggy-owls-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@signalwire/realtime-api': minor
'@signalwire/core': minor
---

Introduce call.pass function to pass the call to another consumer
111 changes: 111 additions & 0 deletions internal/e2e-realtime-api/src/voicePass.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import tap from 'tap'
import { Voice } from '@signalwire/realtime-api'
import { createTestRunner } from './utils'

const handler = () => {
return new Promise<number>(async (resolve, reject) => {
const options = {
host: process.env.RELAY_HOST || 'relay.swire.io',
project: process.env.RELAY_PROJECT as string,
token: process.env.RELAY_TOKEN as string,
contexts: [process.env.VOICE_CONTEXT as string],
// logLevel: "trace",
debug: {
// logWsTraffic: true,
},
}

const [client1, client2, client3] = [
new Voice.Client(options),
new Voice.Client(options),
new Voice.Client(options),
]

let callPassed = false

client2.on('call.received', async (call) => {
console.log(
'Got call on client 2',
call.id,
call.from,
call.to,
call.direction
)

if (callPassed) return

try {
const passed = await call.pass()
tap.equal(passed, undefined, 'Call passed!')
callPassed = true
} catch (error) {
console.error('Inbound - voicePass client 2 error', error)
reject(4)
}
})

client3.on('call.received', async (call) => {
console.log(
'Got call on client 3',
call.id,
call.from,
call.to,
call.direction
)

if (!callPassed) return

try {
const resultAnswer = await call.answer()
tap.ok(resultAnswer.id, 'Inbound - Call answered')

await call.hangup()
} catch (error) {
console.error('error', error)
reject(4)
}
})

try {
const call = await client1.dialPhone({
// make an outbound call to an `office` context to trigger the `call.received` event above
to: process.env.VOICE_DIAL_TO_NUMBER as string,
from: process.env.VOICE_DIAL_FROM_NUMBER as string,
timeout: 30,
})
tap.ok(call.id, 'Call resolved')

const waitForParams = ['ended', 'ending', ['ending', 'ended']] as const
const results = await Promise.all(
waitForParams.map((params) => call.waitFor(params as any))
)
waitForParams.forEach((value, i) => {
if (typeof value === 'string') {
tap.ok(results[i], `"${value}": completed successfully.`)
} else {
tap.ok(
results[i],
`${JSON.stringify(value)}: completed successfully.`
)
}
})

resolve(0)
} catch (error) {
console.error('Outbound - voicePass error', error)
reject(4)
}
})
}

async function main() {
const runner = createTestRunner({
name: 'Voice Pass E2E',
testHandler: handler,
executionTime: 60_000,
})

await runner.run()
}

main()
2 changes: 2 additions & 0 deletions packages/core/src/types/voiceCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ export interface VoiceCallContract<T = any> {

dial(params: VoiceDialerParams): Promise<T>
hangup(reason?: VoiceCallDisconnectReason): Promise<void>
pass(): Promise<void>
answer(): Promise<T>
play(params: VoicePlaylist): Promise<VoiceCallPlaybackContract>
playAudio(
Expand Down Expand Up @@ -1491,6 +1492,7 @@ export type VoiceCallAction = MapToPubSubShape<VoiceCallEvent>
export type VoiceCallJSONRPCMethod =
| 'calling.dial'
| 'calling.end'
| 'calling.pass'
| 'calling.answer'
| 'calling.play'
| 'calling.play.pause'
Expand Down
31 changes: 31 additions & 0 deletions packages/realtime-api/src/voice/Call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,37 @@ export class CallConsumer extends ApplyEventListeners<RealTimeCallApiEvents> {
})
}

/**
* Pass the incoming call to another consumer.
*
* @example
*
* ```js
* call.pass();
* ```
*/
pass() {
return new Promise<void>((resolve, reject) => {
if (!this.callId || !this.nodeId) {
reject(new Error(`Can't call pass() on a call without callId.`))
}

this.execute({
method: 'calling.pass',
params: {
node_id: this.nodeId,
call_id: this.callId,
},
})
.then(() => {
resolve()
})
.catch((e) => {
reject(e)
})
})
}

/**
* Answers the incoming call.
*
Expand Down

0 comments on commit 6a35f0a

Please sign in to comment.