fix(ios): stop VoIP-push crash; hand mic to WebView after answer; missed-call banner
CRASH: iOS terminates an app that receives a VoIP push without calling reportNewIncomingCall. My re-ring 'fix' made the cancel path call completion() without reporting -> crash. Always report then immediately end on cancel (a tiny ring blip is unavoidable; a crash is worse). MIC + earpiece: an ACTIVE CallKit call reserves the mic (WebView WebRTC gets a dead mic + earpiece routing). So on answer keep CallKit active only long enough to foreground the app, then end it and fire 'callHandoff'; the WebView forces the loudspeaker and re-acquires the mic (sfuSetMic off/on, retried while it finishes joining). MISSED CALL: endDmCallByRoom now sends a plain missed-call banner to the callee when the call ends unanswered (timeout / caller hung up before pickup); skipped on decline. Server (missed banner) deploys now; plugin + web handoff need a Codemagic build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -146,6 +146,12 @@ async function endDmCallByRoom(room, silent) {
|
||||
call.users.forEach((uid, i) => { try { CHAT.pushToUser(uid, { type: 'dm-call', active: false, uuid: call.uuid, with: call.users[1 - i], room }); } catch (_) {} });
|
||||
// Stop any CallKit ring on a killed/backgrounded device (no WS to receive the dm-call above).
|
||||
try { for (const uid of call.users) PUSH.sendCallCancel(uid, call.uuid); } catch (_) {}
|
||||
// Missed-call banner to the callee (a plain notification, like a phone's missed call) when the call ended
|
||||
// UNANSWERED — timeout or the caller hung up before pickup. Skipped on decline (silent): they chose to.
|
||||
if (!silent && !call.answered) {
|
||||
const callee = call.users.find((u) => u !== call.startedBy);
|
||||
if (callee) { try { PUSH.sendToUser(callee, { title: call.startedByName || 'Missed call', body: '📞 Missed call', kind: 'dm', id: call.startedBy, tag: 'missed:' + room, data: { kind: 'dm', id: call.startedBy } }); } catch (_) {} }
|
||||
}
|
||||
}
|
||||
|
||||
// Mark a 1:1 call answered when the callee (anyone other than the caller) joins its room, so the end
|
||||
|
||||
@@ -3806,6 +3806,17 @@ async function setupNativeCall(){
|
||||
if(room && (typeof meetRoom!=='undefined') && meetRoom===room){ leaveMeeting(); } // already in it → leave
|
||||
else if(room){ postJSON('/api/calls/decline',{room}).catch(()=>{}); dismissCallInvite(room); } // ringing, not joined → decline
|
||||
}catch(_){} });
|
||||
// After answer, CallKit releases the mic + audio session back to us (an active CallKit call reserves the
|
||||
// mic → dead mic + earpiece). Force the loudspeaker (CallKit used the earpiece), then RE-ACQUIRE the mic
|
||||
// (it was captured dead while CallKit held it). Retry the mic toggle a few times in case the WebView is
|
||||
// still joining the room when the handoff fires.
|
||||
NC.addListener('callHandoff', async ()=>{
|
||||
try{ const ar=nativeAudioRoute(); if(ar) await ar.setSpeaker({on:true}); }catch(_){}
|
||||
for(let i=0;i<5;i++){
|
||||
try{ const lp=(typeof SFU!=='undefined'&&SFU.room&&SFU.room.localParticipant)||null; if(lp && typeof sfuSetMic==='function'){ await sfuSetMic(false); await sfuSetMic(true); break; } }catch(_){}
|
||||
await new Promise(r=>setTimeout(r,700));
|
||||
}
|
||||
});
|
||||
console.log('[callkit] native calling ready');
|
||||
}
|
||||
// Register an OUTGOING call with CallKit so it's a system call too (→ background audio for outgoing calls).
|
||||
|
||||
Reference in New Issue
Block a user