feat(calls): rejoin the voice room after a crash, update or reload (#118)

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
This commit is contained in:
2026-09-20 14:50:57 -04:00
co-authored by Claude Opus 5
parent 22d46a7922
commit dc0d524989
7 changed files with 344 additions and 0 deletions
+2
View File
@@ -6,6 +6,7 @@ import { clearRecentGifs } from './recentGifs';
import { clearRecentStickers } from './recentStickers';
import { clearNavToActivePathStore } from './navToActivePath';
import { DRAFT_MSG_KEY_PREFIX } from '../utils/draft';
import { clearCallSession } from '../utils/callRejoin';
/**
* [Gitea #41] Wipe every persisted composer draft (`draft-msg-<roomId>`). Drafts
@@ -92,6 +93,7 @@ export const clearPlaintextCaches = (userId?: string): void => {
clearRecentGifs();
clearRecentStickers();
clearMsgDrafts();
clearCallSession();
clearStatusMessage();
if (userId) clearNavToActivePathStore(userId);
};
+3
View File
@@ -271,6 +271,8 @@ export interface Settings {
// [Gitea #109] Drop EXIF/XMP/IPTC (GPS, camera, timestamp) from JPEG/PNG/WebP
// uploads without re-encoding. Default on.
stripImageMetadata: boolean;
// [Gitea #118] After a crash/update/reload while in a voice room: ask, rejoin, or nothing.
callRejoinAfterRestart: 'ask' | 'auto' | 'off';
// [Gitea #104] Mirror user preferences to `io.lotus.settings` account data
// so other devices pick them up. Device-local itself (utils/settingsSync).
@@ -394,6 +396,7 @@ const defaultSettings: Settings = {
stripTrackingParams: true,
stripImageMetadata: true,
callRejoinAfterRestart: 'ask',
settingsSync: true,