Reconnection
WebSocket connections drop. Network blips, mobile backgrounding, server restarts — production code has to handle reconnection gracefully.
Why reconnection matters
On the public internet, your connection has a ~99% chance of being killed within a few hours. A long-running consumer needs both reconnection and state-resume logic.
Exponential backoff
Don't hammer the server. Start at 1s, double each attempt, cap at 60s, add jitter.
function nextDelay(attempt) {
const exp = Math.min(2 ** attempt, 60);
const jitter = Math.random() * 0.3 * exp;
return (exp + jitter) * 1000;
}Resuming state
On reconnect:
- Re-authenticate.
- Re-subscribe to every channel you had before.
- For data where you might have missed updates, do a one-shot REST call to catch up.
The server does not remember client state — it's your responsibility to track what you're subscribed to and re-issue subscriptions on reconnect.
Production-ready code
class SportapiStream {
constructor(apiKey) {
this.apiKey = apiKey;
this.subs = new Set();
this.attempt = 0;
this.handlers = {};
this.connect();
}
connect() {
this.ws = new WebSocket('wss://stream.sportapi.io/v1');
this.ws.onopen = () => {
this.attempt = 0;
this.ws.send(JSON.stringify({ type: 'auth', token: this.apiKey }));
for (const ch of this.subs) {
this.ws.send(JSON.stringify({ type: 'subscribe', channel: ch }));
}
};
this.ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'update') {
(this.handlers[msg.channel] || []).forEach(h => h(msg.data));
}
};
this.ws.onclose = () => this.scheduleReconnect();
this.ws.onerror = () => this.ws.close();
}
scheduleReconnect() {
const delay = Math.min(2 ** this.attempt, 60) * 1000;
this.attempt++;
setTimeout(() => this.connect(), delay + Math.random() * 1000);
}
subscribe(channel, handler) {
this.subs.add(channel);
this.handlers[channel] = (this.handlers[channel] || []).concat(handler);
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'subscribe', channel }));
}
}
}