← Back to PRs

#8261: fix: enable A2UI Canvas on remote gateways

by rothnic open 2026-02-03 20:15 View on GitHub →
docs gateway
# fix: enable A2UI Canvas on remote gateways ## Summary Enables A2UI Canvas to work when the gateway runs on a remote server (cloud VM, VPS) and nodes connect via VPN (Tailscale) or over the internet. **Fixes:** https://github.com/openclaw/openclaw/issues/7143 **Related closed issues:** - https://github.com/openclaw/openclaw/issues/5540 - Feature request for advertised URL - https://github.com/openclaw/openclaw/issues/5033 - Similar request for remote canvas access ## The Problem When a macOS/iOS node connects to a remote gateway, A2UI Canvas fails because: 1. **Gateway advertises `127.0.0.1` URLs** - The gateway sends the canvas host URL based on its local address, which remote nodes cannot reach. 2. **macOS app reloads the page repeatedly** - When `a2ui_push` arrives, the content renders briefly, then `maybeAutoNavigateToA2UI()` triggers a reload even though we're already at the target URL, clearing the just-rendered content. ## The Solution ### 1. Gateway: Add `canvasHost.advertisedUrl` config option A new optional config allows operators to specify a public URL that remote nodes can reach: ```json { "canvasHost": { "advertisedUrl": "https://gateway.example.com" } } ``` When set, this URL is returned to nodes instead of the auto-detected local URL. **Files changed:** - `src/config/types.gateway.ts` - Add `advertisedUrl` to `CanvasHostConfig` type - `src/config/zod-schema.ts` - Add to schema validation - `src/infra/canvas-host-url.ts` - Use `advertisedUrl` when set - `src/gateway/server*.ts` - Thread the config through to WebSocket handlers - `docs/gateway/configuration.md` - Document the new option ### 2. macOS App: Prevent redundant page reloads Added a guard in `CanvasManager.swift` to skip navigation when already at the target URL: ```swift if self.lastAutoA2UIUrl == a2uiUrl, let current = controller.currentTarget?.trimmingCharacters(in: .whitespacesAndNewlines), current == a2uiUrl { Self.logger.debug("canvas auto-nav skipped; already at target URL") return } ``` **File changed:** - `apps/macos/Sources/OpenClaw/CanvasManager.swift` ## Testing ### Setup 1. Gateway running on cloud VM (e.g., Oracle Cloud, Hetzner) 2. macOS app connecting via Tailscale VPN 3. Reverse proxy with HTTPS (Traefik, nginx, Caddy, etc.) ### Gateway config ```json { "canvasHost": { "advertisedUrl": "https://gateway.example.com" }, "gateway": { "trustedProxies": ["10.0.0.0/8", "172.16.0.0/12", "100.64.0.0/10"] } } ``` Note: `trustedProxies` must include the IP ranges of connecting nodes (e.g., Tailscale CGNAT `100.64.0.0/10`) for WebSocket connections to work. ### Test ```bash # Push A2UI content to a connected node docker exec $(docker ps -q -f name=clawdbot-gateway) \ node /app/dist/index.js nodes canvas a2ui push \ --jsonl test.jsonl --node "MacBook" ``` **Before:** Content flashes briefly then disappears (or never appears) **After:** Content renders and persists ## Who This Helps - Users running OpenClaw gateway on cloud VMs (Oracle Cloud, Hetzner, AWS, etc.) - Users connecting to their gateway remotely via Tailscale/VPN - Anyone with gateway behind a reverse proxy (nginx, Caddy, Traefik) ## Why It's Worth Shipping A2UI Canvas is a powerful feature, but it's currently broken for the growing segment of users running remote gateways. This is a minimal, non-breaking change: - **Backward compatible** - `advertisedUrl` is optional; existing local-only setups unchanged - **Low risk** - Guard clause in Swift is a simple early-return pattern - **High impact** - Unblocks remote gateway users from using Canvas - **Documentation included** - New config option is documented

Most Similar PRs