package main import ( "encoding/json" "log" "net/http" "sync" "time" "github.com/gorilla/websocket" ) const ( workDuration = 15 * 60 shortBreakDuration = 5 * 60 longBreakDuration = 10 * 60 sessions = 4 ) type BroadcastMessage struct { Mode string `json:"mode"` Session int `json:"session"` MaxSession int `json:"max_session"` TimeLeft int `json:"time_left"` } type ClientCommand struct { Command string `json:"command"` } var clients = make(map[*websocket.Conn]bool) var timerRunning bool var timerStopChannel = make(chan bool, 1) var mu sync.Mutex // to synchronize access to shared state // broadcastMessage sends the remaining time to all connected clients. func broadcastMessage(message BroadcastMessage) { jsonMessage, _ := json.Marshal(message) for client := range clients { err := client.WriteMessage(websocket.TextMessage, jsonMessage) if err != nil { log.Printf("Error broadcasting to client: %v", err) client.Close() delete(clients, client) } } } // startTimer runs the countdown and broadcasts every second. func startTimer(remainingSeconds int, mode string, session int) bool { for remainingSeconds > 0 { select { case <-timerStopChannel: return false // Stop the timer if a stop command is received default: broadcastMessage(BroadcastMessage{ Mode: mode, Session: session, MaxSession: sessions, TimeLeft: remainingSeconds, }) time.Sleep(time.Second) remainingSeconds-- } } broadcastMessage(BroadcastMessage{ Mode: mode, Session: session, MaxSession: sessions, TimeLeft: 0, }) return true } // runPomodoroTimer iterates the Pomodoro work/break sessions. func runPomodoroTimer() { mu.Lock() timerRunning = true for session := 1; session <= sessions; session++ { if !startTimer(workDuration, "Work", session) { break } if session == sessions { if !startTimer(longBreakDuration, "LongBreak", session) { break } } else { if !startTimer(shortBreakDuration, "ShortBreak", session) { break } } } timerRunning = false mu.Unlock() } // upgrade HTTP requests to WebSocket connections. var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } func handleConnections(w http.ResponseWriter, r *http.Request) { // Upgrade initial GET request to a WebSocket ws, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Printf("WebSocket upgrade error: %v", err) return } defer ws.Close() // Register the new client clients[ws] = true // Listen for commands from this client for { _, message, err := ws.ReadMessage() if err != nil { log.Printf("Client disconnected: %v", err) delete(clients, ws) break } // Handle incoming commands var command ClientCommand err = json.Unmarshal(message, &command) if err != nil { log.Printf("Error unmarshalling command: %v", err) continue } // Process the commands switch command.Command { case "start": if !timerRunning { go runPomodoroTimer() } case "stop": if timerRunning { timerStopChannel <- true } } } } func main() { http.HandleFunc("/ws", handleConnections) log.Println("Pomodoro WebSocket server started on :8080") err := http.ListenAndServe(":8080", nil) if err != nil { log.Fatalf("Error starting server: %v", err) } }