From 9d0fd93d7e07741fc5fcadd00e2c9600b485e1cc Mon Sep 17 00:00:00 2001 From: sravan Date: Mon, 3 Aug 2026 11:38:53 +0530 Subject: [PATCH] Fix outgoing-call re-ring: never send the cancel push to the caller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hanging up an UNANSWERED outgoing call re-rang the caller's own phone: the server sent the "cancel" VoIP push to BOTH parties, and the plugin must reportNewIncomingCall for every VoIP push (iOS rule) → a phantom ring on the caller who just hung up. Incoming calls don't hit this (an answered call sends no cancel). Fix: DM cancel goes only to the callee (not startedBy); group cancel skips the starter. Co-Authored-By: Claude Opus 4.8 --- server/calls.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/calls.js b/server/calls.js index 0c6bbb7..9b2e5d8 100644 --- a/server/calls.js +++ b/server/calls.js @@ -91,7 +91,7 @@ async function endGroupCallByRoom(room) { if (call.historyId && teamId) { try { await R.scheduledMeetings.end(call.historyId, teamId); } catch (_) {} } // mark the history row past broadcast(group, { type: 'group-call', group, active: false, room, uuid: call.uuid }); // Stop any CallKit ring on members' killed/backgrounded devices. - try { for (const mid of await R.conversations.members(group)) PUSH.sendCallCancel(mid, call.uuid); } catch (_) {} + try { for (const mid of await R.conversations.members(group)) { if (mid !== call.startedBy) PUSH.sendCallCancel(mid, call.uuid); } } catch (_) {} // not the starter — a cancel to them re-rings their own phone } } @@ -153,7 +153,10 @@ async function endDmCallByRoom(room, silent) { // Stop the CallKit ring on a device that's still RINGING (unanswered) — a killed/asleep callee has no WS // to receive the dm-call above. For an ANSWERED call both sides are awake (the WS event ends it), and a // cancel push would re-ring the device that just hung up — so only cancel when it was NOT answered. - if (!call.answered) { try { for (const uid of call.users) PUSH.sendCallCancel(uid, call.uuid); } catch (_) {} } + // Cancel the ring ONLY on the CALLEE's device(s) — never the caller (startedBy). The caller is placing the + // call, not ringing, so a cancel push to them made their OWN phone re-ring after they hung up an unanswered + // outgoing call (the plugin must reportNewIncomingCall for every VoIP push → a phantom ring on the caller). + if (!call.answered) { const callee = call.users.find((u) => u !== call.startedBy); if (callee) { try { PUSH.sendCallCancel(callee, 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) {