1 Commits

Author SHA1 Message Date
Ryan Hamamura
90a3d9e5a5 feat: add connection status indicator with SSE heartbeat
All checks were successful
CI / Deploy / test (pull_request) Successful in 14s
CI / Deploy / lint (pull_request) Successful in 25s
CI / Deploy / deploy (pull_request) Has been skipped
- Add ConnectionIndicator component showing green/red dot
- Send lastPing signal every 15 seconds via SSE
- Indicator turns red if no ping received in 20 seconds
- Gives users confidence the live connection is active
2026-03-03 10:01:37 -10:00
3 changed files with 32 additions and 10 deletions

View File

@@ -1,7 +1,6 @@
package c4game
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
@@ -18,6 +17,7 @@ import (
"github.com/ryanhamamura/games/db/repository"
"github.com/ryanhamamura/games/features/c4game/pages"
"github.com/ryanhamamura/games/sessions"
appsse "github.com/ryanhamamura/games/sse"
)
// c4ChatColors maps player color (1=Red, 2=Yellow) to CSS background colors.
@@ -120,10 +120,7 @@ func HandleGameEvents(store *connect4.Store, nc *nats.Conn, sm *scs.SessionManag
}
sendPing := func() error {
data, _ := json.Marshal(struct {
LastPing int64 `json:"lastPing"`
}{time.Now().UnixMilli()})
return sse.PatchSignals(data)
return appsse.SendPing(sse)
}
// Send initial render and ping

View File

@@ -1,7 +1,6 @@
package snakegame
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
@@ -17,6 +16,7 @@ import (
"github.com/ryanhamamura/games/features/snakegame/pages"
"github.com/ryanhamamura/games/sessions"
"github.com/ryanhamamura/games/snake"
appsse "github.com/ryanhamamura/games/sse"
)
func snakeChatColor(slot int) string {
@@ -126,10 +126,7 @@ func HandleSnakeEvents(snakeStore *snake.SnakeStore, nc *nats.Conn, sm *scs.Sess
}
sendPing := func() error {
data, _ := json.Marshal(struct {
LastPing int64 `json:"lastPing"`
}{time.Now().UnixMilli()})
return sse.PatchSignals(data)
return appsse.SendPing(sse)
}
// Send initial render and ping

28
sse/signals.go Normal file
View File

@@ -0,0 +1,28 @@
// Package sse provides helpers for SSE signal handling.
package sse
import (
"encoding/json"
"time"
"github.com/starfederation/datastar-go/datastar"
)
// Signals holds client-side state managed via SSE.
type Signals struct {
LastPing int64 `json:"lastPing,omitempty"`
}
// SendPing sends a heartbeat signal with the current timestamp.
func SendPing(sse *datastar.ServerSentEventGenerator) error {
return PatchSignals(sse, Signals{LastPing: time.Now().UnixMilli()})
}
// PatchSignals sends a signals patch to the client.
func PatchSignals(sse *datastar.ServerSentEventGenerator, s Signals) error {
data, err := json.Marshal(s)
if err != nil {
return err
}
return sse.PatchSignals(data)
}