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
70 lines
2.8 KiB
TypeScript
70 lines
2.8 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import type { Room } from 'matrix-js-sdk';
|
|
import { declinedRing, hasActiveCallMembership, isRingableRoom } from './callRing';
|
|
|
|
const fakeRoom = (opts: { callRoom?: boolean; joinRule?: string; spaceParent?: boolean }): Room =>
|
|
({
|
|
isCallRoom: () => !!opts.callRoom,
|
|
getJoinRule: () => opts.joinRule ?? 'invite',
|
|
getLiveTimeline: () => ({
|
|
getState: () => ({ getStateEvents: () => (opts.spaceParent ? [{}] : []) }),
|
|
}),
|
|
}) as unknown as Room;
|
|
|
|
test('voice rooms never ring or show "missed call", even as DMs', () => {
|
|
assert.equal(isRingableRoom(fakeRoom({ callRoom: true }), false), false);
|
|
assert.equal(isRingableRoom(fakeRoom({ callRoom: true }), true), false);
|
|
});
|
|
|
|
test('DMs and invite-only groups outside a space ring', () => {
|
|
assert.equal(isRingableRoom(fakeRoom({ joinRule: 'public' }), true), true);
|
|
assert.equal(isRingableRoom(fakeRoom({ joinRule: 'invite' }), false), true);
|
|
});
|
|
|
|
test('space channels, public and restricted rooms do not ring', () => {
|
|
assert.equal(isRingableRoom(fakeRoom({ joinRule: 'invite', spaceParent: true }), false), false);
|
|
assert.equal(isRingableRoom(fakeRoom({ joinRule: 'public' }), false), false);
|
|
assert.equal(isRingableRoom(fakeRoom({ joinRule: 'restricted' }), false), false);
|
|
});
|
|
|
|
const stateRoom = (
|
|
members: Array<{ sender: string; content: Record<string, unknown> }>,
|
|
timeline: unknown[] = [],
|
|
): Room =>
|
|
({
|
|
getLiveTimeline: () => ({
|
|
getState: () => ({
|
|
getStateEvents: () =>
|
|
members.map((m) => ({
|
|
getSender: () => m.sender,
|
|
getStateKey: () => `_${m.sender}_DEV_m.call`,
|
|
getContent: () => m.content,
|
|
})),
|
|
}),
|
|
getEvents: () => timeline,
|
|
}),
|
|
}) as unknown as Room;
|
|
|
|
test('hasActiveCallMembership reads the room state: joined vs left (#312)', () => {
|
|
const room = stateRoom([
|
|
{ sender: '@bob:x', content: { application: 'm.call', call_id: '' } },
|
|
{ sender: '@carol:x', content: {} },
|
|
]);
|
|
assert.equal(hasActiveCallMembership(room, '@bob:x'), true);
|
|
assert.equal(hasActiveCallMembership(room, '@carol:x'), false, 'left = empty content');
|
|
assert.equal(hasActiveCallMembership(room, '@dave:x'), false);
|
|
});
|
|
|
|
test('declinedRing finds our own decline of that ring only (#312)', () => {
|
|
const decline = (sender: string, rel: string) => ({
|
|
getType: () => 'org.matrix.msc4310.rtc.decline',
|
|
getSender: () => sender,
|
|
getRelation: () => ({ event_id: rel }),
|
|
});
|
|
const room = stateRoom([], [decline('@me:x', '$ring1'), decline('@bob:x', '$ring2')]);
|
|
assert.equal(declinedRing(room, '$ring1', '@me:x'), true);
|
|
assert.equal(declinedRing(room, '$ring2', '@me:x'), false, "someone else's decline");
|
|
assert.equal(declinedRing(room, '$ring3', '@me:x'), false);
|
|
});
|