Files
openclaw/extensions/linux-canvas/src/socket-path.ts
Peter Steinberger b363d5a293 feat(linux): canvas UI via CLI-node + Tauri app IPC bridge (#107633)
* feat(linux): canvas via CLI-node + Tauri app IPC bridge

* refactor: extract gateway helper modules

* build(linux-canvas): register plugin package in lockfile

* fix(linux-canvas): move canvas advertise test out of core, regen docs/protocol/deadcode

* fix(gateway): break node-catalog/registry import cycle via leaf normalize module; add canvas glossary term

* style: oxfmt invoke.ts and runtime.ts after buildNodeEventParams extraction

* fix(linux): load Canvas WebView via dedicated data_directory context

Wry's Linux/WebKitGTK incognito mode discards Tauri's registered
WebContext (wry webkitgtk/mod.rs), so the Canvas window got a fresh
ephemeral context without the openclaw-canvas:// scheme handler — the
bundled A2UI page never committed (stayed about:blank) and every A2UI
command timed out. Use an isolated cache-backed data_directory instead,
which keeps the protocol handler while still isolating Canvas storage
from the dashboard window.

* fix(linux): keep Canvas WebView ephemeral via incognito + data_directory

Autoreview flagged that a dedicated data_directory alone persists Canvas
browser state (cookies, localStorage, IndexedDB, service workers) across
restarts, so an agent that navigates Canvas to a site could leak an
authenticated session into a later session. iOS uses a non-persistent
store; Linux should match.

Add .incognito(true) alongside .data_directory(): the distinct directory
gives Tauri a fresh WebContext key so it still attaches the
openclaw-canvas:// protocol closure, and incognito makes Wry swap in a
fresh *ephemeral* context carrying those protocols. Live-verified on a
Wayland/WebKitGTK box: the bundled page still loads
(location.href=openclaw-canvas://localhost/index.html, openclawA2UI
present, A2UI renders) and the canvas-webview dir holds no persistent
cookie/storage files.
2026-07-14 16:05:14 -07:00

44 lines
1.3 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
export function resolveLinuxCanvasSocketPath(
env: NodeJS.ProcessEnv = process.env,
uid: number | undefined = process.getuid?.(),
): string {
const runtimeDir = env.XDG_RUNTIME_DIR?.trim();
if (runtimeDir) {
return path.join(runtimeDir, "openclaw-canvas.sock");
}
return path.join("/tmp", `openclaw-canvas-${uid ?? "unknown"}.sock`);
}
export function linuxCanvasSocketExists(socketPath: string): boolean {
try {
const stat = fs.lstatSync(socketPath);
const uid = process.geteuid?.() ?? process.getuid?.();
if (!stat.isSocket() || (uid !== undefined && stat.uid !== uid) || (stat.mode & 0o077) !== 0) {
return false;
}
const procSockets = fs.readFileSync("/proc/net/unix", "utf8");
return procSockets.split("\n").some((line) => line.endsWith(` ${socketPath}`));
} catch {
return false;
}
}
export function watchLinuxCanvasSocket(socketPath: string, onChange: () => void): () => void {
const directory = path.dirname(socketPath);
const socketName = path.basename(socketPath);
try {
const watcher = fs.watch(directory, (_event, filename) => {
if (!filename || filename === socketName) {
onChange();
}
});
watcher.on("error", () => {});
return () => watcher.close();
} catch {
return () => {};
}
}