4fc86edd4e
## 关系层 (src/main/relations.ts, relation-service.ts) 原来"发现即信任": UDP 广播包里明文带着 driveToken, 同网段任何进程拿到就能浏览 你的整个网盘, 而且没有"授权"这个概念。现在拆成三层: 发现 (临时) -> 关系 (持久, 用户显式建立) -> 授权 (分级, 双方各自独立) 建立关系的方式跟远程终端的授权闸门**完全一致** —— 对方直接弹窗, 不用邀请码: A 点"发起同步关系" -> WS 帧 relation:request -> B 侧弹窗 (复用 ApprovalDialog, kind='relation') -> 同意 -> relation:accept -> 双方落库并交换 per-peer 令牌。 - 令牌: myToken=我签发给对方的, peerToken=对方签发给我的 - **driveToken 不再进广播包**; file-server 在关系令牌匹配不上时回退到旧的全局 token, 但只允许"只读浏览网盘" —— 不给同步区, 也不给写/删 - 关系存 JSON 文件 (原子写), 不做 schema 迁移 - 授权能力: drive(浏览我的网盘) / sync(与我同步文件), 双方各自独立勾选 - 远程终端/端口转发/USB 仍走原来的 remoteAllowPeers 白名单, 没有并进关系层 - 同一对端 60s 内只弹一次窗 (WS 是"发现即建立"的, 不限流就能被刷屏) ## 同步区: 独立于网盘的第二套空间 用户的同步目录可以是任意文件夹 (~/文档/工作), 不必待在网盘目录里。 /drive/* 网盘 -> 共享目录, 要 drive 授权 /sync/* 同步区 -> 某关系专属的同步目录, 要 sync 授权, 路径严格限制在该目录内 顶层目录可以同时和多个设备同步: 每个关系自动分配一个子目录 (~/备份/MECHREVO/, ~/备份/lenovo/), 物理隔开, 同名文件不会互相覆盖 —— "统一目录多个聊天备份"成立。 ## 文件关系表 (src/main/sync-manifest.ts) —— 同步盘地基 核心是**区分"文件被删了"和"文件本来就不存在"**: - **墓碑**: 删除不抹记录, 只标 state='deleted'。直接抹掉的话, 对方离线回来会把 已删文件当成"新增"重新下载 -> 删除永远删不掉; 对方从备份恢复也会让删除被 静默撤销。有墓碑 + seq 比较就都能挡住。 - **seq = Lamport 时钟**: hash 只能答"一样不一样", seq 才能答"谁更晚" —— 判定 删除方向全靠它。扫描顺序必须是 advanceClock -> reconcileLocal -> mergePeer (先收消息再产生事件), 否则跨机器的 seq 不可比, 删除方向会判反。 - **三方对比**: 基线(上次两边一致的 hash) + 本地 + 远端。缺基线才退回 mtime 兜底。 - 墓碑留 90 天 (比大品牌默认 30 天保守), 且要"对方已越过该 seq"才清理。 - **删除传播**: 对方推关系表过来时, 比对基线确认我没改过才删本地那份; 改过则 报冲突 —— 绝不静默丢数据。 - 冲突: 手动同步弹窗对比让用户选方向; 自动同步跳过并记录, 不替用户决定。 ## 其余 - 自动同步 (src/main/auto-sync.ts): 60s 轮询 + 指数退避, 对方上线/开关打开时 立刻跑一轮。 - 同步面板 (SyncPanel.vue): 关系区 + 变更列表 (含"将删除对方/本机") + 冲突对比 弹窗 + 自动同步开关。 - 关系授权弹窗复用远程终端那套 ApprovalDialog。 - 目录穿越防护: 上传/下载/删除的路径都严格限制在各自根目录内, 且**软链归一后 再比一次** (同步目录是用户任意文件夹, 里面出现软链很常见)。 - 跨关系校验: 令牌与 relationId 绑定, A 关系的对方不能读写 B 关系的同步目录。 - 关系令牌在 IPC 返回给渲染层时剥掉, 界面拿不到凭据。 - 建关系/授权/同步/删除传播都写 audit_log。 ## 测试 - npm run selftest 64 通过 / 0 失败 (R关系 / M墓碑删除传播 / Y同步盘) - npm run selftest:sync 26 条全通过 (双节点端到端: 建立关系->同步->删->传播->防复活) ## 文档 docs/relation-and-sync.md —— 三层模型 / 关系建立 / 墓碑与 seq / 三方对比判定表 / 安全要点 / 已知不一致。
130 lines
5.3 KiB
JSON
130 lines
5.3 KiB
JSON
{
|
|
"name": "local-net-msg",
|
|
"version": "0.1.0",
|
|
"private": true,
|
|
"description": "局域网即时通讯 (Electron + Vue 3) - 飞书风格",
|
|
"homepage": "https://github.com/localnetmsg/local-net-msg",
|
|
"author": "LocalNetMsg <dev@localnetmsg.local>",
|
|
"main": "out/main/index.js",
|
|
"scripts": {
|
|
"dev": "electron-vite dev",
|
|
"build": "electron-vite build",
|
|
"preview": "electron-vite preview",
|
|
"start": "electron-vite preview",
|
|
"typecheck:node": "tsc --noEmit -p tsconfig.node.json",
|
|
"typecheck:web": "vue-tsc --noEmit -p src/renderer/tsconfig.json",
|
|
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
|
"selftest:build": "esbuild scripts/selftest/main.ts --bundle --platform=node --format=cjs --target=node20 --outfile=scripts/selftest/.out/selftest.cjs --alias:electron=./scripts/selftest/electron-stub.cjs --alias:@lydell/node-pty=./scripts/selftest/pty-shim.cjs --external:better-sqlite3 --external:serialport --external:usb --external:node-hid --external:sudo-prompt --external:ws",
|
|
"selftest": "npm run selftest:build && cross-env ELECTRON_RUN_AS_NODE=1 electron scripts/selftest/.out/selftest.cjs",
|
|
"selftest:sync:build": "esbuild scripts/selftest/sync-e2e.ts --bundle --platform=node --format=cjs --target=node20 --outfile=scripts/selftest/.out/sync-e2e.cjs --alias:electron=./scripts/selftest/electron-stub.cjs --external:better-sqlite3 --external:serialport --external:usb --external:node-hid --external:sudo-prompt --external:ws",
|
|
"selftest:sync": "npm run selftest:sync:build && cross-env ELECTRON_RUN_AS_NODE=1 electron scripts/selftest/.out/sync-e2e.cjs",
|
|
"selftest:package": "cross-env ELECTRON_RUN_AS_NODE=1 electron scripts/selftest/packaging-check.cjs",
|
|
"package:win": "cross-env ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/ electron-vite build && electron-builder --win --x64",
|
|
"package:mac": "cross-env ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/ electron-vite build && electron-builder --mac",
|
|
"package:linux": "cross-env ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/ electron-vite build && electron-builder --linux",
|
|
"package:dir": "cross-env ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/ electron-vite build && electron-builder --dir",
|
|
"postinstall": "electron-builder install-app-deps"
|
|
},
|
|
"dependencies": {
|
|
"@element-plus/icons-vue": "^2.3.1",
|
|
"@lydell/node-pty": "^1.2.0-beta.12",
|
|
"@xterm/addon-fit": "^0.11.0",
|
|
"@xterm/addon-web-links": "^0.12.0",
|
|
"@xterm/xterm": "^6.0.0",
|
|
"better-sqlite3": "^11.5.0",
|
|
"electron-store": "^8.2.0",
|
|
"element-plus": "^2.8.4",
|
|
"highlight.js": "^11.10.0",
|
|
"markdown-it": "^14.3.0",
|
|
"node-hid": "^3.3.0",
|
|
"pinia": "^2.2.4",
|
|
"serialport": "^13.0.0",
|
|
"sudo-prompt": "^9.2.1",
|
|
"usb": "^3.0.1",
|
|
"ws": "^8.18.0"
|
|
},
|
|
"devDependencies": {
|
|
"@types/better-sqlite3": "^7.6.11",
|
|
"@types/markdown-it": "^14.1.2",
|
|
"@types/node": "^22.7.5",
|
|
"@types/ws": "^8.5.12",
|
|
"@vitejs/plugin-vue": "^5.1.4",
|
|
"cross-env": "^7.0.3",
|
|
"electron": "^32.1.2",
|
|
"electron-builder": "^24.13.3",
|
|
"electron-vite": "^2.3.0",
|
|
"typescript": "^5.6.2",
|
|
"vite": "^5.4.8",
|
|
"vue": "^3.5.10",
|
|
"vue-router": "^4.4.5",
|
|
"vue-tsc": "^2.1.6"
|
|
},
|
|
"build": {
|
|
"appId": "com.localnetmsg.app",
|
|
"productName": "LocalNetMsg",
|
|
"copyright": "Copyright © 2026",
|
|
"directories": {
|
|
"output": "dist-Electron",
|
|
"buildResources": "resources"
|
|
},
|
|
"npmRebuild": false,
|
|
"files": [
|
|
"out/**/*",
|
|
"package.json"
|
|
],
|
|
"asarUnpack": [
|
|
"**/node_modules/@lydell/node-pty/**/*",
|
|
"**/node_modules/@lydell/node-pty-*/**/*",
|
|
"**/node_modules/@node-usb/**/*",
|
|
"**/node_modules/usb/prebuilds/**/*",
|
|
"**/node_modules/@serialport/**/prebuilds/**/*",
|
|
"**/node_modules/node-hid/prebuilds/**/*"
|
|
],
|
|
"extraResources": [
|
|
{
|
|
"from": "resources/icon.png",
|
|
"to": "icon.png"
|
|
}
|
|
],
|
|
"win": {
|
|
"icon": "resources/icon.png",
|
|
"target": [
|
|
{
|
|
"target": "nsis",
|
|
"arch": [
|
|
"x64"
|
|
]
|
|
}
|
|
],
|
|
"artifactName": "${productName}-${version}-Setup.${ext}"
|
|
},
|
|
"nsis": {
|
|
"oneClick": false,
|
|
"perMachine": false,
|
|
"allowElevation": true,
|
|
"allowToChangeInstallationDirectory": true,
|
|
"createDesktopShortcut": true,
|
|
"createStartMenuShortcut": true,
|
|
"shortcutName": "LocalNetMsg"
|
|
},
|
|
"mac": {
|
|
"icon": "resources/icon.png",
|
|
"target": "dmg",
|
|
"category": "public.app-category.social-networking"
|
|
},
|
|
"linux": {
|
|
"icon": "resources/icons",
|
|
"target": [
|
|
"deb"
|
|
],
|
|
"category": "Network",
|
|
"synopsis": "LAN instant messaging",
|
|
"description": "LocalNetMsg — a LAN instant messaging desktop app.",
|
|
"maintainer": "LocalNetMsg <dev@localnetmsg.local>",
|
|
"desktop": {
|
|
"StartupWMClass": "local-net-msg"
|
|
}
|
|
}
|
|
}
|
|
}
|