fix(calls): no duplicate native tile; drop Ringing placeholder; name+DP from roster

Caller saw the native callee TWICE — the outgoing 'Ringing…' placeholder (never cleared,
since a native callee doesn't send the mesh 'answered') plus a separate id-labelled tile.
Now when a native LiveKit participant joins: remove the __waiting placeholder, stop
ringback, and label the tile from CONTACTS (name + DP) instead of the raw user id.

Also (plugin, needs build): connectRoom disconnects any previous LiveKit connection
before joining, so repeated calls never leave duplicate/stale participants in the room.

WebView fix deploys now (no rebuild); the connectRoom fix rides the next build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 17:51:46 +05:30
parent 3d35a9ece7
commit 82c6c9ce0e
2 changed files with 11 additions and 3 deletions
@@ -59,9 +59,11 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega
private func connectRoom(url: String, token: String) {
guard !url.isEmpty, !token.isEmpty else { return }
let old = room
let r = Room()
room = r
Task { [weak self] in
await old?.disconnect() // drop any previous/stale connection so we never leave DUPLICATE participants
do {
try await r.connect(url: url, token: token)
try await r.localParticipant.setMicrophone(enabled: true)
+9 -3
View File
@@ -4411,10 +4411,16 @@ function sfuAttach(pub, track, participant, _try){
if(!pid){ // uid→peerId map (from the mesh join) may lag the LiveKit track — retry a few times…
if((_try||0)<6){ setTimeout(()=>sfuAttach(pub,track,participant,(_try||0)+1), 400); return; }
// …then fall back: a NATIVE (CallKit + LiveKit) participant joins LiveKit but NOT our WS mesh, so it has
// no mesh peer id. Key its tile + audio by the LiveKit identity so it still shows and is heard.
// no mesh peer id. Key its tile + audio by the LiveKit identity so it still shows and is heard. The
// outgoing "Ringing…" placeholder is cleared here (its mesh 'answered' signal never fires for native),
// and we label the tile from the contact roster so it shows a NAME + DP, not a raw user id.
pid='lk:'+participant.identity;
if(!meetNames.get(pid)) meetNames.set(pid, participant.name||'Caller');
stopRingback();
removeWaitingTile(); stopRingback();
if(!meetNames.get(pid)){
const c=(typeof CONTACTS!=='undefined'?CONTACTS:[]).find(x=>x&&x.id===participant.identity);
meetNames.set(pid, (c&&c.name) || (_dmCallWaiting&&_dmCallWaiting.name) || participant.name || 'Caller');
if(c&&c.avatar) meetAvatars.set(pid, c.avatar);
}
}
const p=SFU.peers.get(pid)||{}; SFU.peers.set(pid,p); const mt=track.mediaStreamTrack;
if(track.kind==='audio') p.audio=mt;