From 79dd4ab0adbf2d9ff492be0bfcf25cee77068611 Mon Sep 17 00:00:00 2001 From: Adnaan Date: Sun, 30 Nov 2025 07:46:39 +0100 Subject: [PATCH] feat: migrate to new Session API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the login example to use the new Session API instead of the deprecated Broadcaster API. Changes: - Replace BroadcastAware with SessionAware interface - Replace Broadcaster with Session for server-initiated updates - Use TriggerAction() instead of Send() for server push - Add serverWelcome action handler for welcome message - Fix StopDockerChrome test signature This example demonstrates: - HTTP form-based login with cookies - Server-initiated updates via Session.TriggerAction() - Multi-tab synchronization for the same user Requires: livetemplate/livetemplate#61 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- login/go.mod | 4 ++-- login/login_test.go | 4 ++-- login/main.go | 49 +++++++++++++++++++++++++++++---------------- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/login/go.mod b/login/go.mod index af81cd8..a289da0 100644 --- a/login/go.mod +++ b/login/go.mod @@ -52,8 +52,8 @@ require ( golang.org/x/time v0.14.0 // indirect ) -// Use local worktree version with auth features -replace github.com/livetemplate/livetemplate => ../../livetemplate/.worktrees/auth-v0.5 +// Use local livetemplate with Session API +replace github.com/livetemplate/livetemplate => ../../livetemplate // Use local lvt for testing utilities replace github.com/livetemplate/lvt => ../../lvt diff --git a/login/login_test.go b/login/login_test.go index e9f8749..67940ca 100644 --- a/login/login_test.go +++ b/login/login_test.go @@ -49,8 +49,8 @@ func TestLoginE2E(t *testing.T) { }() // Start Docker Chrome container - chromeCmd := e2etest.StartDockerChrome(t, debugPort) - defer e2etest.StopDockerChrome(t, chromeCmd, debugPort) + _ = e2etest.StartDockerChrome(t, debugPort) + defer e2etest.StopDockerChrome(t, debugPort) // Connect to Docker Chrome via remote debugging chromeURL := fmt.Sprintf("http://localhost:%d", debugPort) diff --git a/login/main.go b/login/main.go index c840db8..30caa5c 100644 --- a/login/main.go +++ b/login/main.go @@ -14,7 +14,7 @@ import ( ) // AuthState represents the authentication state for a session. -// It implements BroadcastAware to receive WebSocket connection events. +// It implements SessionAware to receive WebSocket connection events. type AuthState struct { Username string IsLoggedIn bool @@ -23,8 +23,8 @@ type AuthState struct { LoginTime time.Time // When user logged in // For server-initiated updates - broadcaster livetemplate.Broadcaster - mu sync.Mutex + session livetemplate.Session + mu sync.Mutex } // Change handles authentication actions @@ -34,6 +34,8 @@ func (s *AuthState) Change(ctx *livetemplate.ActionContext) error { return s.handleLogin(ctx) case "logout": return s.handleLogout(ctx) + case "serverWelcome": + return s.handleServerWelcome(ctx) default: return fmt.Errorf("unknown action: %s", ctx.Action) } @@ -98,11 +100,21 @@ func (s *AuthState) handleLogout(ctx *livetemplate.ActionContext) error { return ctx.Redirect("/", http.StatusSeeOther) } +// handleServerWelcome handles server-initiated welcome messages. +// This is triggered by TriggerAction from sendWelcomeMessage. +func (s *AuthState) handleServerWelcome(ctx *livetemplate.ActionContext) error { + message := ctx.GetString("message") + s.mu.Lock() + s.ServerMessage = message + s.mu.Unlock() + return nil +} + // OnConnect is called when a WebSocket connection is established. -// This implements livetemplate.BroadcastAware interface. -func (s *AuthState) OnConnect(ctx context.Context, b livetemplate.Broadcaster) error { +// This implements livetemplate.SessionAware interface. +func (s *AuthState) OnConnect(ctx context.Context, session livetemplate.Session) error { s.mu.Lock() - s.broadcaster = b + s.session = session isLoggedIn := s.IsLoggedIn username := s.Username s.mu.Unlock() @@ -119,13 +131,13 @@ func (s *AuthState) OnConnect(ctx context.Context, b livetemplate.Broadcaster) e } // OnDisconnect is called when a WebSocket connection is closed. -// This implements livetemplate.BroadcastAware interface. +// This implements livetemplate.SessionAware interface. func (s *AuthState) OnDisconnect() { s.mu.Lock() defer s.mu.Unlock() log.Printf("WebSocket disconnected (user: %s)", s.Username) - s.broadcaster = nil + s.session = nil } // sendWelcomeMessage sends a server-initiated welcome message via WebSocket. @@ -135,21 +147,24 @@ func (s *AuthState) sendWelcomeMessage() { time.Sleep(500 * time.Millisecond) s.mu.Lock() - defer s.mu.Unlock() + session := s.session + isLoggedIn := s.IsLoggedIn + username := s.Username + s.mu.Unlock() - if s.broadcaster == nil || !s.IsLoggedIn { + if session == nil || !isLoggedIn { return } - // Update state with welcome message from server - s.ServerMessage = fmt.Sprintf("Welcome %s! This message was pushed from the server at %s", - s.Username, time.Now().Format("15:04:05")) - - // Push update to client via WebSocket - if err := s.broadcaster.Send(); err != nil { + // Trigger server-initiated action that will call Change() with the welcome data + // This updates the state and sends the update to all user's connections + if err := session.TriggerAction("serverWelcome", map[string]interface{}{ + "message": fmt.Sprintf("Welcome %s! This message was pushed from the server at %s", + username, time.Now().Format("15:04:05")), + }); err != nil { log.Printf("Failed to send welcome message: %v", err) } else { - log.Printf("Server-initiated welcome message sent to %s", s.Username) + log.Printf("Server-initiated welcome message sent to %s", username) } }