CI / Build & Quality Checks (pull_request) Successful in 1m39s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 7s
CI / Playwright smoke (e2e) (pull_request) Successful in 10m58s
After a reload/restart within a ring's lifetime, the catch-up sync replays the ring together with the caller's hang-up and our own decline, and the MatrixRTC session can keep the stale join. The client rang again for up to 90 s and showed "1 Live" (call screen instead of chat) for minutes. - Before ringing, check the room STATE (utils/callRing.ts, unit-tested): no ring if we already declined that ring or are in the call; if the caller has no active membership yet, re-check after 4 s (a real call's membership can land just after its ring) and ring only if they joined. - The "caller gone" dismissal also consults room state, not only the possibly stale session. - When sync becomes live (startup or reconnect), recompute every active RTC session from the current state, clearing a stale "Live". Verified headless (Bob's membership + ring in an invite-only room): live ring rings; hang-up stops it; reload within the window: before rang again + "1 Live", now no ring and no "Live"; a ring that arrives just before the membership still rings. Fixes #312 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
53 lines
2.2 KiB
TypeScript
53 lines
2.2 KiB
TypeScript
import { JoinRule, Room } from 'matrix-js-sdk';
|
|
import { StateEvent } from '../../types/matrix/room';
|
|
import { getStateEvents } from './room';
|
|
|
|
/**
|
|
* Whether a call notification in this room may ring — and, by the same rule,
|
|
* whether an unanswered one may show as "Missed call" (Gitea #162 follow-up).
|
|
* Only DMs and invite-only groups outside any space ring, i.e. rooms where the
|
|
* call button is shown. Persistent voice rooms, space channels, restricted and
|
|
* public rooms never do: joining a voice channel alone is normal and isn't a
|
|
* "call" anyone missed.
|
|
*/
|
|
export const isRingableRoom = (room: Room, isDirect: boolean): boolean => {
|
|
if (room.isCallRoom()) return false;
|
|
if (isDirect) return true;
|
|
const isSpaceChild = getStateEvents(room, StateEvent.SpaceParent).length > 0;
|
|
return !isSpaceChild && room.getJoinRule() === JoinRule.Invite;
|
|
};
|
|
|
|
/** A call-membership state event that is still "in the call" (left = empty content). */
|
|
const isActiveMembership = (content: Record<string, unknown> | undefined): boolean =>
|
|
!!content &&
|
|
(typeof content.application === 'string' ||
|
|
(Array.isArray(content.memberships) && content.memberships.length > 0));
|
|
|
|
/**
|
|
* [Gitea #312] Whether `userId` is in a call in `room` according to the room's
|
|
* current STATE. After a reload the MatrixRTC session can be stale (a join and
|
|
* leave replayed in one catch-up batch), but the state is right.
|
|
*/
|
|
export const hasActiveCallMembership = (room: Room, userId: string): boolean =>
|
|
getStateEvents(room, StateEvent.GroupCallMemberPrefix).some(
|
|
(ev) =>
|
|
(ev.getSender() === userId || ev.getStateKey()?.includes(userId)) &&
|
|
isActiveMembership(ev.getContent()),
|
|
);
|
|
|
|
/**
|
|
* [Gitea #312] Whether we already handled the ring `refEventId` points at:
|
|
* declined it (our own RTCDecline relates to it) — e.g. replayed together with
|
|
* the ring in the first sync after a restart, before any dismiss listener.
|
|
*/
|
|
export const declinedRing = (room: Room, refEventId: string, myUserId: string): boolean =>
|
|
room
|
|
.getLiveTimeline()
|
|
.getEvents()
|
|
.some(
|
|
(ev) =>
|
|
ev.getType() === 'org.matrix.msc4310.rtc.decline' &&
|
|
ev.getSender() === myUserId &&
|
|
ev.getRelation()?.event_id === refEventId,
|
|
);
|