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(/