Several localStorage caches held decrypted message content or user PII and
survived a normal logout, leaving residue on a shared device (the search
index was already wiped; these were not):
- cinny_scheduled_messages_v1 - decrypted IContent.body of pending sends
- cinny_recent_searches_v1 - search query text
- cinny_recent_forward_targets_v1 - recent forward contact/room graph
- cinny_recent_gifs_v1 / cinny_recent_stickers_v1 - media the user sent
- navToActivePath<userId> - per-space last-visited room paths
- (plus the translation cache added earlier)
Add a clear function per module and a single auditable clearPlaintextCaches()
aggregator, called from both logout paths (logoutClient + the server-forced
SessionLoggedOut handler) alongside the existing session/search-index wipes.
Unit-tested.
Deliberately NOT cleared (documented in the aggregator): unsent composer
drafts and the presence status message (preserved by product decision N98);
SDK sync/crypto store + io.lotus.* account data (reminders/bookmarks/notes),
already wiped by mx.clearStores(); low-sensitivity UI/metadata residue.
The forward-targets/gifs/stickers/nav-path additions and the accurate
"not covered" documentation address findings from two review passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up hardening from two review passes on the on-device translation
feature:
- Privacy (HIGH): the translation cache is decrypted message plaintext,
but logout did not clear it (unlike the search index), leaving up to
300 cleartext bodies in localStorage on shared devices. Add
clearTranslationCache() and call it from both logout paths
(logoutClient and the server-forced SessionLoggedOut handler).
- Edited messages (MEDIUM): the cache key was eventId:target with no
content dependence, so an edit reused the pre-edit translation. Fold a
content fingerprint into the key, and re-arm the auto-translate
one-shot when the text changes.
- Settings (LOW): coerce a persisted translateTargetLang to a supported
curated code so the hook never targets a language the engine can't
produce (previously only the UI clamped it).
- Chinese (LOW): restore canonical BCP-47 case (zh-Hant / zh-Hans) at
the Translator API boundary, since normalizeLang lowercases the script
subtag for internal keys.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The IndexedDB crypto store is evictable while the localStorage session survives,
so the browser can drop it out from under a live login -> the device resurrects
with a blank key store and re-uploads a one-time key at an id Synapse already
holds -> a permanent '400 M_UNKNOWN: One time key ... already exists' upload
storm (and undecryptable to-device/media keys downstream).
initClient now calls navigator.storage.persist() before creating the crypto
store, so the origin's storage is marked persistent and won't be evicted.
Best-effort (granted by engagement/PWA-install, no prompt; denial is non-fatal).
Preventive only -- an already-diverged device still needs a clean re-login.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
B2 of the Matrix protocol-gaps roadmap, gate-green (688 tests):
- Enable QR verification methods (show/scan/reciprocate) in initMatrix.
- Extend DeviceVerification: the Ready step offers your own QR (byte-mode encode
via qrcode), a camera 'Scan their QR code' flow, and an emoji fallback; the
Started step routes reciprocate → a confirm step (useVerifierShowReciprocateQr)
or SAS as before.
- New QrScanner component: getUserMedia + jsQR, handing the raw binaryData bytes
to request.scanQRCode (BarcodeDetector is string-only, so can't be used).
- Adds qrcode + jsqr (small, pure-JS, client-only); build-verified under rolldown.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
threadSupport:true makes matrix-js-sdk partition m.thread relations into Thread
objects (replies leave the main timeline; roots stay). markAsRead now sends
UNTHREADED receipts so one receipt still clears room + thread notification
counts — without this, badges would stick unread. The thread panel + summary
chips land in the same push.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Raw-IndexedDB cache (lotus-search-cache: messages keyed [roomId,eventId] +
per-room coverage) merged into local search with in-memory-wins dedupe. OPT-IN
(default off) via a standalone atom — stores decrypted text at rest, so it ships
with a privacy note, a Clear button, and an unconditional wipe on logout
(initMatrix). All IDB errors degrade to cache-miss. +8 tests (1 IDB skip in node).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- initMatrix.ts: import the shared Session type; when a session has a refresh
token + oidc metadata, wire a LotusOidcTokenRefresher via createClient's
refreshToken + tokenRefreshFunction (reactive 401 refresh). Rust crypto is
unaffected (still keyed on userId/deviceId).
- client/oidcTokenRefresher.ts: OidcTokenRefresher subclass that persists rotated
tokens back to the fallback session.
- client/oidcLogout.ts + logoutClient: best-effort revoke access+refresh tokens at
the issuer's revocation_endpoint on logout (tolerant of failure).
- settings/account/OidcManageAccount.tsx: MSC2965 "Manage account" deep-link,
shown only when authMetadata is present (OIDC servers); mirrors OtherDevices.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- N98: logoutClient and handleLogout now call removeFallbackSession() (removes
only the 4 session credential keys) instead of window.localStorage.clear(),
so settings, unsent drafts, PiP position, and status are preserved across a
normal logout. localStorage.clear() stays reserved for clearLoginData() (the
explicit factory-reset path).
- N99: the useSyncState callback now handles ERROR/STOPPED. A sync failure
before the first PREPARED (offline at startup, homeserver unreachable) shows
a dedicated error splash with a Retry button (startMatrix) instead of an
endless "Heating up" spinner alongside a contradictory "Connection Lost!"
banner. Guarded by a hasPreparedRef so post-PREPARED transient errors still
go through <SyncStatus>; PREPARED self-heals the splash on recovery, and the
redundant banner is suppressed while the splash is shown.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- EditHistoryModal: use raw fetch with /_matrix/client/v1/ path for
relations API — Synapse only supports relations at v1, not v3 (fixes
Sentry JAVASCRIPT-REACT-K / 404 error)
- RoomViewHeader: remove useReportRoomSupported spec-version gate —
Synapse 1.114+ has the endpoint but only advertises spec v1.12;
button now always shows for non-creator, non-server-notice rooms
- ReportRoomModal: handle M_UNRECOGNIZED/404 with "not supported by
your homeserver" message
- RoomNavItem: add isServerNotice guard to Room Settings in sidebar
context menu (was only guarded in header three-dots menu)
- initMatrix: bump setMaxListeners from 50 → 150 to prevent
MaxListenersExceededWarning with large room lists
- RoomProfile: save topic with formatted_body + format when markdown
syntax is detected; add markdown hint below topic textarea
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each RoomNavItem subscribes to session_started/session_ended on the
MatrixRTCSessionManager, one per visible room. The default limit of 10
fires a spurious warning when 11+ rooms are in the sidebar. Listeners
are properly cleaned up — this is not a real leak.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- typescript 5.9.3 to 6.0.3
- moduleResolution Node to bundler (correct for Vite projects)
- target/lib ES2016 to ES2020 (enables flatMap, Promise.allSettled)
- Fix global to globalThis in initMatrix.ts (browser env)
- Fix EventEmitter default to named import in CallControl.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix prettier formatting in useCall.ts and initMatrix.ts (unblocks CI)
- Fix viteStaticCopy stripBase so manifest.json and public/locales/ land
at correct output paths (was getting extra 'public/' prefix from v4 path
preservation behavior)
- Silence react-router v7 HydrateFallback warning on root route (SPA has
no SSR hydration, null is intentional)
When matrix-sdk is briefly upgraded then reverted, the local IndexedDB
schema version is higher than the SDK expects. Detect the VersionError
DOMException and show a clear 'Clear local data and reload' button
instead of a cryptic error message.
* fix inaccessible space on alias change
* fix new room in space open in home
* allow opening space timeline
* hide event timeline feature behind dev tool
* add navToActivePath to clear cache function
* chore: Bump matrix-js-sdk to 34.4.0
* feat: Authenticated media support
* chore: Use Vite PWA for service worker support
* fix: Fix Vite PWA SW entry point
Forget this. :P
* fix: Also add Nginx rewrite for sw.js
* fix: Correct Nginx rewrite
* fix: Add Netlify redirect for sw.js
Otherwise the generic SPA rewrite to index.html would take effect, breaking Service Worker.
* fix: Account for subpath when regisering service worker
* chore: Correct types
* handle client boot error in loading screen
* use sync state hook in client root
* add loading screen options
* removed extra condition in loading finish
* add sync connection status bar