]> git.cworth.org Git - lmno.games/commitdiff
Add a new ReconnectingEventSource, and use it in all cames
authorCarl Worth <cworth@cworth.org>
Mon, 9 Mar 2026 01:43:34 +0000 (21:43 -0400)
committerCarl Worth <cworth@cworth.org>
Mon, 9 Mar 2026 01:43:34 +0000 (21:43 -0400)
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.

anagrams/anagrams.jsx
empathy/empathy.jsx
empires/game.js
letterrip/letterrip.jsx
lmno.js
scribe/scribe.jsx
tictactoe/tictactoe.jsx

index 4afbfd4a69844798c9d94edf387070fd2a5af8c0..963087237b87915d6aea45a1f23cb24d1e7665df 100644 (file)
@@ -1497,13 +1497,9 @@ ReactDOM.render(<Game ref={(me) => 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));
index 90bf0fd360829299facce85a1e3593c3a82db63a..b383dba26a14992807aae086f149e396097cde8f 100644 (file)
@@ -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);
index 05b801e352d0e6c0c51e84cfc157e43162024f50..a524307e1863adf7366f6bea4434ac71f6663e80 100644 (file)
@@ -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");
index 7ce0265e6ece4ec5115679e27230b2026b1841f6..06771557e83ce05ebca3a7198f9c12ccf1beb95a 100644 (file)
@@ -1059,13 +1059,9 @@ ReactDOM.render(<Game ref={(me) => 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 4af9878d2ef14e20b517a8902367b23f3c8dbbfd..c65fa5b239d25f237af24aff0d13c3bdf8b95b55 100644 (file)
--- 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.");
index 75742ca8ec160513fbde5f962298d747d12586df..6b49d4a82b0ba1aa19e9dc6e5eb62c531c7e11dc 100644 (file)
@@ -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);
index dbfc5ef93e9ba41d21f026a14653e179828fbd4a..87e20a1be0e3fbf9e99d918e9d3c77f4c4df8132 100644 (file)
@@ -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);