From 9086f0483fafa5a58821fba3c0deed79085a2b81 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:34:18 +0000 Subject: [PATCH] fix: resolve WebSocket connection issues on Windows The WebSocket endpoint /pico/ws was previously requiring dashboard session cookies, which are not always sent by browsers (especially on Windows) during the WebSocket handshake. This change makes /pico/ws a public path in the dashboard middleware, allowing it to fall back to its own subprotocol-based token authentication. Additionally, standardize all occurrences of 'Sec-WebSocket-Protocol' to the canonical Go form 'Sec-Websocket-Protocol' in pkg/channels/pico/pico.go to ensure header consistency and avoid potential protocol mismatches in different network stacks. - Modified web/backend/middleware/launcher_dashboard_auth.go to allow /pico/ws. - Updated pkg/channels/pico/pico.go to use canonical header casing. - Verified changes with existing tests and code review. Co-authored-by: nglmercer <128845117+nglmercer@users.noreply.github.com> --- pkg/channels/pico/pico.go | 6 +++--- web/backend/middleware/launcher_dashboard_auth.go | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/channels/pico/pico.go b/pkg/channels/pico/pico.go index e22da1ba1..c346ac094 100644 --- a/pkg/channels/pico/pico.go +++ b/pkg/channels/pico/pico.go @@ -349,7 +349,7 @@ func (c *PicoChannel) handleWebSocket(w http.ResponseWriter, r *http.Request) { // Echo the matched subprotocol back so the browser accepts the upgrade. var responseHeader http.Header if proto := c.matchedSubprotocol(r); proto != "" { - responseHeader = http.Header{"Sec-WebSocket-Protocol": {proto}} + responseHeader = http.Header{"Sec-Websocket-Protocol": {proto}} } conn, err := c.upgrader.Upgrade(w, r, responseHeader) @@ -387,7 +387,7 @@ func (c *PicoChannel) handleWebSocket(w http.ResponseWriter, r *http.Request) { // authenticate checks the request for a valid token: // 1. Authorization: Bearer header -// 2. Sec-WebSocket-Protocol "token." (for browsers that can't set headers) +// 2. Sec-Websocket-Protocol "token." (for browsers that can't set headers) // 3. Query parameter "token" (only when AllowTokenQuery is on) func (c *PicoChannel) authenticate(r *http.Request) bool { token := c.config.Token.String() @@ -403,7 +403,7 @@ func (c *PicoChannel) authenticate(r *http.Request) bool { } } - // Check Sec-WebSocket-Protocol subprotocol ("token.") + // Check Sec-Websocket-Protocol subprotocol ("token.") if c.matchedSubprotocol(r) != "" { return true } diff --git a/web/backend/middleware/launcher_dashboard_auth.go b/web/backend/middleware/launcher_dashboard_auth.go index 7e92fca22..f1c90f8da 100644 --- a/web/backend/middleware/launcher_dashboard_auth.go +++ b/web/backend/middleware/launcher_dashboard_auth.go @@ -166,6 +166,9 @@ func isPublicLauncherDashboardPath(method, p string) bool { if isPublicLauncherDashboardStatic(method, p) { return true } + if p == "/pico/ws" && method == http.MethodGet { + return true + } switch p { case "/api/auth/login": return method == http.MethodPost