Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions login/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions login/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
49 changes: 32 additions & 17 deletions login/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -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()
Expand All @@ -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.
Expand All @@ -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)
}
}

Expand Down
Loading