fix(correctness): call-invite clock skew, forceState, upload cancel (COR-3/5/6)

COR-3 (CallEmbedProvider): the incoming-call lifetime guard distrusted a
caller's sender_ts only when it was >20s AHEAD of the server ts. A caller clock
that ran SLOW left sender_ts in the past, so the ring auto-dismissed/never
showed for a fresh invite. Trust sender_ts only within ±20s of the server ts,
else fall back to it (also fixes a NaN path when sender_ts is missing).

COR-6 (CallControl): forceState rebuilt CallControlState with 5 args, silently
defaulting screenshareAudioMuted to false; pass this.screenshareAudioMuted.

COR-5 (uploadContent + useBindUploadAtom): cancelling during the retry back-off
was a no-op (mx.cancelUpload only aborts an in-flight request), so the upload
resurrected on the next attempt. Thread an AbortSignal: the back-off sleep
resolves early on abort and the loop stops with an abort error; the hook aborts
a per-upload AbortController on cancel (alongside mx.cancelUpload for the
in-flight case).

All verified by two review passes (no double-settle / no resurrection); includes
their suggested abort-listener cleanup on normal sleep resolution.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 21:28:31 -04:00
co-authored by Claude Opus 4.8
parent 3e1106b2d9
commit ab01d27aa7
4 changed files with 61 additions and 16 deletions
+9 -1
View File
@@ -473,8 +473,16 @@ function IncomingCallListener({ callEmbed, joined }: IncomingCallListenerProps)
const sender = event.getSender();
const content = event.getContent<IRTCNotificationContent>();
// Trust the caller's sender_ts only when it's within 20s of the server's
// timestamp in EITHER direction. A fast caller clock made a fresh invite
// look expired-in-the-future; a SLOW one left sender_ts in the past so
// `Date.now() >= senderTs + lifetime` hid/dismissed the ring for a
// genuinely fresh invite (COR-3). Fall back to the server ts on large skew.
const senderTs =
content.sender_ts - event.getTs() > 20000 ? event.getTs() : content.sender_ts;
typeof content.sender_ts === 'number' &&
Math.abs(content.sender_ts - event.getTs()) <= 20000
? content.sender_ts
: event.getTs();
const lifetime = Math.min(content.lifetime, 120000);
const notificationType = content.notification_type;
const relation =