const ws = new WebSocket(
`wss://mm.akaramarkets.com/api/subscribe/T1?token=${process.env.CONSUMER_TOKEN}`
);
ws.onmessage = (msg) => {
const frame = JSON.parse(msg.data);
// Control messages are not frames: the hello (schema identity — sport +
// contract version) opens every subscription, an error message precedes
// a deliberate close (4404/4403 — stop; 1011 — server-side outage, retry),
// a ping arrives ~every 15s as a keepalive (ignore it), and session_closed
// announces the normal end of the game (a 4000 close follows).
if (frame.type === "hello" || frame.type === "error" || frame.type === "ping"
|| frame.type === "session_closed") return;
// Replace your snapshot wholesale — every frame is authoritative.
updateSnapshot(frame.state);
// The connect frame has event === null. Every subsequent frame carries the triggering press.
if (frame.event !== null) {
appendToTape(frame.event);
}
};
ws.onclose = (e) => {
if (e.code === 4000) {
// Normal end: the game is over (a {"type": "session_closed"} message arrived
// just before this). Every session ends this way. Do NOT reconnect; the full
// tape stays readable via GET /api/history/T1.
return;
}
if (e.code === 4404 || e.code === 4403) {
// Deliberate refusal — game over, never published, or not entitled. Do NOT
// reconnect (the answer won't change); fetch GET /api/history/T1 for the tape.
return;
}
// Anything else is transient (network drop, server restart): reconnect and
// re-backfill from /history. An immediate failure with no frames at all usually
// means the token was rejected (the handshake itself is refused with HTTP 403,
// observed in a browser as a failed connection) — fix it, don't loop.
};