Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

13 — State Management (WS / Realtime Module)

How the realtime layer plugs into client state management (Bloc). Shared conventions: 00-shared/06 (State Management). The module owns one RealtimeBloc; everything else subscribes. Server-side state (rooms, sockets) is ephemeral and resettable by design — there is no persistence layer in source (gateway holds only live socket references).


1. RealtimeState (Bloc state)

StateMeaningExposes
RealtimeIdleapp start, never connected
RealtimeConnectingsocket opening, handshake in flightattempt #
RealtimeConnectedhandshake OK, in tenant room (ws.gateway.ts:50)userId, tenantId (from auth store)
RealtimeReconnectingretrying after unexpected closeattempt #, nextBackoffMs
RealtimeOfflineretries exhausted / transport deadlastError, manual retry available
RealtimeAuthErrorhandshake rejected (ws.gateway.ts:54-56)needs token refresh

Single cubit/bloc, app-scoped (DI singleton), mirrored by AppRealtimeStatusDot (07 §1). No screen holds connection state locally — never a second source of truth.

2. Event routing

RealtimeClient.events  (Stream<WsEnvelope>)
   → RealtimeBloc.router  (pure function: envelope → typed app event)
   → feature blocs subscribe:
        NotificationBloc        ← notification.created
        FeesDashboardBloc       ← payment.completed
        AttendanceBloc          ← attendance.updated
        HomeworkBloc            ← homework.published
        CommunicationBloc       ← communication.*      (planned)
        AdminRealtimeBloc       ← all (debug view, proposed)

Rules:

  • Router is a pure map eventType → AppEvent; unknown types → ignored (06 §6 — additive protocol).
  • Feature blocs own their merge semantics via AppLiveList (06 §3.1); RealtimeBloc never touches domain data.
  • Subscription teardown is explicit (bloc close() cancels stream subs) — prevents fan-out leaks on screen navigation (14 Q5).

3. Reconnect state machine (source-grounded)

Facts driving the machine: server verifies JWT on every connection (ws.gateway.ts:42-44); disconnects silently on failure (ws.gateway.ts:54-56); tenant room is joined automatically (ws.gateway.ts:50) but extra rooms die with the socket (ws.gateway.ts:63-68) — so every successful reconnect re-runs: handshake → join → re-subscribe rooms → reconcile.

stateDiagram-v2
    [*] --> Idle
    Idle --> Connecting : app start / manual retry
    Connecting --> Connected : handshake ok (ws.gateway.ts:42-50)
    Connecting --> AuthError : verify fails (ws.gateway.ts:54-56)
    Connecting --> Reconnecting : transport error
    Connected --> Reconnecting : heartbeat loss / close
    Connected --> Disconnecting : user logout
    Reconnecting --> Connecting : backoff delay elapsed
    Reconnecting --> Offline : attempts > maxBackoffCap
    Offline --> Connecting : "Retry now" / backoff timer
    AuthError --> Connecting : token refreshed via REST
    AuthError --> SignedOut : refresh failed (06 §1.2)
    Disconnecting --> Idle : socket closed cleanly
    Offline --> [*] : app destroyed

Notes:

  • Backoff: exponential with jitter, 1 s → 30 s cap (15 §4); jitter avoids reconnect storms (14 Q1).
  • On Connected from Reconnecting: emit RealtimeReconnected → subscribers run reconcile (re-subscribe rooms + one refetch per visible live screen, 06 §3.1).

4. Room membership state

RoomWho managesSource
tenant:{tenantId}server, automaticws.gateway.ts:50
extra roomsclient, re-applied after each reconnectws.gateway.ts:63-68

RealtimeBloc keeps a Set<String> subscribedRooms; after every Connected, it replays subscribe for each — this is the only piece of state that survives reconnects by design.

5. Persistence

  • No socket state is persisted (no replay — fire-and-forget bridge, ws-bridge.service.ts:16-22).
  • Token source: shared auth store (secure storage); RealtimeBloc reads it at connect and after refresh — never stores its own copy (single source of truth per 00-shared/06).
  • Last-envelope metadata (eventType, occurredAt) kept in-memory for the status sheet (05 §4) — non-critical, dropped on restart.

6. Testing strategy (client)

  • RealtimeBloc unit tests with a fake RealtimeClient (envelope-in → state-out).
  • Router tests: every mapped eventType → typed event; unknown → ignored.
  • State machine tests: full reconnect matrix incl. jitter bounds, auth-error→refresh.
  • AppLiveList merge tests: hit/miss/insert/refetch-bound (pure logic, no socket).