From: Carl Worth Date: Mon, 9 Mar 2026 01:43:34 +0000 (-0400) Subject: Add a new ReconnectingEventSource, and use it in all cames X-Git-Url: https://git.cworth.org/git?a=commitdiff_plain;h=b70775c9df14de0a30338984569a7a01f2611409;p=lmno.games Add a new ReconnectingEventSource, and use it in all cames This wrapper for EventSource will attempt to reconnect after a lost connection (up to 5 times with a 2-second delay between each attempt). With this in place, a quick server restart (< 10 seconds) should be unnoticed by clients. --- diff --git a/anagrams/anagrams.jsx b/anagrams/anagrams.jsx index 4afbfd4..9630872 100644 --- a/anagrams/anagrams.jsx +++ b/anagrams/anagrams.jsx @@ -1497,13 +1497,9 @@ ReactDOM.render( window.game = me} />, * Server-sent event stream * *********************************************************/ -const events = new EventSource("events"); - -events.onerror = function(event) { - if (event.target.readyState === EventSource.CLOSED) { - add_message("danger", "Connection to server lost."); - } -}; +const events = new ReconnectingEventSource("events", { + onclose: () => add_message("danger", "Connection to server lost.") +}); events.addEventListener("game-info", event => { window.game.set_game_info(JSON.parse(event.data)); diff --git a/empathy/empathy.jsx b/empathy/empathy.jsx index 90bf0fd..b383dba 100644 --- a/empathy/empathy.jsx +++ b/empathy/empathy.jsx @@ -17,15 +17,9 @@ ${message} * Handling server-sent event stream * *********************************************************/ -const events = new EventSource("events"); - -events.onerror = function(event) { - if (event.target.readyState === EventSource.CLOSED) { - setTimeout(() => { - add_message("danger", "Connection to server lost."); - }, 1000); - } -}; +const events = new ReconnectingEventSource("events", { + onclose: () => add_message("danger", "Connection to server lost.") +}); events.addEventListener("game-info", event => { const info = JSON.parse(event.data); diff --git a/empires/game.js b/empires/game.js index 05b801e..a524307 100644 --- a/empires/game.js +++ b/empires/game.js @@ -102,13 +102,9 @@ function post_reset() { request.send(); } -const events = new EventSource(GAME_API("events")); - -events.onerror = function(event) { - if (event.target.readyState === EventSource.CLOSED) { - add_message("danger", "Connection to server lost."); - } -}; +const events = new ReconnectingEventSource(GAME_API("events"), { + onclose: () => add_message("danger", "Connection to server lost.") +}); events.addEventListener("spectators", function(event) { const spectators_div = document.getElementById("spectators-div"); diff --git a/letterrip/letterrip.jsx b/letterrip/letterrip.jsx index 7ce0265..0677155 100644 --- a/letterrip/letterrip.jsx +++ b/letterrip/letterrip.jsx @@ -1059,13 +1059,9 @@ ReactDOM.render( window.game = me} />, * Server-sent event stream * *********************************************************/ -const events = new EventSource("events"); - -events.onerror = function(event) { - if (event.target.readyState === EventSource.CLOSED) { - add_message("danger", "Connection to server lost."); - } -}; +const events = new ReconnectingEventSource("events", { + onclose: () => add_message("danger", "Connection to server lost.") +}); events.addEventListener("game-info", event => { window.game.set_game_info(JSON.parse(event.data)); diff --git a/lmno.js b/lmno.js index 4af9878..c65fa5b 100644 --- a/lmno.js +++ b/lmno.js @@ -11,6 +11,57 @@ ${message} message_area.insertAdjacentHTML('beforeend', message); } +/* An EventSource wrapper that automatically reconnects when the + * connection is closed (e.g. server restart). Retries a few times + * with a delay before giving up and calling the onclose callback. + */ +class ReconnectingEventSource { + constructor(url, { max_retries = 5, retry_delay_ms = 2000, onclose } = {}) { + this._url = url; + this._max_retries = max_retries; + this._retry_delay_ms = retry_delay_ms; + this._onclose = onclose; + this._listeners = []; + this._retries = 0; + this._closed = false; + this._connect(); + } + + _connect() { + this._es = new EventSource(this._url); + + this._es.onopen = () => { + this._retries = 0; + }; + + this._es.onerror = () => { + if (this._es.readyState === EventSource.CLOSED) { + this._es.close(); + if (!this._closed && this._retries < this._max_retries) { + this._retries++; + setTimeout(() => this._connect(), this._retry_delay_ms); + } else if (this._onclose) { + this._onclose(); + } + } + }; + + for (const { type, handler } of this._listeners) { + this._es.addEventListener(type, handler); + } + } + + addEventListener(type, handler) { + this._listeners.push({ type, handler }); + this._es.addEventListener(type, handler); + } + + close() { + this._closed = true; + this._es.close(); + } +} + function lmno_join_loadend(request, game_id) { if (request.status === 404) { add_message("danger", game_id + " is not a valid game ID. Try again."); diff --git a/scribe/scribe.jsx b/scribe/scribe.jsx index 75742ca..6b49d4a 100644 --- a/scribe/scribe.jsx +++ b/scribe/scribe.jsx @@ -22,15 +22,9 @@ ${message} * Handling server-sent event stream * *********************************************************/ -const events = new EventSource("events"); - -events.onerror = function(event) { - if (event.target.readyState === EventSource.CLOSED) { - setTimeout(() => { - add_message("danger", "Connection to server lost."); - }, 1000); - } -}; +const events = new ReconnectingEventSource("events", { + onclose: () => add_message("danger", "Connection to server lost.") +}); events.addEventListener("game-info", event => { const info = JSON.parse(event.data); diff --git a/tictactoe/tictactoe.jsx b/tictactoe/tictactoe.jsx index dbfc5ef..87e20a1 100644 --- a/tictactoe/tictactoe.jsx +++ b/tictactoe/tictactoe.jsx @@ -24,13 +24,9 @@ ${message} * Handling server-sent event stream * *********************************************************/ -const events = new EventSource("events"); - -events.onerror = function(event) { - if (event.target.readyState === EventSource.CLOSED) { - add_message("danger", "Connection to server lost."); - } -}; +const events = new ReconnectingEventSource("events", { + onclose: () => add_message("danger", "Connection to server lost.") +}); events.addEventListener("game-info", event => { const info = JSON.parse(event.data);