Merge pull request #2164 from vitorpamplona/claude/fix-old-call-ringing-nM8y5
Prevent stale call offers from retriggering after app restart
This commit is contained in:
+44
-1
@@ -71,6 +71,17 @@ class CallManager(
|
||||
private var resetJob: Job? = null
|
||||
private val processedEventIds = mutableSetOf<String>()
|
||||
|
||||
/** Call IDs for which we have seen a hangup, reject, or answer-elsewhere
|
||||
* signal. Checked before transitioning to [CallState.IncomingCall] so
|
||||
* that stale offer events replayed by relays after an app restart do not
|
||||
* trigger ringing for calls that already ended. */
|
||||
private val completedCallIds = mutableSetOf<String>()
|
||||
|
||||
/** Timestamp (epoch seconds) when this CallManager was created. Events
|
||||
* created before this are from a previous app session and should not
|
||||
* trigger ringing. */
|
||||
private val initTimestamp = TimeUtils.now()
|
||||
|
||||
/** Peers whose answers we saw while still ringing (IncomingCall).
|
||||
* After we accept, we trigger callee-to-callee mesh setup with them. */
|
||||
private val discoveredCalleePeers = mutableSetOf<HexKey>()
|
||||
@@ -81,7 +92,16 @@ class CallManager(
|
||||
const val MAX_EVENT_AGE_SECONDS = 20L // discard signaling events older than this
|
||||
}
|
||||
|
||||
private fun isEventTooOld(event: Event): Boolean = TimeUtils.now() - event.createdAt > MAX_EVENT_AGE_SECONDS
|
||||
private fun isEventTooOld(event: Event): Boolean {
|
||||
val age = TimeUtils.now() - event.createdAt
|
||||
if (age > MAX_EVENT_AGE_SECONDS) return true
|
||||
// Reject events created before this CallManager was initialized.
|
||||
// After an app restart the relay replays old events; even if the
|
||||
// wall-clock age is within the window, events from a previous
|
||||
// session should never start ringing.
|
||||
if (event.createdAt < initTimestamp - MAX_EVENT_AGE_SECONDS) return true
|
||||
return false
|
||||
}
|
||||
|
||||
// ---- Call initiation (state + publish) ----
|
||||
|
||||
@@ -174,6 +194,11 @@ class CallManager(
|
||||
return
|
||||
}
|
||||
|
||||
if (callId in completedCallIds) {
|
||||
Log.d("CallManager") { "onIncomingCallEvent: callId=$callId already completed — ignoring stale offer" }
|
||||
return
|
||||
}
|
||||
|
||||
val currentState = _state.value
|
||||
|
||||
// Mid-call offer: same call-id, we're already in the call
|
||||
@@ -607,6 +632,21 @@ class CallManager(
|
||||
|
||||
Log.d("CallManager") { "Processing signaling event kind=${event.kind} id=${event.id.take(8)} state=${_state.value::class.simpleName}" }
|
||||
|
||||
// Record call-ids from termination signals so that a later offer
|
||||
// for the same call is recognised as stale (common after app restart
|
||||
// when relay replays events out of order).
|
||||
if (event is CallHangupEvent || event is CallRejectEvent) {
|
||||
val terminatedCallId =
|
||||
when (event) {
|
||||
is CallHangupEvent -> event.callId()
|
||||
is CallRejectEvent -> event.callId()
|
||||
else -> null
|
||||
}
|
||||
if (terminatedCallId != null) {
|
||||
completedCallIds.add(terminatedCallId)
|
||||
}
|
||||
}
|
||||
|
||||
when (event) {
|
||||
is CallOfferEvent -> onIncomingCallEvent(event)
|
||||
is CallAnswerEvent -> onCallAnswered(event)
|
||||
@@ -655,6 +695,8 @@ class CallManager(
|
||||
resetJob = null
|
||||
processedEventIds.clear()
|
||||
discoveredCalleePeers.clear()
|
||||
// Note: completedCallIds is intentionally NOT cleared here so that
|
||||
// stale offers for previously-ended calls remain blocked.
|
||||
}
|
||||
|
||||
private fun transitionToEnded(
|
||||
@@ -662,6 +704,7 @@ class CallManager(
|
||||
peerPubKeys: Set<HexKey>,
|
||||
reason: EndReason,
|
||||
) {
|
||||
completedCallIds.add(callId)
|
||||
_state.value = CallState.Ended(callId, peerPubKeys, reason)
|
||||
cancelTimeout()
|
||||
resetJob?.cancel()
|
||||
|
||||
Reference in New Issue
Block a user