2026-10-16 20:29:50 -04:00
|
|
|
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;
|
|
|
|
|
};
|
2026-10-16 21:28:33 -04:00
|
|
|
|
|
|
|
|
/** 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,
|
|
|
|
|
);
|