Fix SSE architecture for reliable connections #13

Merged
ryan merged 9 commits from fix/sse-architecture into main 2026-03-03 23:33:14 +00:00
3 changed files with 8 additions and 54 deletions
Showing only changes of commit 155ac2c71a - Show all commits

View File

@@ -25,6 +25,7 @@ templ GamePage(g *connect4.Game, myColor int, messages []chat.Message, chatCfg c
templ GameContent(g *connect4.Game, myColor int, messages []chat.Message, chatCfg chatcomponents.Config) {
<div id="game-content">
@sharedcomponents.LiveClock()
@sharedcomponents.BackToLobby()
@sharedcomponents.StealthTitle("text-3xl font-bold")
@components.PlayerInfo(g, myColor)

View File

@@ -1,7 +1,7 @@
package components
import (
"fmt"
"time"
"github.com/starfederation/datastar-go/datastar"
)
@@ -48,60 +48,12 @@ templ NicknamePrompt(returnPath string) {
</main>
}
func isStale(lastPing int64) bool {
return lastPing == 0
}
var connectionWatcherHandle = templ.NewOnceHandle()
// ConnectionIndicator shows a small dot indicating SSE connection status.
// Server patches this with a timestamp; client JS detects staleness.
templ ConnectionIndicator(lastPing int64) {
<div
id="connection-indicator"
class="fixed top-2 right-2"
data-last-ping={ fmt.Sprintf("%d", lastPing) }
>
<div class="inline-grid *:[grid-area:1/1]">
<div
id="connection-ping"
class={
"status status-sm",
templ.KV("status-success animate-ping", !isStale(lastPing)),
templ.KV("status-error", isStale(lastPing)),
}
></div>
<div
id="connection-dot"
class={
"status status-sm",
templ.KV("status-success", !isStale(lastPing)),
templ.KV("status-error", isStale(lastPing)),
}
></div>
// LiveClock shows the current server time, updated with each SSE patch.
// If the clock stops updating, users know the connection is stale.
templ LiveClock() {
<div class="fixed top-2 right-2 text-xs opacity-50 font-mono">
{ time.Now().Format("15:04:05") }
</div>
</div>
@connectionWatcherHandle.Once() {
@connectionWatcher()
}
}
script connectionWatcher() {
setInterval(function() {
var el = document.getElementById('connection-indicator');
var dot = document.getElementById('connection-dot');
var ping = document.getElementById('connection-ping');
if (!el || !dot || !ping) return;
var lastPing = parseInt(el.dataset.lastPing, 10) || 0;
var stale = Date.now() - lastPing > 20000;
dot.classList.toggle('status-success', !stale);
dot.classList.toggle('status-error', stale);
ping.classList.toggle('status-success', !stale);
ping.classList.toggle('status-error', stale);
ping.classList.toggle('animate-ping', !stale);
}, 1000);
}
templ GameJoinPrompt(loginURL string, registerURL string, gamePath string) {

View File

@@ -44,6 +44,7 @@ templ GamePage(sg *snake.SnakeGame, mySlot int, messages []chat.Message, chatCfg
templ GameContent(sg *snake.SnakeGame, mySlot int, messages []chat.Message, chatCfg chatcomponents.Config, gameID string) {
<div id="game-content">
@components.LiveClock()
@components.BackToLobby()
<h1 class="text-3xl font-bold">~~~~</h1>
@snakecomponents.PlayerList(sg, mySlot)