Skip to content

Latest commit

 

History

History
119 lines (92 loc) · 5.29 KB

File metadata and controls

119 lines (92 loc) · 5.29 KB

LedFx Client Identity & WebSocket Protocol

This document describes how LedFx handles client identification, metadata synchronization, and state persistence across the network.


🆔 Core Identity Concepts

A client in LedFx is identified by three distinct pieces of information:

  1. Device ID (device_id):

    • Source: Generated by the frontend on first load (crypto.randomUUID()).
    • Persistence: localStorage.
    • Scope: Persistent across browser restarts and tab closures for a specific browser/hardware.
    • Format: web-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    • Purpose: To allow the backend to recognize a returning "device" even if the session has changed.
  2. Client ID (client_id / clientId):

    • Source: Assigned by the Backend upon WebSocket connection.
    • Persistence: In-memory (lost on disconnect).
    • Scope: Unique per active network connection.
    • Purpose: Used for direct messaging and identifying a specific socket session.
  3. Client Metadata (name and type):

    • Source: User-defined or automatically generated.
    • Persistence: sessionStorage.
    • Scope: Unique per Browser Tab.
    • Purpose: Allows multiple tabs in the same browser to act as different entities (e.g., one tab as a "visualiser" and another as a "controller").

🛠️ Storage Strategy

Storage Mechanism Data Stored Why?
localStorage device_id Must survive browser restarts to uniquely identify the physical machine/browser.
sessionStorage name, type, client_id Allows tab-specific identities. Opening a new tab creates a fresh session with its own name and purpose.
Backend Registry Full ClientMetadata Serves as the single source of truth for the entire network.

🤝 WebSocket Handshake & Sync Flow

1. Connection Initiation

When the frontend connects, the backend immediately sends the client_id assigned to that socket.

  • Event: { "event_type": "client_id", "client_id": "..." }

2. Initial Registration (set_client_info)

Once connected, the frontend sends its known identity to the backend.

  • Action: set_client_info
  • Payload: { "device_id": "web-...", "name": "...", "type": "..." }
  • Backend Response: Backend updates its internal registry. If a name conflict occurs, the backend may modify the name.

3. Metadata Updates (update_client_info)

When a user changes their name or type in the UI:

  • Action: update_client_info
  • Payload: { "name": "...", "type": "..." } (Note: device_id is omitted during updates).

4. Server-Side Sync (client_info_updated)

If the backend changes a client's info (e.g., during conflict resolution), it notifies the client.

  • Event: { "event_type": "client_info_updated", "name": "...", "type": "...", "client_id": "..." }
  • Frontend Action: Updates local store and sessionStorage.

5. Network-Wide Updates (clients_updated)

Whenever any client connects, disconnects, or changes metadata, the backend broadcasts a global refresh.

  • Event: { "event_type": "clients_updated" }
  • Client Action: All connected clients perform a GET /api/clients to refresh their peer list.

📝 Use-Case Scenarios

Case A: First-Time Connection

  1. User opens http://localhost:3000.
  2. Frontend generates device_id: "web-abc".
  3. Frontend defaults metadata to name: "Client-abc", type: "unknown".
  4. WebSocket connects. Backend sends client_id: "conn-1".
  5. Frontend sends set_client_info with device_id: "web-abc".
  6. Backend registers "Client-abc" as a new client.

Case B: Reconnect (Network Flap)

  1. Client loses Wi-Fi. WebSocket closes.
  2. Frontend keeps its state in memory and sessionStorage.
  3. Wi-Fi restores. WebSocket reconnects.
  4. Backend sends new client_id: "conn-2".
  5. Frontend sends set_client_info with the same device_id: "web-abc" and saved metadata.
  6. Backend sees web-abc already exists, updates its mapping to the new client_id, and marks it as active.

Case C: New Tab (Multiple Visualizers)

  1. User already has Tab 1 open as "Main Visualizer".
  2. User opens Tab 2.
  3. Tab 2 reads device_id: "web-abc" from localStorage (Shared).
  4. Tab 2 sees empty sessionStorage. It generates a fresh name: "Client-abc-2", type: "unknown".
  5. Tab 2 connects via WebSocket. Backend sends client_id: "conn-3".
  6. Backend now shows two clients in the list, both sharing the same device_id but having unique client_ids and names.

📡 Broadcast Messaging

Broadcasts use a targeted system to send data to specific clients without flooding the network.

Targeting Modes:

  • all: Every connected client.
  • type: Clients of a specific type (e.g., all visualiser clients).
  • names: Specific clients identified by name.
  • uuids: Specific clients identified by their session client_id.

Example: Syncing Visualizer Configs

{
  "type": "broadcast",
  "data": {
    "broadcast_type": "visualiser_control",
    "target": { "mode": "type", "value": "visualiser" },
    "payload": { "config": { "brightness": 0.8 } }
  }
}