First off, thanks for the project — we're using f1-dash extensively as the reference for porting the realtime/decoders side of a personal F1 live-timing project to TypeScript on Cloudflare Workers, and the layering has been very pleasant to follow.
While porting signalr/src/lib.rs, I think I've stumbled on a latent bug that affects only dev mode (i.e. running realtime against the local simulator):
Repro
cargo run --bin simulator -- save <recording.jsonl> during a live session.
ADDRESS=0.0.0.0:8000 cargo run --bin simulator -- replay <recording.jsonl>.
F1_DEV_URL=ws://localhost:8000/ws cargo run --bin realtime.
Expected: realtime connects, gets the initial state, starts streaming.
Observed (per my reading of the code; haven't run it end-to-end myself): signalr::subscribe calls receive_valid_response, which loops until a text message parses as Response { i: String, r: Option<Value> } (PascalCase → JSON `I`/`R`).
But simulator/src/save.rs writes:
- Line 1: the unwrapped `r` content (the result of
signalr::subscribe's previous run): `{"Heartbeat":{...},"DriverList":{...},...}` — no `I` field.
- Lines 2..N: raw text from `listen_raw`, all in Update shape `{"M":[{"A":[topic, data, ts]}]}` — no `I` field either.
And simulator/src/replay/server.rs just dumps every line as-is, with a 100 ms gap, ignoring any incoming Subscribe message.
So receive_valid_response should never satisfy its parse check and the await should hang indefinitely. The check at signalr/src/lib.rs:161 (if response.i != id && env::var_os(\"F1_DEV_URL\").is_none()) skips the id mismatch error in dev mode, but doesn't avoid the loop itself.
I'm aware f1-dash.com runs in production mode, so this wouldn't surface there. Wanted to flag it in case it bites someone else trying to use the simulator workflow.
A possible fix
In dev mode, skip receive_valid_response entirely (treat the first incoming message as the initial state, since that's the format the simulator sends — line 1 of the recording is the unwrapped initial). Something like:
let initial = if env::var_os(\"F1_DEV_URL\").is_some() {
// Simulator emits the unwrapped initial as the first text frame; consume it directly.
receive_first_text(&mut client.stream).await?
} else {
let response = receive_valid_response(&mut client.stream).await?;
if response.i != id { return Err(anyhow::anyhow!(\"Response ID does not match\")); }
response.r.ok_or_else(|| anyhow::anyhow!(\"No result in response\"))?
};
Happy to send a PR if you want; otherwise feel free to close — I'm mostly leaving this as a breadcrumb. Thanks again for the project.
First off, thanks for the project — we're using f1-dash extensively as the reference for porting the realtime/decoders side of a personal F1 live-timing project to TypeScript on Cloudflare Workers, and the layering has been very pleasant to follow.
While porting
signalr/src/lib.rs, I think I've stumbled on a latent bug that affects only dev mode (i.e. runningrealtimeagainst the localsimulator):Repro
cargo run --bin simulator -- save <recording.jsonl>during a live session.ADDRESS=0.0.0.0:8000 cargo run --bin simulator -- replay <recording.jsonl>.F1_DEV_URL=ws://localhost:8000/ws cargo run --bin realtime.Expected:
realtimeconnects, gets the initial state, starts streaming.Observed (per my reading of the code; haven't run it end-to-end myself):
signalr::subscribecallsreceive_valid_response, which loops until a text message parses asResponse { i: String, r: Option<Value> }(PascalCase → JSON `I`/`R`).But
simulator/src/save.rswrites:signalr::subscribe's previous run): `{"Heartbeat":{...},"DriverList":{...},...}` — no `I` field.And
simulator/src/replay/server.rsjust dumps every line as-is, with a 100 ms gap, ignoring any incoming Subscribe message.So
receive_valid_responseshould never satisfy its parse check and the await should hang indefinitely. The check atsignalr/src/lib.rs:161(if response.i != id && env::var_os(\"F1_DEV_URL\").is_none()) skips the id mismatch error in dev mode, but doesn't avoid the loop itself.I'm aware f1-dash.com runs in production mode, so this wouldn't surface there. Wanted to flag it in case it bites someone else trying to use the simulator workflow.
A possible fix
In dev mode, skip
receive_valid_responseentirely (treat the first incoming message as the initial state, since that's the format the simulator sends — line 1 of the recording is the unwrapped initial). Something like:Happy to send a PR if you want; otherwise feel free to close — I'm mostly leaving this as a breadcrumb. Thanks again for the project.