package ws // system_ws.go — MAJ-4 補丁:/ws/system WebSocket endpoint // // 對應 TDD v2/server-lifecycle.md §2.3(Minor 4)與 v2/web-ui-offline-overlay.md §3.2a。 // // 用途:讓瀏覽器 tab 訂閱 server 的 `server:shutdown-imminent` 廣播, // 以便在 Wails 關閉 / Restart 流程 SIGTERM 前立即顯示 Offline Overlay, // 避免只靠 health polling 導致的 15 秒延遲與 race。 // // 室(room)名稱:`system` // - Hub.BroadcastToRoom("system", payload) 由 system_handler.ShutdownNotify 呼叫 // - 目前只支援單一類型事件(server:shutdown-imminent),未來若有其他 system 事件可共用此 room // // 設計注意: // - 不向新 client 送任何歷史訊息(shutdown event 必須「當下」收到才有意義, // 補送一個過期的 shutdown 訊息會誤導前端)。 // - Read pump 只用於偵測 client 主動斷線,不處理任何 client → server 訊息。 // - 沿用 upgrader + CheckOrigin(loopback 白名單)。 import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) // SystemEventsHandler 處理 /ws/system 的 WebSocket 升級與訂閱。 func SystemEventsHandler(hub *Hub) gin.HandlerFunc { return func(c *gin.Context) { conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { return } defer conn.Close() client := &Client{Conn: conn, Send: make(chan []byte, 16)} sub := &Subscription{Client: client, Room: "system"} hub.RegisterSync(sub) defer hub.Unregister(sub) // Read pump — 唯一目的:偵測 client 斷線(close frame / socket error)→ 觸發 conn.Close // 使得 write pump 的 range client.Send 結束後能乾淨退出。 go func() { defer conn.Close() for { if _, _, err := conn.ReadMessage(); err != nil { return } } }() // Write pump — 把 Hub 塞進 client.Send 的訊息送出去。 for msg := range client.Send { if err := conn.WriteMessage(websocket.TextMessage, msg); err != nil { return } } } }