While joined, a device-local record {roomId, deviceId, joinedAt,
lastSeen, mic, video} is written and refreshed every 30 s; a deliberate
hangup (HangupCall/Close) and logout clear it. On the next start, once
the first sync is in and the room's MatrixRTC session has reported its
members (waited for up to 10 s — it fills a moment after sync), a fresh
record (< 10 min) for this device with someone still in the call yields
either a sticky "Rejoin voice?" toast (tap to rejoin) or an automatic
rejoin, per the new Settings → Calls → After a Restart (Ask / Rejoin
automatically / Do nothing; default Ask). Skipped when our own membership
is already live from another device, or only our stale one is left.
Mic/camera state comes from the record (camera still gated by
cameraOnJoin); PTT is applied by startCall as usual.
Pure decision in utils/callRejoin.ts with tests. Verified headless:
reload mid-call → toast → tap → back in the call; hangup → reload → no
toast; auto mode → back in without a prompt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
57 lines
1.9 KiB
TypeScript
57 lines
1.9 KiB
TypeScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { CALL_REJOIN_MAX_AGE_MS, decideRejoin, RejoinContext } from './callRejoin';
|
|
|
|
const base: RejoinContext = {
|
|
record: {
|
|
roomId: '!r',
|
|
deviceId: 'DEV',
|
|
joinedAt: 900_000,
|
|
lastSeen: 1_000_000,
|
|
microphone: true,
|
|
video: false,
|
|
},
|
|
mode: 'ask',
|
|
now: 1_060_000,
|
|
myDeviceId: 'DEV',
|
|
roomJoined: true,
|
|
ownMemberDevices: [],
|
|
memberCount: 2,
|
|
};
|
|
|
|
describe('decideRejoin', () => {
|
|
it('asks by default when the call is still going', () => {
|
|
assert.deepEqual(decideRejoin(base), { action: 'ask', roomId: '!r' });
|
|
assert.deepEqual(decideRejoin({ ...base, mode: 'auto' }), { action: 'auto', roomId: '!r' });
|
|
});
|
|
|
|
it('does nothing when off, stale, another device, or no record', () => {
|
|
assert.deepEqual(decideRejoin({ ...base, mode: 'off' }), { action: 'none' });
|
|
assert.deepEqual(decideRejoin({ ...base, record: undefined }), { action: 'none' });
|
|
assert.deepEqual(decideRejoin({ ...base, now: 1_000_000 + CALL_REJOIN_MAX_AGE_MS + 1 }), {
|
|
action: 'none',
|
|
});
|
|
assert.deepEqual(decideRejoin({ ...base, myDeviceId: 'OTHER' }), { action: 'none' });
|
|
assert.deepEqual(decideRejoin({ ...base, roomJoined: false }), { action: 'none' });
|
|
});
|
|
|
|
it('skips when we already rejoined from another device', () => {
|
|
assert.deepEqual(decideRejoin({ ...base, ownMemberDevices: ['PHONE'], memberCount: 2 }), {
|
|
action: 'none',
|
|
});
|
|
});
|
|
|
|
it('skips when only our own stale membership is left', () => {
|
|
assert.deepEqual(decideRejoin({ ...base, ownMemberDevices: ['DEV'], memberCount: 1 }), {
|
|
action: 'none',
|
|
});
|
|
assert.deepEqual(decideRejoin({ ...base, ownMemberDevices: [], memberCount: 0 }), {
|
|
action: 'none',
|
|
});
|
|
assert.deepEqual(decideRejoin({ ...base, ownMemberDevices: ['DEV'], memberCount: 2 }), {
|
|
action: 'ask',
|
|
roomId: '!r',
|
|
});
|
|
});
|
|
});
|