1 Commits

Author SHA1 Message Date
Ryan Hamamura
45e6e08a5c 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:02:52 -10:00
3 changed files with 10 additions and 32 deletions

View File

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

View File

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

View File

@@ -1,28 +0,0 @@
// 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)
}