feat(warp): Cloudflare WARP 设备注销(删除入队 + 机会式 drain 重试)#122
Merged
Conversation
按设计稿方案 F 落地 Cloudflare WARP 远端设备移除:
- WireGuardSettings 加可选 warpDevice {deviceId, token}(仅 WARP 节点填,
与 privateKey 同信任类、同脱敏,随 config 落盘)。
- shared/warp.ts 加纯逻辑:buildUnregisterRequest(裸 DELETE /reg/{id} +
Bearer + okhttp UA/CF-Client-Version)、classifyDeregisterResult
(204/404=done,401/403=drop,网络失败/超时/5xx/400/1020=retry)、
入队/drain 计划纯函数、阈值常量(7天/100/10)。
- WarpService.register 不再丢弃 token,随 draft.warpDevice 回渲染;
新增 unregister 捕获 HTTP 状态码(4xx/5xx 不抛)按分类返 done/drop/retry。
- warp-panel / wireguard-form 透传 warpDevice(生成与编辑都不丢凭据)。
- SERVER_DELETE 加 WARP 分支:带 token 则入队 pending-deregister.json
(删除恒瞬时、不内联调网络),守 MAX_QUEUE;旧节点无凭据则跳过仅删本地。
- 新增 WarpDeregisterQueue 服务:文件/网络注入式,drain 按年龄/分类
done/drop/超龄出队、retry 留队、MAX_PER_DRAIN 截断、并发重入保护;
触发点 = app 启动 + 成功注册 WARP 后,无常驻定时器。日志只打 deviceId 前缀。
- 脱敏:token 已在 SECRET_KEYS,递归脱敏天然覆盖 warpDevice.token 与队列条目;
补单测验证。DiagnosticService 不读队列文件,token 不入诊断包。
单测:注销请求构造 / 失败分类全矩阵 / 队列入队护栏 / drain 年龄+截断 /
register 产出 warpDevice / unregister 状态码分类 / token 脱敏。
审查发现三个真实问题并修复: 1. lost-update(队列健壮性):drain 读旧快照后在 await unregister 网络往返 期间,用户删新节点触发 enqueue 追加凭据,drain 用旧快照全量覆盖写回会抹掉 新条目 → 永久孤儿。改为:drain 写回前重读最新磁盘队列,仅剔除本次处理掉的 deviceId(done/drop/expire),保留 retry 与并发新增项。drain 写回与 enqueue 均经 opChain 串行链互斥,杜绝读改写交错。 2. 跨实例并发写:server-handlers 与 startup-tasks 各自 new 独立队列实例,实例级 串行链形同虚设。收敛为进程内单例 getWarpDeregisterQueue,三处触发点共用。 .tmp 临时文件改唯一后缀(pid+随机)+ 失败清理,杜绝多 writer 写同名 .tmp 致 字节交错损坏正本;readQueue 区分 ENOENT 与损坏 JSON(损坏记 warn)。 3. 1020 误判 drop(失败分类):classifyDeregisterResult 的 1020 优先逻辑依赖 err 文本,但 unregister 走 requestStatus 丢弃响应体、传 err=undefined → CF 版本串失效返回的 403+body-code-1020 被误判 drop(token 死)而非 retry, 版本过期时全部设备注销被永久误放弃。requestStatus 改为保留截断响应体并传给 分类,使 1020 检测在注销路径真正生效;同步补回超大响应 destroy 防护。 补单测:403+1020→retry / 403+1012→drop 集成回归;lost-update 写回回归。 gate 全绿(eslint 0 error,tsc main+renderer,jest 1423 passed)。
Sway-Chan
added a commit
that referenced
this pull request
Jun 20, 2026
* refactor: 精简 #86-122 收敛面(原子写/登录轮询/safeUrl/IPC 常量/dns lambda 等) - 抽 utils/atomic-write.writeFileAtomic:ConfigManager.saveConfig(保留 <rand6hex> 后缀匹配 sweep 正则 + mode:0o600) 与 WarpDeregisterQueue.writeQueueToDisk(默认 pid+随机后缀)复用;ProxyManager.atomicWrite 因固定 .tmp 后缀是 孤儿清扫正则契约保持原样 - 抽 shared/url.safeHttpUrl,替换 ProxyManager.handleTailscaleLoginUrl + use-native-events.handleTailscaleAuth 两处内联 - ProxyManager.watchTailscaleLogin 改走 runLoginPollLifecycle(Phase1 kill no-op);抽 emitTailscaleAuthOk 供两路共用 - ProxyManager.cleanup 收尾 tailscaleAuthSeen.clear()(URL 去重绑定核会话) - buildSpeedTestOutbound wireguard 分支用 isEndpointProtocol 单一真值(保留 tailscale→null 特例) - ProtocolParser.parseTuic heartbeat 接 normalizeDuration(与 parseAnyTls idle 对齐,防订阅裸毫秒 missing unit) - singbox-dns-builder 抽 withDotPrefix(NTP/others/bypass 三处)+ INTERNAL_DNS_SUFFIXES(保留 .local/bank 不合并) - IPC 常量收敛:补 EVENT_EXIT_PRIVACY_MODE/EVENT_NAVIGATE,index/Tray/UpdateService/tray-actions/App 裸字符串改引常量 - 删 App.tsx 冗余 refreshTailscaleLoginStates(loadConfig 内已 fire-and-forget 刷新) 行为字节保持:config-snapshot 34 快照零变化;gate 全绿(eslint/tsc×2/jest 1423 通过)。 * fix: #86-122 复审硬化(时长字段归一化防内核FATAL/瞬态登录核SIGKILL升级/登录态整表防覆盖) * fix: #86-122 复审硬化续1(locale 全 4 约束/WARP drain 补跑/IpInfo 重试收敛 + NIT TUIC·AnyTLS·DoH + #1/#2/#5 补单测) * fix: #86-122 复审硬化续2(DNS reverse 翻转/ir-ru 剪枝/watchTailscaleLogin 取消/ICMP 热切 regen/toast serverId) - #3 DNS reverse 翻转:singbox-dns-builder 地区分流 DNS 解析器镜像 route 侧 reverse—— reverse 时 region-local 域名→dns-remote、其余→dns-domestic、dns.final 翻 dns-remote; 正向字节零变(新增 reverse config-snapshot fixture,既有 34 快照不变)。 - #7 ir/ru region geosite DNS 侧 fail-closed:引用 region geo rule_set 前镜像 route 的 isValidSrsFile 缺失即跳过,防 DNS 悬空 rule_set 致 sing-box initialize FATAL。 - #8 watchTailscaleLogin 取消收紧:isCancelled 加 tailscaleEndpointInRunningCore 判定 (节点已切走且未就绪/已删除→取消),避免空转到 2min + 对已切走节点误发 AUTH_OK; 成功路径不破坏(state 落盘→ready→不取消→check 命中发 AUTH_OK)。 - #9 ICMP 兜底跨边界 regen:抽 selectedExitRoutesIcmpViaProxy 单一真值(route 发射 + 热切换共用),planHotSwitch 检测跨 proxy↔direct 边界即退回去抖重启重生成 config。 - NIT③ toast serverId:EVENT_TAILSCALE_AUTH_URL 带 serverId,渲染端 tsAuthToastId 用 serverId??nodeName,防同 hostname 不同 serverId 节点登录 toast 冲突。 * style: 清零 prettier 残留警告(min_idle_session 测试单行/inbounds 链式缩进/ip 测试换行) #86-122 复审硬化终审:eslint --fix 收敛 4 处 prettier/prettier 警告至 0 warning, 纯格式(换行合并/拆分),零行为变化。本轮两遍 gate 全绿(tsc main+renderer 0 错、 jest 96 suites/1459 tests/35 snapshots 全 pass)。 * fix(ipc): 修复测速 toast 通道被误并入逐节点通道——新增独立常量 EVENT_SPEED_TEST_RESULT_LIST('speedTestResult');IPC 收敛误把托盘 toast 的裸串映射成 EVENT_SPEED_TEST_RESULT('event:speedTestResult')致 toast 监听收到逐节点对象 results.map 崩溃 + latencyMap 污染
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
WARP「一键生成」会在 Cloudflare 侧注册一台匿名设备,但删本地节点时远端设备无从注销:当前
register()拿到的 token 用后即弃、draft.meta也被丢弃,FlowZ 没有凭据可调注销 API → 设备永久残留(匿名、零计费,但长期积累不优雅)。设计
register()不再丢 token,把{deviceId, token}随节点存进wireguardSettings.warpDevice(与privateKey同处、同脱敏)。判据「是否需注销」=warpDevice.token存在与否(零误判,不靠端点启发式)。<userData>/warp/pending-deregister.json(不内联调网络,删本地是纯本地操作、不依赖 Cloudflare 可达)。DELETE /{version}/reg/{deviceId}(Bearer + TLS1.2/okhttp 指纹)。无常驻定时器。204/404→done、401/403→drop、网络/超时/5xx/400/1020→retry。warpDevice→ 删除时跳过入队、仅删本地、不报错(注定孤儿、历史债)。SECRET_KEYS含token、redactDeep递归打码warpDevice.token与队列文件每条 token;日志只打deviceId前缀;诊断采集不读队列文件。验证
DELETE /reg/{id}+Bearer→204;再删→404Registration not found)。