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());
|
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.
|
// When local user joins any call (via header / other UI), drop any toast for that room.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (callEmbed) {
|
if (callEmbed) {
|
||||||
|
|
@ -161,6 +166,7 @@ export const useIncomingRtcNotifications = (): void => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const registry = registryRef.current;
|
const registry = registryRef.current;
|
||||||
|
const declinedTimers = declinedTimersRef.current;
|
||||||
|
|
||||||
const removeByKey = (key: string) => {
|
const removeByKey = (key: string) => {
|
||||||
const entry = registry.get(key);
|
const entry = registry.get(key);
|
||||||
|
|
@ -205,10 +211,22 @@ export const useIncomingRtcNotifications = (): void => {
|
||||||
return setTimeout(() => removeByKey(key), delay);
|
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> => {
|
const processEvent = async (ev: MatrixEvent, room: Room): Promise<void> => {
|
||||||
if (ev.getType() === EventType.RTCDecline) {
|
if (ev.getType() === EventType.RTCDecline) {
|
||||||
const rel = ev.getRelation();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,15 +248,20 @@ export const useIncomingRtcNotifications = (): void => {
|
||||||
|
|
||||||
const sender = ev.getSender();
|
const sender = ev.getSender();
|
||||||
if (!sender) return;
|
if (!sender) return;
|
||||||
const callId = await resolveCallId(mx, room, sender, rel.event_id);
|
|
||||||
if (callId === undefined) return;
|
|
||||||
|
|
||||||
const evId = ev.getId();
|
const evId = ev.getId();
|
||||||
if (!evId) return;
|
if (!evId) return;
|
||||||
|
if (declinedTimers.has(evId)) return;
|
||||||
|
|
||||||
// Re-check membership after the (possibly networked) callId resolve —
|
const callId = await resolveCallId(mx, room, sender, rel.event_id);
|
||||||
// a join event from another device could have landed during the await.
|
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 (session.memberships.some((m) => m.sender === mx.getUserId())) return;
|
||||||
|
if (declinedTimers.has(evId)) return;
|
||||||
|
|
||||||
const key = getIncomingCallKey(callId, room.roomId);
|
const key = getIncomingCallKey(callId, room.roomId);
|
||||||
if (registry.has(key)) return;
|
if (registry.has(key)) return;
|
||||||
|
|
@ -284,8 +307,9 @@ export const useIncomingRtcNotifications = (): void => {
|
||||||
const room = mx.getRoom(roomId);
|
const room = mx.getRoom(roomId);
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
// No liveEvent flag on Decrypted. Backfill safety relies on
|
// No liveEvent flag on Decrypted. Backfill safety relies on
|
||||||
// `isRtcNotificationExpired` (ring branch) and `registry.has(key)` dedup;
|
// `isRtcNotificationExpired` (ring branch), `registry.has(key)` dedup,
|
||||||
// a stale decline just no-ops via `removeByNotifId`.
|
// and `declinedTimersRef` (blocks a late-decrypted notification whose
|
||||||
|
// decline already landed cleartext-first on the timeline).
|
||||||
processEvent(ev, room);
|
processEvent(ev, room);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -313,6 +337,8 @@ export const useIncomingRtcNotifications = (): void => {
|
||||||
entry.unsubMemberships?.();
|
entry.unsubMemberships?.();
|
||||||
});
|
});
|
||||||
registry.clear();
|
registry.clear();
|
||||||
|
declinedTimers.forEach((timer) => clearTimeout(timer));
|
||||||
|
declinedTimers.clear();
|
||||||
};
|
};
|
||||||
}, [mx, setIncoming]);
|
}, [mx, setIncoming]);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue