Skip to content

Commit 7fbed2e

Browse files
jpsantosbhgiavacLittorWired
authored
BUg Fix: Incoming calls are unable to send the verto.answer (#1263)
* fix incoming calls * Update packages/webrtc/src/RTCPeer.ts Co-authored-by: Giacomo Vacca <[email protected]> * Update internal/e2e-client/utils.ts Co-authored-by: LittorWired <[email protected]> * prettier --------- Co-authored-by: Giacomo Vacca <[email protected]> Co-authored-by: LittorWired <[email protected]>
1 parent ffaedf9 commit 7fbed2e

File tree

3 files changed

+127
-4
lines changed

3 files changed

+127
-4
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { CallSession } from '@signalwire/client'
2+
import { test, expect } from '../../fixtures'
3+
import { SERVER_URL, createCFClient, dialAddress } from '../../utils'
4+
5+
test.describe('CallFabric Incoming Call over WebSocket', () => {
6+
test('should handle incoming call via WebSocket between two subscribers', async ({
7+
createCustomPage,
8+
}) => {
9+
// Create a page named Callee
10+
const calleePage = await createCustomPage({ name: '[callee]' })
11+
await calleePage.goto(SERVER_URL)
12+
13+
// Create a page named Caller
14+
const callerPage = await createCustomPage({ name: '[caller]' })
15+
await callerPage.goto(SERVER_URL)
16+
17+
// Create clients for both pages
18+
await createCFClient(calleePage, { reference: 'callee' })
19+
await createCFClient(callerPage, { reference: 'caller' })
20+
21+
// In Callee page: use client.address.getMyAddresses to find the callee address
22+
const calleeAddress = await calleePage.evaluate(async () => {
23+
const client = window._client!
24+
const addresses = await client.address.getMyAddresses()
25+
26+
if (!addresses || addresses.length === 0) {
27+
throw new Error('No addresses found for callee')
28+
}
29+
30+
const myAddressId = addresses[0].id
31+
const myAddress = await client.address.getAddress({ id: myAddressId })
32+
return myAddress.channels.video
33+
})
34+
35+
expect(calleeAddress).toBeTruthy()
36+
37+
// In Callee page: set up promise to track call events
38+
const calleeCallAnsweredPromise = calleePage.evaluate(() => {
39+
return new Promise<boolean>((resolve) => {
40+
// @ts-expect-error
41+
window._calleeCallAnswered = resolve
42+
})
43+
})
44+
45+
const calleeCallDestroyedPromise = calleePage.evaluate(() => {
46+
return new Promise<boolean>((resolve) => {
47+
// @ts-expect-error
48+
window._calleeCallDestroyed = resolve
49+
})
50+
})
51+
52+
// In Callee page: use client.online to register a listener for invites over WebSocket only
53+
await calleePage.evaluate(async () => {
54+
const client = window._client!
55+
56+
await client.online({
57+
incomingCallHandlers: {
58+
websocket: (notification) => {
59+
console.log(
60+
'Callee received incoming call:',
61+
notification.invite.details
62+
)
63+
64+
// Accept the call
65+
notification.invite
66+
.accept({
67+
rootElement: document.getElementById('rootElement')!,
68+
audio: true,
69+
video: true,
70+
})
71+
.then(async (calleeCall) => {
72+
// @ts-expect-error
73+
window._calleeCall = calleeCall
74+
75+
// Set up event listeners
76+
calleeCall.on('call.state', (state) => {
77+
console.log('Callee call state:', state.call_state)
78+
})
79+
80+
calleeCall.on('destroy', () => {
81+
console.log('Callee call destroyed')
82+
// @ts-expect-error
83+
window._calleeCallDestroyed(true)
84+
})
85+
86+
// @ts-expect-error
87+
window._calleeCallAnswered(true)
88+
})
89+
},
90+
},
91+
})
92+
})
93+
94+
// In Caller: dial the callee address with "?channel=video"
95+
const callerDialPromise = dialAddress(callerPage, {
96+
address: calleeAddress ?? '',
97+
})
98+
99+
// Wait for both the caller to connect and callee to answer
100+
const [callerCallSession, calleeAnswered] = await Promise.all([
101+
callerDialPromise,
102+
calleeCallAnsweredPromise,
103+
])
104+
105+
expect(calleeAnswered).toBe(true)
106+
expect(callerCallSession).toBeDefined()
107+
108+
// In Caller page: hang up the call
109+
await callerPage.evaluate(async () => {
110+
// @ts-expect-error
111+
const callObj: CallSession = window._callObj
112+
await callObj.hangup()
113+
})
114+
115+
// In Callee page: expect the call to be destroyed
116+
const calleeDestroyed = await calleeCallDestroyedPromise
117+
expect(calleeDestroyed).toBe(true)
118+
})
119+
})

internal/e2e-client/utils.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export const createTestJWTToken = async (body: CreateTestJWTOptions) => {
137137
return data.jwt_token
138138
}
139139

140-
export const createTestSATToken = async () => {
140+
export const createTestSATToken = async (reference?: string) => {
141141
const response = await fetch(
142142
`https://${process.env.API_HOST}/api/fabric/subscribers/tokens`,
143143
{
@@ -147,7 +147,7 @@ export const createTestSATToken = async () => {
147147
Authorization: `Basic ${BASIC_TOKEN}`,
148148
},
149149
body: JSON.stringify({
150-
reference: process.env.SAT_REFERENCE,
150+
reference: reference || process.env.SAT_REFERENCE,
151151
}),
152152
}
153153
)
@@ -341,13 +341,14 @@ export const leaveRoom = async (page: Page) => {
341341

342342
interface CreateCFClientParams {
343343
attachSagaMonitor?: boolean
344+
reference?: string
344345
}
345346

346347
export const createCFClient = async (
347348
page: Page,
348349
params?: CreateCFClientParams
349350
) => {
350-
const sat = await createTestSATToken()
351+
const sat = await createTestSATToken(params?.reference)
351352
return createCFClientWithToken(page, sat, params)
352353
}
353354

packages/webrtc/src/RTCPeer.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -891,10 +891,13 @@ export default class RTCPeer<EventTypes extends EventEmitter.ValidEventTypes> {
891891

892892
// Check if we're still in the right state
893893
if (
894+
this.type === 'offer' &&
894895
!['have-local-offer', 'have-local-pranswer'].includes(
895896
this.instance.signalingState
896897
)
897898
) {
899+
// the local SDP was processed already and onnegotiationneeded was not fired
900+
// this happens because there are multiple places calling _sdpReady
898901
this.logger.warn(
899902
`_sdpReady called in wrong state: ${this.instance.signalingState}`
900903
)
@@ -1020,7 +1023,7 @@ export default class RTCPeer<EventTypes extends EventEmitter.ValidEventTypes> {
10201023
if (this.instance.localDescription?.sdp) {
10211024
if (sdpHasValidCandidates(this.instance.localDescription.sdp)) {
10221025
// Take a snapshot of candidates at this point
1023-
if (this._candidatesSnapshot.length === 0) {
1026+
if (this._candidatesSnapshot.length === 0 && this.type === 'offer') {
10241027
this._candidatesSnapshot = [...this._allCandidates]
10251028
this.logger.info(
10261029
'SDP has candidates for all media sections, calling _sdpReady for early invite'

0 commit comments

Comments
 (0)