From 196c56fab78d4bf4db7efecbafc3a23cd005ab92 Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Thu, 2 Apr 2026 17:50:27 +0200 Subject: [PATCH] Safari: revert websocket redirect workaround (#28753) --- assets/js/views/App.vue | 40 +--------------------------------------- tests/ws.spec.ts | 22 ---------------------- 2 files changed, 1 insertion(+), 61 deletions(-) diff --git a/assets/js/views/App.vue b/assets/js/views/App.vue index fd17b33eb..fc259f67a 100644 --- a/assets/js/views/App.vue +++ b/assets/js/views/App.vue @@ -33,9 +33,6 @@ import HelpModal from "../components/HelpModal.vue"; import collector from "../mixins/collector"; import { defineComponent } from "vue"; -const WS_OPEN_TIMEOUT_MS = 5000; -const WS_RETRY_PARAM = "wsRetry"; - // assume offline if not data received for 5 minutes let lastDataReceived = new Date(); const maxDataAge = 60 * 1000 * 5; @@ -67,7 +64,6 @@ export default defineComponent({ data: () => { return { reconnectTimeout: null as number | null, - openTimeout: null as number | null, ws: null as WebSocket | null, authNotConfigured: false, }; @@ -150,34 +146,6 @@ export default defineComponent({ window.clearTimeout(this.reconnectTimeout); } }, - // Safari 26 bug: with hash fragment URLs the HTTP upgrade - // request is sometimes silently dropped when serving from cache. - // Recover by navigating without hash, once (wsRetry guards against loops). - startOpenTimeout() { - const url = new URL(window.location.href); - if (url.searchParams.has(WS_RETRY_PARAM)) return; - this.openTimeout = window.setTimeout(() => { - console.warn("websocket open timeout, forcing navigation"); - this.ws?.close(); - url.hash = ""; - url.searchParams.set(WS_RETRY_PARAM, "true"); - window.location.href = url.href; - }, WS_OPEN_TIMEOUT_MS); - }, - clearOpenTimeout(success = false) { - if (this.openTimeout) { - window.clearTimeout(this.openTimeout); - this.openTimeout = null; - } - if (success) { - const url = new URL(window.location.href); - if (url.searchParams.has(WS_RETRY_PARAM)) { - console.warn("websocket open timeout recovered, clearing retry param"); - url.searchParams.delete(WS_RETRY_PARAM); - window.history.replaceState(window.history.state, "", url.href); - } - } - }, pageShowHandler(event: PageTransitionEvent) { if (event.persisted) { this.clearReconnectTimeout(); @@ -201,7 +169,6 @@ export default defineComponent({ }, 2500); }, disconnect() { - this.clearOpenTimeout(); if (this.ws) { this.ws.onerror = null; this.ws.onopen = null; @@ -228,22 +195,17 @@ export default defineComponent({ const loc = new URL("ws", window.location.href); loc.protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + this.ws = new WebSocket(loc.href); - - this.startOpenTimeout(); - this.ws.onerror = () => { console.log({ message: "Websocket error. Trying to reconnect." }); - this.clearOpenTimeout(); this.ws?.close(); }; this.ws.onopen = () => { - this.clearOpenTimeout(true); console.log("websocket connected"); window.app.setOnline(); }; this.ws.onclose = () => { - this.clearOpenTimeout(); window.app.setOffline(); this.reconnect(); }; diff --git a/tests/ws.spec.ts b/tests/ws.spec.ts index df6961d11..c03ea76a9 100644 --- a/tests/ws.spec.ts +++ b/tests/ws.spec.ts @@ -43,25 +43,3 @@ test("show offline when websocket is closed", async ({ page }) => { await expect(page.getByText("Not connected to a server.")).toBeVisible(); await expect(page.getByRole("link", { name: "Let's start configuration" })).toBeHidden(); }); - -test("force navigation after websocket open timeout", async ({ page }) => { - // Replace WebSocket with a mock that never fires onopen, - // simulating Safari's bug where the upgrade request is silently dropped. - await page.addInitScript(() => { - (window as any).WebSocket = class { - readyState = 0; - onopen: (() => void) | null = null; - onclose: ((ev: CloseEvent) => void) | null = null; - onerror: (() => void) | null = null; - onmessage: (() => void) | null = null; - close() { - this.readyState = 3; - this.onclose?.(new CloseEvent("close")); - } - send() {} - }; - }); - await page.goto("/"); - // after the 5s open timeout the app navigates to strip the hash fragment - await page.waitForURL(/wsRetry/, { timeout: 10000 }); -});