/** * desktop-bridge.js — 桌面壳(pywebview)原生桥探测与调用 * * 形态约定: * esp32_app.vbs → --window → WebView2:JS 可经 window.pywebview.api 调到 * GUI 进程 Python,由它直连 ecat_native(不经浏览器 fetch、不经页面服务子进程代理)。 * esp32_web.vbs / 浏览器标签:无 pywebview,本模块返回 null,调用方回落 HTTP。 * * 电机页在 tabs.html 的 iframe 内:pywebview 只注入顶层窗口,同源 iframe * 必须向上找 window.parent / window.top 上的 pywebview.api。 */ "use strict"; function collectApi() { const roots = []; try { roots.push(window); } catch (_) {} try { if (window.parent && window.parent !== window) roots.push(window.parent); } catch (_) {} try { if (window.top && window.top !== window) roots.push(window.top); } catch (_) {} for (const w of roots) { try { const api = w.pywebview && w.pywebview.api; if (api && typeof api.ecat_api === "function") return api; } catch (_) { /* 跨源 parent 会抛 */ } } return null; } let _api = null; /** @description 取桌面桥 API;没有则 null(不缓存失败,pywebview 可能稍后注入)。 */ export function getDesktopEcatApi() { if (_api && typeof _api.ecat_api === "function") return _api; _api = collectApi(); return _api; } /** @description 是否跑在 pywebview 桌面壳里(有壳但桥方法未就绪时也可能 true)。 */ export function isDesktopShell() { try { return !!(window.pywebview || (window.parent && window.parent !== window && window.parent.pywebview) || (window.top && window.top !== window && window.top.pywebview)); } catch (_) { return false; } } /** * 桌面桥 POST。path 接受 "motor/send" / "/api/motor/send" / "/ecat/api/motor/send"。 * @returns {Promise} 与 native 同构的 JSON */ export async function desktopEcatPost(path, body, timeoutMs) { const api = getDesktopEcatApi(); if (!api) throw new Error("desktop bridge unavailable"); return api.ecat_api(path, body == null ? {} : body, timeoutMs || 4000); } /** @description 状态栏用:当前 ECAT 控制通道形态。 */ export function desktopBridgeLabel() { return getDesktopEcatApi() ? "APP 原生桥" : "HTTP 代理"; }