diff --git a/electron-builder.config.mjs b/electron-builder.config.mjs index 9b4f704..ffaccb7 100644 --- a/electron-builder.config.mjs +++ b/electron-builder.config.mjs @@ -18,7 +18,10 @@ export default { }, files: [ "out/**/*", - "package.json" + "package.json", + // ws 已从主进程 bundle 外部化(运行时 require),必须随包发布; + // ws 为零依赖纯 JS 库,asar 内即可解析 + "node_modules/ws/**/*" ], extraResources: [ { from: path.join(root, "resources", "catalog.json"), to: "catalog.json" }, diff --git a/electron.vite.config.ts b/electron.vite.config.ts index de253f4..a26c507 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -4,7 +4,9 @@ import react from "@vitejs/plugin-react"; export default defineConfig({ main: { - plugins: [externalizeDepsPlugin()], + // ws 必须外部化运行时 require:打包进 bundle 时其可选原生依赖 bufferutil 的解析产物 + // 会破坏 mask fallback(客户端发帧静默丢字节 / 报 mask is not a function) + plugins: [externalizeDepsPlugin({ include: ["ws"] })], resolve: { alias: [ { find: "@shared", replacement: resolve("src/shared") }, diff --git a/package.json b/package.json index 65468e4..8bc97fc 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "react-dom": "^18.3.1", "sm-crypto": "^0.5.8", "tesseract.js": "^7.0.0", + "ws": "^8.22.0", "zustand": "^5.0.2" }, "devDependencies": { diff --git a/packages/rpa-core/src/browser/external.ts b/packages/rpa-core/src/browser/external.ts index 900adb1..775ebf9 100644 --- a/packages/rpa-core/src/browser/external.ts +++ b/packages/rpa-core/src/browser/external.ts @@ -302,7 +302,7 @@ export async function openNewPageTarget(httpBase: string, url = "about:blank"): } return { id: String(target.id ?? ""), - webSocketDebuggerUrl: target.webSocketDebuggerUrl, + webSocketDebuggerUrl: String(target.webSocketDebuggerUrl), url: String(target.url ?? url) }; } diff --git a/packages/rpa-core/src/browser/transport.ts b/packages/rpa-core/src/browser/transport.ts index 1dfcb1c..1893db4 100644 --- a/packages/rpa-core/src/browser/transport.ts +++ b/packages/rpa-core/src/browser/transport.ts @@ -60,7 +60,11 @@ export class ExternalTransport implements CdpTransport { readonly description: string ) { this.openPromise = new Promise((resolve, reject) => { - const ws = new WebSocket(wsUrl, { maxPayload: 256 * 1024 * 1024 }); + // perMessageDeflate 必须显式关闭:ws 客户端默认开启压缩协商后,若 ws 被打包进 + // 主进程 bundle,其可选依赖 bufferutil 的解析产物会破坏 mask fallback, + // 压缩发送路径的错误又被静默吞掉 —— 表现为帧零字节写出且无任何报错。 + // (ws 已在 electron.vite.config 外部化运行时 require,此项为双保险) + const ws = new WebSocket(wsUrl, { maxPayload: 256 * 1024 * 1024, perMessageDeflate: false }); this.ws = ws; ws.on("open", () => resolve()); ws.on("message", (data: unknown) => this.handleMessage(String(data))); diff --git a/resources/capability-snapshot.json b/resources/capability-snapshot.json index 8e7d48a..55dad12 100644 --- a/resources/capability-snapshot.json +++ b/resources/capability-snapshot.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-11-04T04:34:06.782Z", + "generatedAt": "2026-11-04T09:12:00.449Z", "count": 909, "ids": [ "App_GetPid", diff --git a/scripts/package.mjs b/scripts/package.mjs index 4f23244..6808e45 100644 --- a/scripts/package.mjs +++ b/scripts/package.mjs @@ -46,16 +46,21 @@ function removeTree(target) { async function verifyLaunch(executable, label) { const userData = path.join(os.tmpdir(), `RPA-Studio-UserData-${Date.now()}-${Math.random().toString(16).slice(2)}`); + // ELECTRON_RUN_AS_NODE / NODE_OPTIONS 来自外部工具链环境时会把打包应用带进纯 Node 模式 + // (表现为「bad option」退出码 9),冒烟必须剥离这些不属于应用的控制变量 + const env = { + ...process.env, + RPA_SKIP_SINGLE_INSTANCE: "1", + RPA_E2E: "1", + RPA_FIXTURE_PORT: String(18000 + Math.floor(Math.random() * 1000)) + }; + delete env.ELECTRON_RUN_AS_NODE; + delete env.NODE_OPTIONS; const child = spawn(executable, [`--user-data-dir=${userData}`], { cwd: path.dirname(executable), stdio: "ignore", windowsHide: false, - env: { - ...process.env, - RPA_SKIP_SINGLE_INSTANCE: "1", - RPA_E2E: "1", - RPA_FIXTURE_PORT: String(18000 + Math.floor(Math.random() * 1000)) - } + env }); await delay(8000); const exited = child.exitCode !== null || child.signalCode !== null; diff --git a/src/main/flows.ts b/src/main/flows.ts index d8029ea..c673f3b 100644 --- a/src/main/flows.ts +++ b/src/main/flows.ts @@ -74,6 +74,9 @@ export function createFlow(name = "未命名流程"): FlowDefinition { /** 演示流程浏览器类型:优先 Chrome,未安装时回退 Edge;两者都缺失则保持 chrome 由能力层给出友好错误 */ function demoBrowserType(): string { + // RPA_DEMO_BROWSER 可强制指定(e2e/诊断用) + const forced = String(process.env.RPA_DEMO_BROWSER || "").trim().toLowerCase(); + if (forced === "chrome" || forced === "edge") return forced; const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"); const chromePaths = [ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",