Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/calm-cats-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@zapo-js/voip': patch
---

Preserve the caller phone device JID from incoming call offers when initializing audio media.
7 changes: 4 additions & 3 deletions packages/voip/src/call/WaCallManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ export class WaCallManager extends EventEmitter {
}

const callCreator = nodeInfo.innerNode.attrs?.['call-creator'] || peerJid
const callerPn = nodeInfo.innerNode.attrs?.['caller_pn']
const isVideo = hasNodeChild(nodeInfo.innerNode, 'video')

const callKey = await decryptCallKey(
Expand All @@ -204,7 +205,7 @@ export class WaCallManager extends EventEmitter {
)

const mediaType = isVideo ? CallMediaType.Video : CallMediaType.Audio
const info = CallInfo.newIncoming(callId, peerJid, callCreator, undefined, mediaType)
const info = CallInfo.newIncoming(callId, peerJid, callCreator, callerPn, mediaType)

if (callKey) {
info.encryptionKey = callKey
Expand All @@ -228,7 +229,7 @@ export class WaCallManager extends EventEmitter {
try {
const creds = this.deps.authClient.getCurrentCredentials()
const selfLid = creds?.meLid || creds?.meJid || ''
await session.initMedia(selfLid, peerJid)
await session.initMedia(selfLid, callerPn || peerJid)
await session.sendIncomingPreaccept(peerJid)
await session.sendIncomingRelayLatency()
} catch (err) {
Expand Down Expand Up @@ -463,7 +464,7 @@ export class WaCallManager extends EventEmitter {
const creds = this.deps.authClient.getCurrentCredentials()
const selfLid = creds?.meLid || creds?.meJid || ''

await session.initMedia(selfLid, session.info.peerJid)
await session.initMedia(selfLid, session.info.callerPn || session.info.peerJid)
await session.sendIncomingPreaccept(session.info.peerJid)
await session.sendIncomingRelayLatency()

Expand Down
19 changes: 17 additions & 2 deletions packages/voip/src/call/__tests__/call-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function createMockDeps(): { deps: WaVoipDeps; stores: WaVoipStores; sent: Binar
return { deps, stores, sent }
}

function buildOfferNode(callId: string, from = '2222222222:0@lid'): BinaryNode {
function buildOfferNode(callId: string, from = '2222222222:0@lid', callerPn?: string): BinaryNode {
return {
tag: 'call',
attrs: { from, id: 'OFFERMSGID' },
Expand All @@ -56,7 +56,8 @@ function buildOfferNode(callId: string, from = '2222222222:0@lid'): BinaryNode {
tag: 'offer',
attrs: {
'call-id': callId,
'call-creator': from
'call-creator': from,
...(callerPn ? { caller_pn: callerPn } : {})
},
content: [
{ tag: 'audio', attrs: { enc: 'opus', rate: '16000' }, content: undefined }
Expand Down Expand Up @@ -183,6 +184,20 @@ test('incoming offer with capacity creates a second session', async () => {
assert.equal(manager.getCalls().length, 2)
})

test('incoming offer preserves the caller phone device jid', async () => {
const { deps, stores } = createMockDeps()
const manager = new WaCallManager({ deps, stores, maxConcurrentCalls: 1 })
const callerPn = '5511999999999:3@s.whatsapp.net'
const callId = 'INCOMINGCALLWITHCALLERPN00001'

await manager.handleCallOffer(
buildOfferNode(callId, '2222222222:0@lid', callerPn),
'2222222222:0@lid'
)

assert.equal(manager.getCall(callId)?.callerPn, callerPn)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The new regression test only asserts callerPn is preserved in CallInfo metadata; it never verifies the fix's core behavior - that initMedia is called with the caller phone JID. A regression reverting initMedia back to peerJid would still pass. Consider capturing the initMedia peer argument (e.g., spy/stub session initMedia or assert on the derived peer SSRC) so the fix is actually covered.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/voip/src/call/__tests__/call-manager.test.ts, line 198:

<comment>The new regression test only asserts callerPn is preserved in CallInfo metadata; it never verifies the fix's core behavior - that initMedia is called with the caller phone JID. A regression reverting initMedia back to peerJid would still pass. Consider capturing the initMedia peer argument (e.g., spy/stub session initMedia or assert on the derived peer SSRC) so the fix is actually covered.</comment>

<file context>
@@ -183,6 +184,20 @@ test('incoming offer with capacity creates a second session', async () => {
+        '2222222222:0@lid'
+    )
+
+    assert.equal(manager.getCall(callId)?.callerPn, callerPn)
+})
+
</file context>

})

test('handleCallTerminate only ends the matching call', async () => {
const { deps, stores } = createMockDeps()
const manager = new WaCallManager({ deps, stores, maxConcurrentCalls: 2 })
Expand Down
Loading