wangjia
37c1724b67
fix(client): 切后台停看门狗 — 后台 urltest 被限流不再误判节点死断连
...
真机(Honor/MagicOS)复现:连上后切后台 ~1min 必断,报「当前节点异常」。
logcat 铁证:进程存活、Dart 看门狗后台照跑,
[Watchdog] urltest stale 59s → upstream dead → unhealthy → doStop
根因:后台(Doze)把 urltest 主动探测限流 → urltest 停更,但 speed 帧仍在流
→ statsLive=true + urltest 陈旧 → 路径A 误判上游死,把正常隧道断掉。上次的
statsLive 闸只挡「stats 整体停」(macOS 睡眠),挡不住「speed 在流、只 urltest 停」。
修:看门狗是前台 UX 功能(发现节点死好让用户换),后台无 UI 可响应、urltest 必被限流
→ AppLifecycleListener 在 paused/hidden 停看门狗,resumed 且仍连接则重启(基准由
_onStats 首帧/gap 重置)。真·内核掉线(路径A',事件驱动)不受影响,仍上报。
测试:connection_watchdog 新增「切后台停看门狗」回归(后台 speed 在流+urltest 停 >45s
不切节点);全 7 例过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 08:54:16 +08:00
wangjia
b23377acde
build(scripts): 各端 libbox 构建脚本修正 + 自动部署产物
...
排查 Android 打包"libbox.aar not found / Unresolved reference nekohasekai"踩坑:
- scripts/build-libbox.sh:
· 构建后自动部署产物到工程引用位置(android→app/kernel/dist/android/libbox.aar;
apple→client/{macos,ios}/Frameworks/Libbox.xcframework),不再留 /tmp 让人手动拷
· android 自动挑 NDK ≥28(未设 ANDROID_NDK_HOME 时):官方 build_libbox 拉 cronet-go,
其 libcronet.a 在 NDK 27 lld 链接报 unknown relocation(315)失败
· 头注释补全前置(NDK≥28 / JDK17 / gomobile 入 PATH)与产物路径
- app/kernel/build-android.sh:补 -javapkg io.nekohasekai —— 缺它产出默认包名 `libbox`,
与 Kotlin 的 io.nekohasekai.libbox 对不上、APK Kotlin 编译失败
- build.gradle:注释指明两条构建途径 + 包名必须 io.nekohasekai.libbox
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 07:53:35 +08:00
wangjia
07861ea04b
fix(client): 看门狗后台唤醒不再误判节点死→假重连 ( #18 )
...
现象:app/Mac 后台或睡眠一段时间再打开,出现"重连过程"但 VPN 没掉。
根因:连通看门狗路径A(urltest stale)用墙上时钟算陈旧度,挂起期 stats 停更、
_lastUrltestOk 冻住;唤醒时一算 >45s 即判上游死 → _onNodeUnhealthy 触发
disconnect+connect(智能模式还自动切节点),但隧道一直好好的。
修(connection_provider.dart 一处):
- _onStats 记 _lastStatsAt(任意 stats 帧时刻);stats 出现 >10s 长间断(挂起/唤醒)
时把 _lastUrltestOk 前移到现在,给节点新的 45s 窗口自证
- 看门狗路径A 增加 statsLive 闸:仅在 stats 确实在流(距今 <10s)且 urltest 停 ≥45s
时才判节点死;stats 整体停(挂起未恢复)则跳过本轮
- 加可注入时钟 _now(默认 DateTime.now),使时间驱动的 path A 可单测
测试:connection_watchdog_test 新增两例 —— ① stats 在流但 urltest 停≥45s 仍正常判死
切节点(path A 未被削弱);② 挂起 60s 唤醒不假重连(无 statsLive 闸则会误切,真回归)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 07:10:34 +08:00
wangjia
6bcdbbe752
revert(macos): killswitch 回退 L0(低优先级暂缓)
...
用户判定 killswitch 优先级低、暂不做:
- includeAllNetworks 实测堵死整机网络;退化方案 enforceRoutes+on-demand
几乎等于没做(核心 fail-closed 没拿到),on-demand 还对免费版配额造成
重连 churn —— 性价比低
- VpnChannel.swift 恢复到 L0 stub(setKillSwitch→result(nil));
PacketTunnelProvider.swift 已是原样
- docs §6.5 改为「尝试与暂缓」记录,状态表 macOS 回 L0
- local_test.sh 的 ks-* 漏测子命令保留(将来重拾 killswitch 时可用)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 06:38:47 +08:00
wangjia
a32d87c9c0
fix(macos): killswitch 撤掉 includeAllNetworks(堵死整机网络) — 改 enforceRoutes+on-demand
...
实测 includeAllNetworks=true 把所有流量(含 sing-box 连服务器握手、app 调控制面
请求)在隧道建起前就塞进隧道 → 握手出不去 → 连接失败 + on-demand 死循环 + 整机
断网(连「我的」页账户信息都拉不到、显示 —)。
改为不会误伤握手/控制面的组合:
- configureKillSwitch:includeAllNetworks 恒 false;保留 enforceRoutes(连接期防漏)
+ excludeLocalNetworks + NEOnDemandRule 常开(兜重连)
- 撤回 start() 的 killSwitch option 及扩展侧 includeAllNetworks 透传(回 false)
- 诚实标注:当前 L1→L2 之间,未到 includeAllNetworks-L3;要拿「扛进程被杀」须先
在扩展内放行服务器/控制面连接,留待真机验证
docs/killswitch-design.html: §6.5 加踩坑修正、状态表 macOS 改「L1→L2 之间」
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 06:09:04 +08:00
wangjia
97c0fdbbad
test(macos): local_test.sh 加 killswitch fail-closed 漏测子命令
...
ks-baseline:VPN 断开下记录真实公网 IP 基线
ks-test:VPN 连接(killswitch 开)下 sudo 杀扩展进程,在 on-demand 重连
窗口内反复探 IP —— 任何一次拿到真实 IP 即判泄漏(FAIL),全程被阻断/
仅隧道 IP 即 fail-closed 生效(PASS);随后验 on-demand 自愈
ks-status:看 VPN 状态 + 统一日志里 killSwitch/includeAllNetworks 打点
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 00:01:51 +08:00
wangjia
680f1af205
feat(macos): KillSwitch L0→L3 — NE includeAllNetworks+enforceRoutes+on-demand
...
macOS 走原生 NE,strict_route 不生效。补齐断网保护到天花板 L3:
VpnChannel.swift
- 缓存 killSwitchEnabled(默认 true,与 Dart AppSettings.killSwitch 对齐)
- setKillSwitch 落地:存标志,manager 已装配则即时重写 NE 配置+保存
- configureKillSwitch:includeAllNetworks+enforceRoutes(L2 OS 强制、扛崩溃)
+ NEOnDemandRule 常开(L3)+ excludeLocalNetworks 放行 LAN
(enforceRoutes/excludeLocalNetworks 为 macOS 11+ API,#available 守卫)
- stop() gotcha:on-demand 常开时先关 isOnDemandEnabled+save 再 stop,
否则手动断开被 OS 立刻拉回
- start() options 带 killSwitch 传扩展
PacketTunnelProvider.swift
- startTunnel 读 killSwitch 选项(OS on-demand 自启时 nil→默认 true)
- libbox 回调 includeAllNetworks() 返回该值,与 NE 层对齐
docs/killswitch-design.html: 新增 §6.5 实现方案,状态表 macOS L0→L3
验证:xcodebuild CODE_SIGNING_ALLOWED=NO 两 target SwiftCompile 通过;
端到端 fail-closed/on-demand 需真机手测(NE 难单测)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 23:54:32 +08:00
wangjia
e2440e44cc
docs: 补 CLAUDE.md 移动端/跨端统计章节 + 新增 urltest 排障文档
...
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Has been cancelled
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Has been cancelled
ci-pangolin / Go — build + test (push) Has been cancelled
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Has been cancelled
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Has been cancelled
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Has been cancelled
- CLAUDE.md:加「移动端(iOS/iPad+Android)原生隧道」「跨端实时统计+urltest 延迟」两节——
libbox 构建前置(JDK 17/包名/gitignore)、clash_api 注入正解、libbox 单 client Group 坑、
Dart 共享广播流、连接态跳直连实测;附「最后在线取 last_seen」一句。
- 新增 docs/connect-latency-urltest.html(排障/Runbook):四端 urltest 实现 + 根因链 + 打点法,
登记进 docs/index.html。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 22:56:10 +08:00
wangjia
53fb6492b1
chore: 整理——gitignore android/.gradle 构建缓存 + 同步 todo 看板
...
- client/.gitignore 加 /android/.gradle/(gradle 构建缓存,不该入 git)。
- 同步 todo.json/html 到当前状态。
注:server/internal/pb/agent/v1 是 stray 生成产物(未被任何代码 import,代码用手写的
agentv1),未跟踪、不影响合并;清理需 rm,留给用户(! rm -rf server/internal/pb/agent)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 22:52:32 +08:00
wangjia
00bec95255
chore(client): 清理 tablet golden 旧命名孤儿文件( #11 )
...
删除 5 个旧命名(无 theme/lang 后缀)且无任何测试引用的 tablet golden:
tablet_account/connect/connect_dark/servers/stats.png。当前 tablet_pages_golden_test 的
命名矩阵是 tablet_<view>_{light_zh,light_en,dark_zh}(12 个),孤儿是早期命名残留。
顺手清掉 test/golden/failures/(gitignored 的失败产物)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 22:47:39 +08:00
wangjia
d0d39c93f6
fix(client): 设备列表离线时间改用 last_seen「最后在线」(原 last_login 不准)
...
设备列表对离线设备显示「最后登录 X 前」,取的是 last_login(会话创建那一刻)。一直在线
的设备断开后会错显成登录时间(如 13 小时前),与实际「最后活跃」不符。改用 last_seen
(连接/用量上报/~15s 会话轮询都会刷新),标签相应改为「最后在线」/「Last online」。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 22:30:43 +08:00
wangjia
1666b1d462
fix(client/android): libbox 包名 io.nekohasekai.libbox(原 import libbox.* 编不过)
...
gomobile(sing-box build_libbox)产出的 AAR 包是 io.nekohasekai.libbox,而 Kotlin 一直
import libbox.*(含内联 libbox.NetworkInterface)→ 全模块 Unresolved。此前 Android 从未编
到这步(libbox.aar 缺失),故未暴露。改为 io.nekohasekai.libbox.* 后 APK 构建通过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 22:20:52 +08:00
wangjia
e994c44116
feat(client/android): urltest 延迟与 iOS/macOS 对齐 + 开明文(联调 http API)
...
Android 同 iOS 根因:单 CommandClient 的 Group 命令拿不到 urltest + 配置无 clash_api。
- PangolinVpnService:startKernel 注入 clash_api(127.0.0.1)+ cache_file;新增 pollClash
经本地 HTTP 查 /proxies(history)+ /group/<name>/delay 取真实延迟,替换原依赖 Group
回调的 urlTestTimer(Group 不回调,延迟恒空)。逻辑与 iOS StatsCollector 完全一致。
- Manifest:usesCleartextTraffic=true —— 控制面 API 当前 http,Android 9+ 默认禁明文,
否则登录就连不上(生产转 https 后去掉)。
注:需 libbox.aar(gomobile,要 JDK 17)才能编译验证;本机仅 JDK 21,待装 17。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 22:06:11 +08:00
wangjia
328d34a320
fix(client): 「我的」页加「设置」入口(我的设备下面)——iPad 竖屏/手机可达设置
...
iPad 竖屏回落 mobile 底 Tab(无侧栏),设置入口够不着。在账户页「我的设备」下面加一行
「设置」→ 下钻 SettingsScreen(复用桌面 SettingsPage + 移动返回栏)。与「联系我们」一致:
desktop/tablet 侧栏 + 账户页都可进。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 21:48:54 +08:00
wangjia
849e30dd05
fix(client): iPad(tablet)对齐桌面——侧栏补设置/联系我们 + 顶部避让状态栏
...
iPad 测试反馈:① 平板侧栏只有 连接/节点/统计,缺「设置」「联系我们」(桌面侧栏有,只能从
「我的」页进);② 顶部 brand/标题与 iOS 状态栏重叠(TabletShell 没包 SafeArea)。
- TabletShell:侧栏补 设置/联系我们 两项 + content 路由到 SettingsPage/ContactPage(对齐
DesktopShell);整体包 SafeArea(bottom:false)避开状态栏/notch。
- SettingsPage:开机自启是桌面登录项概念,iOS/Android 无此能力 → 仅 desktop 平台显示该行。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 21:36:33 +08:00
wangjia
ae5d497f0b
feat(client/ios): 实时统计+urltest 延迟与 macOS 对齐 + 修 pbxproj UUID 冲突
...
把 macOS 已验证的统计架构移植到 iOS,补齐连接页延迟(此前 iOS 同 macOS 根因:扩展没注入
clash_api + app 单 client Status+Group → urltest 恒空):
- 扩展 PacketTunnelProvider:加 injectClashApi(clash_api+cache_file)+ 进程内 StatsCollector
(libbox 取速率 + clash /proxies、/group/delay 取 urltest);handleAppMessage 增 action="stats"。
- app StatsClient:改为每秒 sendProviderMessage("stats") 拉取 → VpnStatsStreamHandler 推送,
去掉直连 socket 的单 client Status+Group 坏写法。
修两处编译阻断(框架补齐后才暴露,此前 iOS 从未编译到这步):
- pbxproj 致命 bug:StatsClient.swift 与 PacketTunnelProvider.swift 复用同一 UUID
(...0002...AA/AB),Xcode 无法区分 → 文件错配 target、全模块 cannot-find 级联。
PacketTunnelProvider.swift 改用独立 ...0007... ID。
- tunFd 的 CTLIOCGINFO 单行 | 链拆成子表达式,避免 Swift 类型检查器超时。
前置:scripts/build-libbox.sh apple ios 生成 client/ios/Frameworks/Libbox.xcframework(gitignore)。
Dart 侧无需改(iOS 同走 VpnNativeBridge,本轮 macOS 的广播流/_onStats 回退/连接态跳直连实测已共享)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 19:12:28 +08:00
wangjia
7add0259b4
chore(client/macos): 清理延迟排查的诊断打点 + debug 注入(最终干净版)
...
延迟显示已修通。移除排查期的临时诊断:扩展侧 Log 客户端/klog 捕获、log.level=debug 注入、
diag 字段/setDiag;主 app StatsClient 每5帧的 dl/ul/urltest 打印;Dart 侧 [Stats]/[Ping] 打点。
保留全部修复:clash_api 注入取 urltest、sendProviderMessage 统计通道、/proxies history+active
缓存、共享广播流、_onStats 回退 effectiveNode、连接态跳过直连实测。
CURRENT_PROJECT_VERSION 52→53。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 18:03:30 +08:00
wangjia
e00163e2d9
fix(client): statsStream/statusStream 缓存为共享广播流,修延迟不回写
...
根因:VpnNativeBridge.statsStream getter 每次访问都 receiveBroadcastStream(),多订阅者
(连接页速度 + ConnectionController._onStats 回写 urltest 延迟)各起一条原生订阅;但
EventChannel 原生侧只留一个 sink(StatsStreamHandler.onListen 覆盖),后订阅者赢、先订阅者
变哑。速度订阅赢了 sink → _onStats 收不到 → urltest 实测(已确认到达 native:reality-out=595)
永不回写 node.ping → 连接页延迟一直 —。
修复:receiveBroadcastStream() 只调一次、缓存成 asBroadcastStream() 共享流,所有 Dart
订阅者复用同一条原生订阅。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 17:57:37 +08:00
wangjia
e6e7d5db10
diag(client): _onStats 顶部无条件打点 + 订阅打点,确认是否被调用
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 17:47:16 +08:00
wangjia
d14f7a0ec5
diag(client): _onStats/setLivePing 打点,定位延迟为何不回写 node.ping
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 17:26:56 +08:00
wangjia
dbe657f997
fix(client): 自动连接到已运行隧道时延迟显示 — —— _onStats 回退 effectiveNode
...
连接页延迟一直 —:自动连接/重连到「已在跑的隧道」时不走 _connect → _connectedNode 为
null → _onStats 提前 return,内核 urltest 实测(已确认到达 app:reality-out=579)无处回写
node.ping。与看门狗 _checkHealth/_probe 一致,回退到 effectiveNode(当前/智能所选节点)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 17:17:57 +08:00
wangjia
e3fb5239a6
diag(client/macos): 抓 sing-box urltest 内核日志,定位探测为何失败
...
确认 urltest 为空是内核探测 100% 失败(传输/读取链路均正常)。本次注入 log.level=debug,
StatsCollector 新增 Log 命令客户端,抓取内核日志里 urltest/gstatic 相关行(失败原因:
DNS/连接/超时),塞进 stats 的 klog 字段经主 app 可读日志透出 —— 直接看到内核报错。
CURRENT_PROJECT_VERSION 51→52。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 14:58:07 +08:00
wangjia
211fda37e3
fix(client/macos): urltest 完全照搬 Windows 策略——读 /proxies history + active 刷新
...
Windows(kernel_process.dart)的策略:显示源是 /proxies 各 URLTest 成员的 history.last.delay
(每拍读、缓存住、稳定);/group/<name>/delay 只为让内核重测并写进 history,其结果不直接用。
我之前直接用 active 结果,拥塞链路上 active 恒返回 {} 就全空了。
本次照搬:主显示读 /proxies history(extractUrltestResults 字段与 Windows 一致),active
/group/delay 既刷新 history 又顺手缓存(双保险);非空才更新,单次失败不覆盖旧值。
CURRENT_PROJECT_VERSION 50→51。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 14:45:14 +08:00
wangjia
e8e6a0dc24
fix(client/macos): urltest 回到 active /group/delay 取值(v48 验证可行),跳过 GLOBAL
...
v49 改读 /proxies history 后为空:sing-box 的 /proxies 不带 member history。回到 v48 验证
可行的 active 取法——/group/<name>/delay(跳过 GLOBAL 选择器组),成功就缓存,超时/空不
覆盖旧值,显示稳定。移除无效的 extractUrltestResults。
扩展代码变更 → CURRENT_PROJECT_VERSION 49→50。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 14:22:52 +08:00
wangjia
a228a1d221
fix(client/macos): urltest 主源改读 /proxies history(稳定),修连接页延迟
...
v48 验证延迟已打通(urltest=reality-out=576 到达主 app),但 active /group/delay 在拥塞
链路上常超时返回 {}。改为与 Windows extractUrltestResults 完全一致:主源读 /proxies 里各
URLTest 组成员的 history 缓存延迟(稳定,active 探测超时也不丢),并对 URLTest 组(跳过
GLOBAL 选择器)主动跑 /group/<name>/delay 刷新 history。延迟显示由此稳定。
扩展代码变更 → CURRENT_PROJECT_VERSION 48→49。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 14:13:58 +08:00
wangjia
70e8f67702
diag(client/macos): 把 /group/delay 原始响应塞进 diag,定位 urltest 为空
...
v47 诊断:clash 注入生效(组名 auto 已识别),但 /group/auto/delay 返回无数字延迟。
本次把该接口原始响应体(含 HTTP 状态码)透出到 diag,并兼容嵌套 {tag:{delay:ms}} 形态,
一眼区分:内核探测失败(raw={} / 错误消息)还是返回结构不符(解析问题)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 13:28:55 +08:00
wangjia
03dbbf8db9
fix(client/macos): urltest 延迟改走注入的 clash API(对齐 Windows),内核侧打点
...
诊断:即便专开 Group client,扩展回的 urltest 数组恒空——根因是服务端下发配置无
experimental.clash_api,sing-box 不暴露出站组/urltest 历史,libbox Group 命令拿不到。
修复(与 Windows 完全同法):扩展内向配置注入 clash_api(127.0.0.1 本地监听)+ cache_file,
StatsCollector 经本地 HTTP 查 /proxies 找 URLTest 组、/group/<name>/delay 取真实延迟。
速率仍走 libbox Status。新增内核侧 NSLog 打点 kernel delay(<group>)=<tag:ms>,并把该值
塞进 stats 的 diag 字段经主 app 可读日志透出——一眼分清「内核有没有算出延迟」vs「外面没拿到」。
扩展代码变更 → CURRENT_PROJECT_VERSION 46→47。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 12:54:40 +08:00
wangjia
783687d7c2
fix(client/macos): StatsCollector 用独立 client 订阅 Group,修 urltest 延迟为空
...
诊断确认:连接页延迟显示 — 是因为扩展回给主 app 的 urltest 数组恒为空(speeds 正常)。
根因:libbox command 协议是「一连接一命令」,把 Status 与 Group addCommand 到同一 client
只有 Status 生效,Group 永不回调 → 无 urltest。对齐 sing-box-for-apple:每命令各开一个
client。新增 groupClient 专订 Group + 跑 urlTest,延迟即随统计帧回到连接页(与 Windows 同源)。
扩展代码变更 → CURRENT_PROJECT_VERSION 45→46,让 sysextd 重装。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 12:33:00 +08:00
wangjia
a43f8a93de
diag(client/macos): StatsClient 每5帧打印收到的 urltest 延迟,排查连接页延迟为 —
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 12:25:33 +08:00
wangjia
6809e0a377
fix(client): 连接态延迟只认内核 urltest,跳过直连实测(对齐 Windows 方法)
...
连接页延迟显示 ~3ms:连接态下 _measure 的直连 TCP 实测被全局 TUN 在本地接住,
connect() 几毫秒即返回(假值),覆盖了内核 urltest 经 setLivePing 回写的真实 RTT
(macOS system 栈 TUN 尤其明显)。Windows 端延迟本就取 sing-box urltest(clash API
/proxies 的 history delay),macOS 经 libbox writeGroups 的 item.urlTestDelay 同源。
修复:_measure 在 connectionProvider.phase==on 时直接返回,不做直连实测——连接态延迟
只认 urltest(与 Windows 一致),断开后再恢复直连实测。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 12:18:32 +08:00
wangjia
6c19b1bd92
fix(client/macos): 实时统计改走 sendProviderMessage,根治容器路径死局
...
上一版试图让 root 扩展把 command.sock 建到用户容器,反而让 startTunnel 抛错、隧道连不上
(root 在用户 home 建 socket、属主/路径与无 root 的 app 对不上,且方向本就错)。回退该路子。
正解:用 NetworkExtension 官方跨进程通道,绕开「root 扩展容器 vs 用户 app 容器」死局——
- 扩展内新增 StatsCollector:连本进程自己的 command.sock(同容器、root 可达),订阅
status/group 缓存最新上下行 + urltest;handleAppMessage 收到 "stats" 即回最新 JSON。
- 主 app StatsClient 改为每秒 NETunnelProviderSession.sendProviderMessage("stats") 拉取、
解析后推 onStats(不再直连 socket);VpnChannel 三处启动点改传 session。
- 扩展代码变更 → CURRENT_PROJECT_VERSION 44→45,让 sysextd 重装。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 12:05:43 +08:00
wangjia
71132df669
fix(client/macos): sysext 把 command.sock 建在登录用户容器并 chown,修实时统计断流
...
根因:系统扩展以 root 运行,其 App Group 容器是 /var/root/Library/Group Containers/...,
而用户 app 的是 /Users/<user>/...(容器 home 相对)。libbox 把 command.sock 建在 basePath
下 → 落在 root 容器;无 root 权限的 app 永远 connect 不上 → 连接页 — KB/s + 延迟不刷新。
(经 sudo ls 实测确认:socket 在 /var/root 容器,用户容器里没有。)
- PacketTunnelProvider 用 SCDynamicStoreCopyConsoleUser 取登录用户,把 libbox basePath/
workingPath 指向该用户的同名容器(root 可写),command.sock 即落在 app 查找的路径。
- server.start() 后 chown 容器/work/command.sock 给登录用户:Unix socket connect() 需写
权限,仅 owner 满足,否则 app 仍 EACCES。拿不到登录用户时回退 root 容器(隧道可用、仅统计不可用)。
- sysext 代码改动 → CURRENT_PROJECT_VERSION 43→44,让 sysextd 重装新扩展。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 11:43:41 +08:00
wangjia
4f727beb60
fix(client/macos): 启动即激活 sysext + command.sock 打点
...
旧 sysext(on-demand/常驻)会在 app 启动前自动重连,app 走 primeExistingConnection
而非 start() → 永不提交 OSSystemExtensionRequest → bundle 内的新版 sysext 永远装不上,
运行的还是旧版(无 CommandServer)→ command.sock 不存在 → StatsClient 断流(— KB/s + 旧延迟)。
- VpnChannel.register 阶段调 activateOnLaunch():同开发者高版本静默 replace 运行中的旧扩展。
- StatsClient 连接前打点 command.sock 路径与存在性,直接暴露「旧 sysext 没起 CommandServer」。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 10:50:23 +08:00
wangjia
77f31c90ec
fix(client/macos): 递增 PacketTunnel CFBundleVersion 23→43,让 sysextd 重装新 sysext
...
根因:PacketTunnel 的 CURRENT_PROJECT_VERSION 写死 23(其配置无 baseConfig、拿不到
FLUTTER_BUILD_NUMBER 自动递增)→ 每次构建出的 sysext 都是 v23 → sysextd 视为同版本拒绝
重装 → 跑的一直是老 v23 sysext(没有 LibboxCommandServer)→ App Group 容器无 command.sock
→ 主 app StatsClient 连不上(no such file)→ 实时流量/urltest 全失效(连接页 0 B/s + 延迟 1ms)。
bump 到 43(高于主 app 的 42)→ sysextd 重装带命令服务的新 sysext。
注:sysext 代码改动后须手动递增此值(CLAUDE.md 铁律)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 10:34:15 +08:00
wangjia
cf19ca2d34
feat(client/macos): 开机自启用 SMAppService + LoginItem helper(学 Tailscale)
...
根因:launch_at_startup 0.5.1 在 macOS 无自带实现,需宿主接 MethodChannel,而我们 Runner
从没实现 → enable() 抛 MissingPluginException 被 try/catch 吞掉 → 登录项从未注册,自启失败。
学 Tailscale(LoginItemHelper-macsys + SMAppService):
- 新增无界面 LoginItem helper(macos/login_helper,LSBackgroundOnly),登录时带 --autostart
拉起主 app(→隐藏到托盘);
- build_login_helper.sh 构建期编译+签名+嵌入 Contents/Library/LoginItems/(新 Run Script 阶段);
- MainFlutterWindow 接 launch_at_startup MethodChannel → SMAppService.loginItem 注册/注销。
构建通过:helper 嵌入+签名,主 app --deep --strict 校验过。Windows 自启(schtasks)不变。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 10:25:27 +08:00
wangjia
9e26dd6d97
chore: bump 1.0.41+42
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 09:00:00 +08:00
wangjia
8680131b3d
fix(client): 登出(主动/被强退/会话过期)一律断开隧道
...
bug:logout() 只清 token,不碰隧道 → 被强制下线/正常退出后 VPN 还连着、继续用数据面。
修:ConnectionController 监听 authProvider,isLoggedIn 由 true→false 时断开隧道(_disconnect)。
一处覆盖所有登出路径(设置页登出/会话轮询强退/token 续期失败登出);token 续期仍登录不触发。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 09:00:00 +08:00
wangjia
f0af3bcc94
fix(server): 强退设备立即离线 — online 须含活跃会话 + 撤销会话不刷 last_seen
...
trick bug:强退后被踢设备的下一次会话轮询会先刷 last_seen 再发现 active=false,那一刷
把它顶成「在线」直到 90s 窗口过期。修:① online = last_seen 新 且 有未撤销会话
(ActiveSessionDeviceIDs);被强退设备无活跃会话 → 立即离线。② SessionActive 仅在 active
时才 TouchLastSeen,撤销会话的轮询不再刷新。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 08:51:10 +08:00
wangjia
2fbaba7608
chore: bump 1.0.40+41
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 08:05:32 +08:00
wangjia
d192d96d95
fix(client): 设备页每 15s 静默刷新列表,其他设备状态变化 ~15s 内可见
...
原设备列表打开时加载一次就不动(感觉十几分钟才同步)。加透传式轮询包装器
_DevicesAutoRefresh:在设备页期间每 15s 调 DevicesNotifier.refresh()(静默、不闪 loading、
失败保留旧列表)。配合服务端会话轮询刷 last_seen + 90s 窗口,其他设备上/下线 ~15s 内反映。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 08:05:32 +08:00
wangjia
3167ca8a74
fix(server): 设备「在线」准确反映 app 运行中 — 会话轮询刷 last_seen + 窗口缩到 90s
...
原 online=last_seen 在 3min 内,而 last_seen 只在连接/用量上报时刷 → app 运行≠刷新,
显示成'最近连过'而非'正在运行';关 app 后还'在线'到窗口过期。修:SessionActive(每 15s
轮询)顺便 TouchLastSeen → 运行中的 app 持续刷新;onlineWindow 3min→90s,关 app ~90s 转离线。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 07:55:17 +08:00
wangjia
ca34d0cadb
chore: bump 1.0.39+40
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 07:33:43 +08:00
wangjia
6bab18a1bf
feat(client): 被强制下线时弹对话框告知原因再登出
...
会话轮询检测到 active=false → 先弹「此设备已被强制下线/需重新登录」对话框(沿用现有
AlertDialog 样式:surface 底/xl 圆角/accent 按钮),用户确认后再 logout 跳登录页,
不再默默跳走。_forcedLogoutHandling 防重复弹。新增 l10n sessionRevoked{Title,Body,Ok}。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 07:33:43 +08:00
wangjia
af24612e04
chore: bump 1.0.38+39
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 07:26:31 +08:00
wangjia
0b33f12400
feat: 近实时远程下线 — 客户端轮询会话有效性(~15s),服务端 GET /v1/me/session
...
控制面无推送通道,access token 是无状态 JWT(15min),强制退出后被踢设备要等 token 过期
(≤15min)才登出。改成客户端每 15s 轮询会话是否仍有效,被强制退出即登出 → 延迟压到 ~15s。
- 服务端:sessions.HasActiveSession(user,device) + devices.SessionActive(按 UUID,fail-open)
+ GET /v1/me/session?device_id= 返回 {active}(恒 200,判据在 body)。无新迁移。
- 客户端:account_api.sessionActive + main.dart _RootFlowState 15s 轮询,active=false 即 logout
(网络/鉴权异常不据此登出,fail-safe)。
- 测试:TestSQLite_SessionHasActiveSession(建会话=活跃→RevokeByDevice→非活跃)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 07:25:16 +08:00
wangjia
7f070a0693
fix(client): 看门狗 path A 仅在「连上(on)后掉线」触发,排除 connecting 握手瞬态
...
macOS NE 连接序列是 off→connecting→off→connecting→on,connecting 期有瞬态 off。原 path A
条件 wasActive 含 connecting → 一连接就在握手瞬态 off 上误报「节点异常」(实际连上了)。
改:仅 phase==on(真正连上过)再非用户主动掉线才判节点异常;connecting→off(握手抖动/
连接失败)不触发。跨端修复(桌面也更稳)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 07:01:13 +08:00
wangjia
48bc5c6b39
chore: bump 1.0.37+38
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 01:32:15 +08:00
wangjia
b99d6d71ae
fix(client): 修 _disposed 潜伏 bug + build 只 watch isLoggedIn,根治延迟冻成 —
...
根因:1.0.36 的 401 自愈触发 auth token 续期 → authProvider 状态变 → nodesProvider.build()
重跑 → Riverpod 先调上一轮 ref.onDispose(()=>_disposed=true),新 build 没复位 → _disposed
永久 true → setLivePing/_measure 开头的 if(_disposed)return 让 urltest 回写/延迟实测全部静默
→ 延迟冻在 —。修:① build 开头 _disposed=false 复位;② build 改 watch authProvider.select
((s)=>s.isLoggedIn),token 续期(accessToken 变但仍登录)不再重建 nodesProvider、不清延迟。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 01:32:15 +08:00
wangjia
15133b8a72
feat(client/macos): 开机自启隐藏到托盘(--autostart),与 Windows 对齐
...
macOS 登录项(launch_at_startup)注册时带 args:['--autostart'];main() 读该参数
windowManager.hide();MainFlutterWindow.awakeFromNib 检测到 --autostart 时 orderOut
兜底(避免 storyboard Visible At Launch 闪窗)。常规启动无参数,正常显示窗口。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 01:27:30 +08:00
wangjia
ba65b03fd5
chore: bump 1.0.36+37
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 01:23:13 +08:00