Track declined notification IDs and re-check them after resolveCallId await to block stale rings when a decline lands during the async yield.
This commit is contained in:
parent
fb6f7bd982
commit
aaebdffc4d
1 changed files with 33 additions and 7 deletions
|
|
@ -135,6 +135,11 @@ export const useIncomingRtcNotifications = (): void => {
|
|||
|
||||
const registryRef = useRef<Map<string, RegistryEntry>>(new Map());
|
||||
|
||||
// Notification IDs whose RTCDecline already arrived — stops a late-decrypted
|
||||
// RTCNotification from resurrecting an already-declined ring when decline is
|
||||
// cleartext (killed-state receiver path) but notification is encrypted.
|
||||
const declinedTimersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
||||
|
||||
// When local user joins any call (via header / other UI), drop any toast for that room.
|
||||
useEffect(() => {
|
||||
if (callEmbed) {
|
||||
|
|
@ -161,6 +166,7 @@ export const useIncomingRtcNotifications = (): void => {
|
|||
|
||||
useEffect(() => {
|
||||
const registry = registryRef.current;
|
||||
const declinedTimers = declinedTimersRef.current;
|
||||
|
||||
const removeByKey = (key: string) => {
|
||||
const entry = registry.get(key);
|
||||
|
|
@ -205,10 +211,22 @@ export const useIncomingRtcNotifications = (): void => {
|
|||
return setTimeout(() => removeByKey(key), delay);
|
||||
};
|
||||
|
||||
const rememberDeclined = (notifEventId: string) => {
|
||||
const existing = declinedTimers.get(notifEventId);
|
||||
if (existing) clearTimeout(existing);
|
||||
const timer = setTimeout(() => {
|
||||
declinedTimers.delete(notifEventId);
|
||||
}, RTC_NOTIFICATION_DEFAULT_LIFETIME);
|
||||
declinedTimers.set(notifEventId, timer);
|
||||
};
|
||||
|
||||
const processEvent = async (ev: MatrixEvent, room: Room): Promise<void> => {
|
||||
if (ev.getType() === EventType.RTCDecline) {
|
||||
const rel = ev.getRelation();
|
||||
if (rel?.event_id) removeByNotifId(rel.event_id);
|
||||
if (rel?.event_id) {
|
||||
rememberDeclined(rel.event_id);
|
||||
removeByNotifId(rel.event_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -230,15 +248,20 @@ export const useIncomingRtcNotifications = (): void => {
|
|||
|
||||
const sender = ev.getSender();
|
||||
if (!sender) return;
|
||||
const callId = await resolveCallId(mx, room, sender, rel.event_id);
|
||||
if (callId === undefined) return;
|
||||
|
||||
const evId = ev.getId();
|
||||
if (!evId) return;
|
||||
if (declinedTimers.has(evId)) return;
|
||||
|
||||
// Re-check membership after the (possibly networked) callId resolve —
|
||||
// a join event from another device could have landed during the await.
|
||||
const callId = await resolveCallId(mx, room, sender, rel.event_id);
|
||||
if (callId === undefined) return;
|
||||
|
||||
// Re-check anything that can change during the await. resolveCallId can
|
||||
// yield for seconds (5s MembershipsChanged wait, then fetchRoomEvent) —
|
||||
// a membership join or a matching decline can land meanwhile and must be
|
||||
// observed before we commit the ADD.
|
||||
if (session.memberships.some((m) => m.sender === mx.getUserId())) return;
|
||||
if (declinedTimers.has(evId)) return;
|
||||
|
||||
const key = getIncomingCallKey(callId, room.roomId);
|
||||
if (registry.has(key)) return;
|
||||
|
|
@ -284,8 +307,9 @@ export const useIncomingRtcNotifications = (): void => {
|
|||
const room = mx.getRoom(roomId);
|
||||
if (!room) return;
|
||||
// No liveEvent flag on Decrypted. Backfill safety relies on
|
||||
// `isRtcNotificationExpired` (ring branch) and `registry.has(key)` dedup;
|
||||
// a stale decline just no-ops via `removeByNotifId`.
|
||||
// `isRtcNotificationExpired` (ring branch), `registry.has(key)` dedup,
|
||||
// and `declinedTimersRef` (blocks a late-decrypted notification whose
|
||||
// decline already landed cleartext-first on the timeline).
|
||||
processEvent(ev, room);
|
||||
};
|
||||
|
||||
|
|
@ -313,6 +337,8 @@ export const useIncomingRtcNotifications = (): void => {
|
|||
entry.unsubMemberships?.();
|
||||
});
|
||||
registry.clear();
|
||||
declinedTimers.forEach((timer) => clearTimeout(timer));
|
||||
declinedTimers.clear();
|
||||
};
|
||||
}, [mx, setIncoming]);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue