Files
Toju/emergency-fix/03-architecture-map.md
T
myxelium e49b3ec112 chore: dev-stack switches, shared e2e harness, and desktop shell rules
- `LIVE_RELOAD=false npm run dev` keeps the renderer alive across a machine
  suspend; the reload client otherwise destroys the session under test.
- `dev-peer.sh` plus a separate userdata dir runs a second local peer.
- `tools/voice-probe.js` samples peer state and RTP counters from a live
  window, persisting to localStorage so a renderer reload cannot erase it.
- e2e helpers for voice pairs, peer-role election, and a TURN relay.
- Electron single-instance and dev-client-load decisions move into rules
  files with colocated specs.
2026-08-14 03:19:29 +02:00

4.9 KiB
Raw Blame History

03 — Architecture map (for agents)

Bounded contexts (product client)

See toju-app/src/app/domains/README.md. Emergency-relevant domains:

Domain Owns
authentication Login/register HTTP, provision secret, per-URL credentials, authorize navigation
server-directory Endpoints, health, discovery, invites, room metadata affinity
chat Message rules, sync rules, chat UI
direct-message DMs, friends, offline queue, PeerDelivery usage
direct-call Private call sessions / rings
voice-session Join/leave bookkeeping, floating controls, settings storage
voice-connection Facade over realtime for mic/camera/playback/VAD
screen-share Picker / quality
attachment Chunked P2P files
access-control Permissions / bans

Infrastructure (not a domain): infrastructure/realtime/ (WebRTC + signaling), infrastructure/persistence/, infrastructure/mobile/.

Global NgRx: store/users, store/rooms, store/messages — orchestration across domains.


Identity & credentials

Home profile (NgRx User)
  id / username / displayName / homeSignalServerUrl
        │
        ├─ AuthTokenStore (legacy per-URL token)     metoyou.authTokens
        ├─ SignalServerCredentialStore               metoyou.signalServerCredentials
        │     { serverUrl → userId, token, provisioned? }
        └─ ProvisionSecretStore (per home user id)
              Electron safeStorage | web sessionStorage

Actor resolution for a room: SignalServerAuthService.resolveActorUserIdForServer(sourceUrl, homeOderId) — foreign URL must use provisioned userId.

Identify on socket: SignalingTransportHandler.getIdentifyCredentialsForSignalUrl(url) must resolve token+actor for that URL (store fallback).
Danger: getIdentifyCredentials() returns home credential — used for some localOderId / polite-peer paths → cross-signal initiator bugs (06, 09).


Room ↔ signal affinity

Room { id, channels[], sourceUrl, sourceId, ... }
        │
        ▼
RoomSignalingConnection
  ensureCredentialForServerUrl(sourceUrl)
  identify(actor for sourceUrl)
  join_server / view_server on that WS
        │
        ▼
ServerSignalingCoordinator
  maps serverId ↔ signalUrl ↔ peers

Fallback: try other online compatible endpoints on outage; do not treat Cloudflare 521/522 as “client incompatible”. Non-federated: peers must converge on same endpoint for that room.


Realtime composition root

WebRTCService (realtime-session.service.ts) wires:

  • SignalingTransportHandler → many SignalingManager (one WS per URL)
  • PeerConnectionManager → negotiation, DC, recovery
  • MediaManager / noise / screen share
  • WebRtcStateController (signals)

Inbound WS → SignalingMessageHandler → users/rooms/voice/chat effects.


Message send path (server channel)

Composer → MessagesActions.sendMessage({ id? })
  → local DB + NgRx
  → DC broadcast chat-message (+ message-revision)
  → WS chat_message fallback (room members)
  → account_sync to sibling devices

Receive gates: room must be current or saved; channel scoping for text.


Voice join path

UI join channel
  → VoiceSessionFacade startSession
  → leave previous exclusive target
  → enableVoice / heartbeat
  → voice_state on WS (+ DC control)
  → MediaManager.syncVoiceRouting (same channel only)
  → VoicePlaybackService for remote same-channel peers

Peer PC may already exist from presence mesh; join mainly attaches tracks + announces state.


Call / DM delivery path

DirectCallService / DirectMessageService
  → PeerDeliveryService
       1) data channel if open
       2) signaling forward (targetUserId)
       3) offline queue (DM) / silent fail (call if ignored)

Inbound call admission: direct-call-participant-identity.rules.ts (aliases — implemented).
Outbound routable id pick: documented in LESSONS as fixed; symbols not in tree — see 10-code-lies.


Recovery constants (cheat sheet)

Constant Value
WS reconnect backoff 1s → 30s
Connect timeout 5s
Keepalive interval / ack timeout 25s / 10s
Health probe 5s
Peer disconnect grace 10s
Peer reconnect 5s × 12 then silent abandon
DC recovery grace (closing) 2.5s
Non-initiator give-up 5s
Offer-sent grace 20s
Inventory / full sync limit 20_000
Sync batch 200
Sync poll 10s fast / 15 min slow

Source: realtime.constants.ts, message-sync.rules.ts.


Server responsibilities (only when needed)

Default agent scope excludes server/ unless packet expands. Know this:

  • Serializes WS handlers per connection (identify-before-join).
  • Relays RTC only when peers share membership (DM types exempt).
  • No message persistence.
  • voiceActive routes offers to owning connection.

Canonical envelopes: agents-docs/features/signaling.md.