The web session generates new Olm keys for an existing device ID.
Synapse rejects the upload (400 — keys mismatch) which crashes the
entire client init. Result: no sync, no room history, no calls.
Proper E2EE requires persistent Olm account storage (IndexedDB) so
the same device ID always sends the same keys. Deferred to Phase 3.
Without Olm loaded, the Matrix SDK gracefully skips encryption init.
Unencrypted rooms work fully. E2EE rooms show 'Encrypted message'.
Also unregisters stale service workers that served cached old JS
(caused calls to use the old GET URL instead of POST /sfu/get).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_RemoteVideoView watched liveKitServiceProvider which returns the same
keepAlive instance. When activeRoom was set after connecting, Riverpod
didn't detect the field change so the widget stayed on the null path.
Fix: also watch callControllerProvider — when state transitions from
Connecting to Active, the widget rebuilds and finds activeRoom non-null,
then ListenableBuilder takes over for participant/track changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Listens for ParticipantDisconnectedEvent on the LiveKit Room. When no
remote participants remain (1:1 call ended by other side), waits 5
seconds then auto-hangs up. The delay allows for brief reconnections.
Also ensures our app properly cleans up when Element X user hangs up
on their end — our side won't sit connected to an empty LiveKit room.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Element X stays connected because it sees the state event clear while
we're still in the LiveKit room. Reversed the order: disconnect from
LiveKit first (remote party sees us leave), then clear the state event.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_RemoteVideoView and _LocalVideoPip watched the Riverpod provider but
not the LiveKit Room's ChangeNotifier. When a remote participant joined
or published a track, notifyListeners() fired but no widget rebuilt.
Fix: wrap both views in ListenableBuilder(listenable: room) so they
rebuild on every Room event (participant connect, track subscribe, etc).
Added _AudioOnlyPlaceholder for when remote has audio but no video.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When starting a call, the app now sends org.matrix.msc3401.call.member
state event to the Matrix room with LiveKit foci info. This is what
Element X listens for to show incoming call notifications.
On disconnect, the state event is cleared (empty memberships) so
Element X knows the call has ended.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- lk-jwt-service returns {jwt: '...', url: '...'} not {token: '...'}
Now reads both 'jwt' and 'token' fields for compatibility
- Server nginx: added proxy_hide_header to strip upstream CORS headers
preventing duplicate Access-Control-Allow-Origin (browser rejects)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Calls:
- JWT fetch now uses correct MSC4143 flow: get OpenID token from
Synapse, then POST to /_matrix/livekit/jwt/sfu/get (was using
GET with Bearer token to wrong path — returned 301→404)
- Error messages now visible for 3 seconds before popping screen
(was flashing away instantly — user couldn't see failure reason)
- Voice vs video calls differentiated via ?video=0/1 query param
- Debug logging added to JWT flow for troubleshooting
Messages:
- Chat timeline now shows newest at bottom (standard behaviour).
Was reversed twice: SDK returns newest-first, code reversed to
oldest-first, then ListView(reverse:true) put oldest at bottom.
Removed the extra .reversed — newest-first + reverse:true = correct.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add 12s timeout to restoreSession() — client.init() could hang
indefinitely if token expired or server unreachable, leaving user
on spinner forever. Now clears stale credentials and shows login.
- Sync persistence startup wrapped in try-catch — non-fatal
- .htaccess blocks .git directory (was returning 200 to scanners)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>