From 3ac83ddfcaca9d5806e05078df6a6693012ec3e3 Mon Sep 17 00:00:00 2001 From: Coy Geek <65363919+coygeek@users.noreply.github.com> Date: Sun, 12 Jul 2026 02:13:04 -0700 Subject: [PATCH] fix: Canvas WebSocket creation in browser context silently swallows... (#82505) * fix(canvas): report live reload websocket errors Surface WebSocket initialization failures from the injected canvas live reload client through console diagnostics, DOM attributes, and a browser event instead of silently swallowing them. * refactor(canvas): keep reload diagnostics console-only * fix(canvas): distinguish page teardown from socket failure * style(canvas): brace pagehide listener --------- Co-authored-by: Peter Steinberger --- extensions/canvas/src/host/a2ui-shared.ts | 22 +++++- extensions/canvas/src/host/server.test.ts | 96 +++++++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/extensions/canvas/src/host/a2ui-shared.ts b/extensions/canvas/src/host/a2ui-shared.ts index 4e92128c209b..dfc38421d5eb 100644 --- a/extensions/canvas/src/host/a2ui-shared.ts +++ b/extensions/canvas/src/host/a2ui-shared.ts @@ -27,6 +27,16 @@ export function injectCanvasLiveReload(html: string): string { // - iOS: window.webkit.messageHandlers.openclawCanvasA2UIAction.postMessage(...) // - Android: window.openclawCanvasA2UIAction.postMessage(...) const handlerNames = ["openclawCanvasA2UIAction"]; + let liveReloadErrorReported = false; + let pageUnloading = false; + globalThis.addEventListener?.("pagehide", () => { pageUnloading = true; }, { once: true }); + function reportCanvasLiveReloadError(err) { + if (liveReloadErrorReported) return; + liveReloadErrorReported = true; + try { + console.error("OpenClaw canvas live reload unavailable:", err); + } catch {} + } function postToNode(payload) { try { const raw = typeof payload === "string" ? payload : JSON.stringify(payload); @@ -67,7 +77,17 @@ export function injectCanvasLiveReload(html: string): string { ws.onmessage = (ev) => { if (String(ev.data || "") === "reload") location.reload(); }; - } catch {} + ws.onerror = (ev) => { + reportCanvasLiveReloadError(ev); + }; + ws.onclose = (ev) => { + if (ev.code !== 1000 && !(ev.code === 1001 && pageUnloading)) { + reportCanvasLiveReloadError(ev); + } + }; + } catch (err) { + reportCanvasLiveReloadError(err); + } })(); `.trim(); diff --git a/extensions/canvas/src/host/server.test.ts b/extensions/canvas/src/host/server.test.ts index 3c9738e95bb2..6cbdaca9c5e8 100644 --- a/extensions/canvas/src/host/server.test.ts +++ b/extensions/canvas/src/host/server.test.ts @@ -4,6 +4,7 @@ import type { IncomingMessage } from "node:http"; import os from "node:os"; import path from "node:path"; import type { Duplex } from "node:stream"; +import vm from "node:vm"; import { defaultRuntime } from "openclaw/plugin-sdk/runtime-env"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { @@ -117,6 +118,51 @@ async function captureA2uiFixtureResponse( return await captureHttpResponse(createA2uiHttpRequestHandler({ rootDir }), url, method); } +function extractInjectedScript(html: string): string { + const match = html.match(/