修复外部浏览器 CDP 无响应: ws 从主进程 bundle 外部化
根因: ws 被打进主进程 bundle 时, 其可选依赖 bufferutil 的打包解析产物 破坏了客户端发帧的 mask fallback; permessage-deflate 协商开启时错误被 压缩路径静默吞掉 —— 表现为 ws 握手成功但所有 CDP 命令零字节发出、 无响应(输入文本节点 10s 超时), dev 与打包版皆受影响。 - electron.vite.config: externalizeDepsPlugin include ws, 运行时 require - package.json: ws 提升到根依赖, electron-builder 随包收集 - electron-builder.config: 确认 node_modules/ws 进入 asar - transport: 显式 perMessageDeflate:false(双保险, 附注释) - package.mjs: verifyLaunch 剥离 ELECTRON_RUN_AS_NODE/NODE_OPTIONS, 防止外部工具链环境把打包应用带进纯 Node 模式(bad option 退出码 9) - flows: 演示流程支持 RPA_DEMO_BROWSER 强制指定浏览器(Chrome 缺装回退 Edge) 验证: 全量测试 349 绿 + 浏览器 e2e 绿 + portable/NSIS 双冒烟通过, 安装包/zip/SHA256 已产出。
This commit is contained in:
parent
3a9a8b63c2
commit
22a7ee1595
@ -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" },
|
||||
|
||||
@ -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") },
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
@ -60,7 +60,11 @@ export class ExternalTransport implements CdpTransport {
|
||||
readonly description: string
|
||||
) {
|
||||
this.openPromise = new Promise<void>((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)));
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"generatedAt": "2026-11-04T04:34:06.782Z",
|
||||
"generatedAt": "2026-11-04T09:12:00.449Z",
|
||||
"count": 909,
|
||||
"ids": [
|
||||
"App_GetPid",
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user