call: stop the ring on a user's other devices when one answers (answered-elsewhere)
A user signed in on two devices (iPhone + Android) rang on both. When they
answered on Android, the iPhone kept ringing: markDmAnswered only emitted a
WS 'call-taken', which never reaches a backgrounded/killed device ringing on
CallKit (no live socket).
Now markDmAnswered also pushes a native cancel to the answerer's OTHER
devices. The device that actually answered is skipped via exceptToken (its own
call-push token) — critical on iOS, where a cancel would end the just-answered
CallKit call; Android's cancel branch never tears down a live call anyway.
- push.sendCallCancel(userId, uuid, { exceptToken, reason }) — skips the
answering device's token; existing 2-arg callers unchanged.
- calls.markDmAnswered(room, userId, exceptToken) — fires the cancel.
- POST /api/calls/answered now takes deviceToken.
- home.html: the native answerCall handler POSTs /api/calls/answered with this
device's own token (ios-voip or fcm), so native/LiveKit calls (which bypass
the mesh) are marked answered and siblings stop ringing.
Server + web only — no native build needed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
aa794102c3
commit
08eff043ec
4 files changed
+36
-11
No files matched your search
@@ -4337,6 +4337,7 @@ async function unsubscribePush(){
|
||||
let _callkitReady=false;
|
||||
let _callWebViewMedia=false; // Android Increment 1: native full-screen ring, but MEDIA stays in the WebView (iOS runs the call media natively via the plugin). Governs the answer/outgoing branches below.
|
||||
let _nativeAnswered=new Set(); // rooms this device answered natively (so a "call-taken" from the server doesn't end our own call)
|
||||
let _myVoipTok=''; // this device's own iOS VoIP (ios-voip) token — sent on answer so the answered-elsewhere cancel skips us
|
||||
function nativeCallPlugin(){ const P=window.Capacitor&&window.Capacitor.Plugins; return (P&&P.NativeCall)||null; }
|
||||
function nativeCallOn(){ return _callkitReady; }
|
||||
async function setupNativeCall(){
|
||||
@@ -4356,8 +4357,8 @@ async function setupNativeCall(){
|
||||
if(!cfg || !cfg.callkit){ console.log('[callkit] disabled by server — using WebView calls'); return; }
|
||||
_callkitReady=true;
|
||||
// VoIP (PushKit) token → register as an 'ios-voip' device so the server sends CallKit wake pushes.
|
||||
NC.addListener('voipToken', (e)=>{ const token=e&&e.token; if(!token) return; postJSON('/api/v1/devices',{ platform:'ios-voip', token }).then(()=>console.log('[callkit] voip token registered')).catch((err)=>console.warn('[callkit] voip register failed', err)); });
|
||||
try{ const t=await NC.getToken(); if(t&&t.token) postJSON('/api/v1/devices',{ platform:'ios-voip', token:t.token }).catch(()=>{}); }catch(_){}
|
||||
NC.addListener('voipToken', (e)=>{ const token=e&&e.token; if(!token) return; _myVoipTok=token; postJSON('/api/v1/devices',{ platform:'ios-voip', token }).then(()=>console.log('[callkit] voip token registered')).catch((err)=>console.warn('[callkit] voip register failed', err)); });
|
||||
try{ const t=await NC.getToken(); if(t&&t.token){ _myVoipTok=t.token; postJSON('/api/v1/devices',{ platform:'ios-voip', token:t.token }).catch(()=>{}); } }catch(_){}
|
||||
}
|
||||
// Native media: the plugin carries the call over its OWN LiveKit room. The WebView must NOT also join
|
||||
// (LiveKit = one connection per identity). So on answer we do NOT enterMeeting — we just tell the server
|
||||
@@ -4366,6 +4367,11 @@ async function setupNativeCall(){
|
||||
NC.addListener('answerCall', (d)=>{ try{
|
||||
if(!d||!d.room) return;
|
||||
_nativeAnswered.add(d.room);
|
||||
// Tell the server THIS device answered. Native/LiveKit calls bypass our mesh, so without this the call is
|
||||
// never marked answered server-side and the answerer's OTHER devices (e.g. an iPhone ringing on CallKit)
|
||||
// keep ringing. deviceToken = our own call token so the answered-elsewhere cancel skips us (never ends the
|
||||
// call we just picked up). iOS → the VoIP token; Android → the FCM token.
|
||||
try{ const _ct=(nativePlatform()==='ios')?_myVoipTok:_nativeTok; postJSON('/api/calls/answered',{ room:d.room, deviceToken:_ct||'' }).catch(()=>{}); }catch(_){}
|
||||
dismissCallInvite(d.room);
|
||||
// Open the REAL meeting window. Joining the mesh marks the call answered server-side, shows the caller's
|
||||
// tile, and wires the end-both-sides lifecycle. meetReturn = the chat to land back on when the call ends.
|
||||
|
||||
Reference in new issue
Block a user