Skip to content

WebSocket Protocol

Engram uses WebSocket for real-time communication between the backend and the frontend dashboard. All connected clients receive live updates about job progress, state transitions, drive events, and more.

Connection

Endpoint: ws://localhost:8000/ws

The frontend connects on page load and automatically reconnects with exponential backoff on disconnection. On reconnect, the frontend reloads the full job list via REST to resync state.

The ConnectionManager singleton manages all active connections. It maintains a list of connected WebSocket clients protected by an asyncio.Lock and handles graceful cleanup of disconnected clients during broadcast.


Message Format

All WebSocket messages are JSON objects. Server-to-client messages follow this general structure:

{
  "type": "<message_type>",
  "<field>": "<value>",
  ...
}

Flat structure

Messages use a flat key-value structure (not nested under a data key). The type field identifies the message kind, and remaining fields carry the payload.


Server to Client Messages

job_update

Sent on any job state change, progress update, or metadata change. Only includes optional fields when their values are not null, so the frontend can safely merge without overwriting existing values.

Field Type Always Present Description
type string Yes "job_update"
job_id int Yes Job identifier
state string Yes Current JobState value (e.g., "ripping", "completed")
progress_percent float No Overall progress (0-100)
current_speed string No Ripping speed (e.g., "2.3x")
eta_seconds int No Estimated time remaining in seconds
current_title int No Index of title currently being processed
total_titles int No Total number of titles on disc
error_message string No Error description (when state is "failed")
content_type string No "tv", "movie", or "unknown"
detected_title string No Detected media name (e.g., "The Office")
detected_season int No Detected season number
review_reason string No Human-readable reason for review

drive_event

Sent when a physical disc is inserted or removed.

Field Type Description
type string "drive_event"
drive_id string Drive path (e.g., "E:" or "/dev/sr0")
event string "inserted" or "removed"
volume_label string Disc volume label (may be empty)

titles_discovered

Sent after disc scanning completes, carrying all discovered titles.

Field Type Description
type string "titles_discovered"
job_id int Job identifier
titles array List of DiscTitle objects
content_type string "tv", "movie", or "unknown"
detected_title string? Detected media name
detected_season int? Detected season number

title_update

Sent on any title (track) state change, progress update, or match result. Only includes optional fields when their values are meaningful.

Field Type Always Present Description
type string Yes "title_update"
job_id int Yes Parent job identifier
title_id int Yes Title identifier
state string Yes Current TitleState value
matched_episode string No Episode code (e.g., "S01E03")
match_confidence float No Match confidence score (0.0-1.0)
match_stage string No Current matching stage description
match_progress float No Matching or ripping progress (0-100)
duration_seconds int No Title duration
file_size_bytes int No File size
expected_size_bytes int No Expected final size during ripping
actual_size_bytes int No Current size during ripping
matches_found int No Number of candidate matches found
matches_rejected int No Number of candidates rejected
match_details string No JSON string with score breakdown
organized_from string No Source filename
organized_to string No Destination path in library
output_filename string No Output filename
is_extra bool No Whether title was organized as extra content
error string No Error message on failure

subtitle_event

Sent during subtitle download progress.

Field Type Description
type string "subtitle_event"
job_id int Job identifier
status string "downloading", "completed", or "failed"
downloaded int Number of subtitles downloaded so far
total int Total number of subtitles to download
failed_count int Number of subtitle downloads that failed

Client to Server Messages

The WebSocket connection is server-push only. There are no client-to-server messages. All user actions (review decisions, job cancellation, configuration changes) go through the REST API.


Architecture Layers

WebSocket broadcasting flows through two layers:

EventBroadcaster (domain-specific methods)
        |
        v
ConnectionManager (transport-level broadcasting)
        |
        v
WebSocket clients (frontend)

The EventBroadcaster provides semantic methods like broadcast_title_matched() and broadcast_subtitle_download_progress(). These translate domain concepts into the correct ConnectionManager calls with properly named parameters.

The ConnectionManager handles the actual WebSocket transport: connection tracking, JSON serialization, broadcasting to all clients, and cleanup of disconnected clients.


Contract Validation

Parameter names must match exactly across all layers. Mismatches between EventBroadcaster method calls and ConnectionManager method signatures cause TypeError exceptions at runtime.

Known Validated Contracts

These contracts have been verified by integration tests:

EventBroadcaster Method ConnectionManager Method Key Parameters
broadcast_job_failed(job_id, error_message) broadcast_job_update(job_id, state, error=...) Uses error=, not error_message=
broadcast_subtitle_download_progress(...) broadcast_subtitle_event(job_id, status, downloaded, total, failed_count) No error_msg parameter
broadcast_title_failed(title, error) broadcast_title_update(job_id, title_id, state, error=...) Uses error= in transport layer

Parameter name mismatches

Integration tests have caught two production bugs caused by parameter name mismatches between the EventBroadcaster and ConnectionManager layers. Always verify that parameter names used in keyword arguments match the receiving method's signature exactly.

Testing

Integration tests validate WebSocket parameter contracts end-to-end. The test suite sends messages through the full EventBroadcaster -> ConnectionManager -> WebSocket pipeline and verifies the JSON output matches expected schemas.

All state values in WebSocket messages must use the string values from the JobState or TitleState enums (e.g., "ripping", "matched"), never the enum member names.