Files
LocalNetMsg/docs/windows-compat.md
T
xiaoshuai d854c95ee6 fix(build): 修好 npm install 挂掉 (postinstall 只重建 better-sqlite3)
`npm install` 在 postinstall 阶段直接失败退出:

    node-hid 编译报错 -> node-addon-api/napi.h:2477 expected unqualified-id

根因不是 node-hid 坏了, 是重建范围错了:

1. postinstall 跑的是 `electron-builder install-app-deps`, 它重建**所有**原生模块;
2. 它给子进程传 `npm_config_build_from_source=false` —— 但这是**字符串** "false",
   npm 11 对未知的 npm_config_* 不做布尔转换;
3. pkg-prebuilds-verify 用 `if (process.env.npm_config_build_from_source)` 判断,
   字符串 "false" 在 JS 里是真值 -> 直接 exit 1 -> 强制源码编译;
4. node-hid 于是去编译它自带的 node-addon-api 3.2.1, 和现代 Node 头文件不兼容 -> 编译失败
   -> 整个 npm install 挂掉。

而 node-hid 根本不需要编译 —— 它自带 N-API 预编译产物, 任何 ABI 都能加载。

真正需要按 Electron ABI 重编的只有 better-sqlite3 (非 N-API, 绑死 V8/ABI,
Electron 32 -> node ABI 128); 其余 (@lydell/node-pty / node-hid /
@serialport/bindings-cpp / usb) 都是 N-API 预编译, 重编既没必要又有害。

改动:

- postinstall 换成 `node scripts/postinstall.js`:
  只重建 better-sqlite3, 装完**在 Electron 下真跑一次读写校验**, 失败就 exit 1
  —— 把"启动即崩"提前到 install 阶段暴露
- package.json 加 `allowScripts` 白名单 (npm 11 新机制, 目前只提醒不拦截,
  但迟早会拦; 顺便让 npm install 输出干净)
- AGENTS.md 坑 #3 改写成新规矩 + 完整根因链
- docs/windows-compat.md / docs/remote-terminal-forward-usb.md 里过时的
  "postinstall 走 install-app-deps" 表述一并纠正

验证:

- npm run build            ✅
- npm run typecheck        ✅ node + web
- npm run selftest         ✅ 64 通过 / 0 失败
- npm run selftest:sync    ✅ 全部通过
- npm run package:linux    ✅ 新 deb
- npm run selftest:package ✅ 4/4 (从 app.asar 真实 spawn pty)
2026-10-21 09:32:32 +08:00

8.7 KiB
Raw Blame History

Windows 兼容性分析与注意事项

LocalNetMsg 是跨平台应用 (Electron 32 / Node 20 / Chromium 128)。这份文档记录 Windows 上能跑什么、哪些地方有坑、以及已经修过的地方,避免以后改代码时把 Windows 弄坏。

测试环境:Ubuntu 22.04 (x64) + Electron 32.3.3。Windows 侧结论基于 依赖包源码与打包产物的静态验证,未在真实 Windows 机器上跑过(见文末"待验证")。


1. 结论速览

功能 Windows 说明
聊天 / 文件传输 / 网盘 ✅ 纯 Node HTTP/WS,无平台依赖
局域网发现 ✅ UDP 广播,多网卡 + 255.255.255.255 兜底
远程终端 ✅(修过) 需 ConPTY (Win10 1809+),打包规则曾有坑,见 §3.1
串口转发 ✅ serialport 有 win32 预编译;虚拟串口走 com0com
USB (libusb 字节桥) ⚠️ node-usb 可用,但需 WinUSB 驱动 (Zadig)
USB/IP 真透传 ❌ Linux 内核特性,代码里已明确拒绝
开机自启 ✅ app.setLoginItemSettings
托盘 / 通知 ✅ 托盘图标走 resources/icon.png

2. 原生模块:全是 N-API 预编译,不需要针对 Electron 重编

这是 Windows 打包能不能成的第一道关。build.npmRebuild 是 false (原生模块已在 Electron ABI 上,重编反而坏事),所以所有原生模块必须自带 跨 ABI 的预编译二进制。现状:

模块 Windows 产物 ABI 是否需要编译
@lydell/node-pty @lydell/node-pty-win32-x64 N-API (napi_register_module_v1) 否
node-hid prebuilds/HID-win32-x64/node-napi-v4.node N-API 否
@serialport/bindings-cpp prebuilds/win32-x64/*.node N-API 否
usb (node-usb 3) @node-usb/usb-win32-x64-msvc napi-rs 否
better-sqlite3 源码编译 非 N-API 是,靠 postinstall 的 scripts/postinstall.js

坑:better-sqlite3 是唯一需要编译的。npmRebuild: false 只影响打包期, postinstall 仍然会重编它。如果用 npm ci --ignore-scripts,应用会启动即崩 (sqlite 打开失败)。

postinstall 只重建 better-sqlite3 这一个包,不是 electron-builder install-app-deps。 后者会重建所有原生模块,并给子进程传 npm_config_build_from_source=false; npm 11 对未知的 npm_config_* 保留原始字符串,子进程里它是字符串 "false"(JS 真值), pkg-prebuilds-verify 直接 exit 1 强制源码编译,node-hid 编译失败把整个 npm install 拖垮 —— 而它自带 N-API 预编译产物,本来根本不用编译。 完整根因见 AGENTS.md 坑 #3。

node-pty 1.2 换成了"主包 + 平台包"拆包架构:主包只是 loader, 真正的二进制在 @lydell/node-pty-<platform>-<arch> 里。换平台时别忘了 平台包也要在 asarUnpack 名单里(见 §3.1)。


3. 已修复的 Windows 阻塞项

3.1 打包:node-pty 平台包没被解包 → Windows 终端起不来

现象:Linux 上终端正常,Windows 上一点就报 spawn 失败。

根因:build.asarUnpack 里写的是

"**/node_modules/@lydell/node-pty-*/*"     // ❌ 只匹配一层

/* 只匹配直接子项,于是 prebuilds/ 目录本身匹配上了,但 prebuilds/win32-x64/conpty.node 这种两层深的文件没匹配上 → 留在 asar 里。

为什么 Linux 上没暴露:Linux 的 PTY 只需要一个 pty.node, Electron 的 asar 层能 dlopen 归档里的 .node(实测可用)。 Windows 就不行了:

  • conpty.node 要加载同目录的 conpty.dll(OS loader 从真实路径加载,asar 读不了)
  • 还要 OpenConsole.exe(CreateProcess 必须是磁盘上的真文件)
  • node-pty 自己就写了 __dirname.replace('node_modules.asar', 'node_modules.asar.unpacked') —— 它明确假设平台包在 unpacked 目录里,因为 worker_threads.Worker 的脚本路径也必须是真实文件

修复:

"**/node_modules/@lydell/node-pty-*/**/*"   // ✅ 递归

自测 S7 会守住这条规则(防止以后又被改回单层)。

3.2 文件名:Windows 保留设备名 + 尾部点空格

Win32 的两个坑,Linux 上完全不存在:

  • 保留设备名:CON PRN AUX NUL COM1-9 LPT1-9 CLOCK$, 带扩展名也算(CON.txt 依然是控制台设备)。对这类名字 open() 打开的是设备, 数据会写进控制台或者直接失败。
  • 尾部点/空格被悄悄吞掉:report. 实际落到 report, 于是 existsSync('report.') 为真,而我们以为写的是另一个文件 —— 重名判断和实际落盘对不上。

修复:新增 src/main/safe-name.ts 的 safeFileName(), 在两处落盘前调用:

  • file-server.ts 的 targetPath() —— 接收方保存上传文件
  • drive.ts 的 downloadDriveFile() —— 名字来自对端共享目录,属于不可信输入

清洗策略是"最小必要":非法字符 \ / : * ? " < > | 全平台都清; 保留名和尾点尾空格只在 win32 上处理,避免无谓改掉 Linux 上合法的文件名。

3.3 终端工作目录

process.env.HOME 在 Windows 上通常不存在,但装了 Git Bash / MSYS 之后 可能被设成 POSIX 路径(/c/Users/xxx)—— 那个路径在 Win32 下不存在,pty 起不来。

修复:shell.ts 新增 defaultCwd(),win32 优先 USERPROFILE。


4. 已有 Windows 分支的地方(别改坏)

文件 Windows 处理
remote/shell.ts defaultShell() 优先 PowerShell 7 (pwsh),其次 Windows PowerShell 5;用 where 而不是 command -v
remote/vcom.ts 虚拟串口走 com0com(自动下载安装器 + setupc.exe 配对),Linux/macOS 走 socat
permissions.ts fixWindowsHID() 用 Zadig 装 WinUSB 驱动;diagnoseUsb() 给 Windows 提示
notify.ts win32 分支单独处理通知
discovery.ts UDP setBroadcast(true),同时发 255.255.255.255 / 127.0.0.1 / 每接口子网广播

Linux 专属逻辑(udev 规则、dialout 组、usbip、/sys/bus/usb 读取)都在 platform === 'linux' 判断内,Windows 不会走到。


5. 已知限制与注意事项

  1. ConPTY 需要 Windows 10 1809+。更老的 Windows(Server 2016 及以前) 没有 ConPTY,node-pty 1.2 的 Windows 实现直接依赖它 → 终端不可用。
  2. Windows 防火墙首次运行会弹窗问是否允许 Electron 访问网络。 用户若选了"取消",UDP 发现和 WS 都连不上 —— 这是最常见的"装完连不上"原因。
  3. USB 真透传需要 WinUSB 驱动。node-usb 走 libusb/WinUSB, Windows 上必须用 Zadig 把设备驱动换成 WinUSB,否则 loadDevices() 枚举不到 (代码里已能自动下载 Zadig 并提权安装)。
  4. 虚拟串口要 admin。com0com 装驱动必须管理员,代码走 sudo-prompt 提权。
  5. i.family 的取值。discovery.ts 里判断 i.family !== 'IPv4', Node 18+ 才把 family 从数字 4 改成字符串 'IPv4'。Electron 32 = Node 20 ✓, 但别把这行改成数字比较。
  6. 长路径。Windows 默认 260 字符上限,safeFileName 把文件名截到 200 留余量, 但"深目录 + 长文件名"仍可能超限,需要系统开启 LongPathsEnabled。
  7. 路径大小写不敏感。existsSync 在 Windows 上大小写不敏感, 所以 A.apk 和 a.apk 会被当成同一个文件 —— 重名自增逻辑能正确处理, 但 Linux/macOS 上会存成两个文件。两端行为不一致,属预期。

6. 在 Windows 上验证的清单

代码改完,Windows 侧至少要过这一遍(本机无法替代):

  • 安装后能启动、图标正常(见 docs/connection-status.md 里的图标坑)
  • 侧边栏能发现到对端设备(UDP 广播 + 防火墙)
  • 收发文字消息正常
  • 收发文件正常,文件名带中文/特殊字符/CON 也不出错
  • 网盘能浏览对方目录、能下载(含整目录保持层级)
  • 远程终端能打开、能看到回显、能输入
  • 串口列表能看到 COM 口(设备管理器里有)
  • USB 模式能枚举到设备(装了 WinUSB 的话)
  • 托盘图标正常、右键菜单能唤出窗口
  • 开机自启开关生效

7. 相关文件

文件 作用
src/main/safe-name.ts 跨平台文件名清洗(Windows 保留名 / 尾点空格)
src/main/remote/shell.ts 跨平台 shell 选择 + 默认工作目录
src/main/remote/vcom.ts 虚拟串口(socat / com0com)
src/main/permissions.ts 权限诊断与一键修复(udev / dialout / WinUSB)
build.asarUnpack (package.json) 原生模块解包规则,必须递归