import 'dart:async'; import 'package:device_plugin_interface/device_plugin_interface.dart'; import 'package:get/get.dart'; import '../data/models/user_Info.dart'; import 'bes/bes_device_auth.dart'; import 'bes/bes_device_plugin.dart'; import 'device_bind_registry.dart'; import 'device_hub.dart'; import 'holder/holder_device_plugin.dart'; import 'smartcar/smartcar_device_auth.dart'; import 'smartcar/smartcar_device_plugin.dart'; /// 确权结论(厂商无关)。 /// /// ⚠️ [unknown] 的语义是**放行**,不是"失败"。判不出来时放行是刻意选的失败方向: /// 判严的代价是合法用户一断网就用不了自己的设备;判宽只是未登记设备离线时 /// 能用一会儿,联网第一次确权就会被拦下。 enum DeviceAuthResult { /// 白名单命中 / 服务端确认 / 已在 User.devices 里 authorized, /// 服务端明确拒绝(这台 MAC 没登记) denied, /// 未登录 / 断网 / 拿不到 MAC —— **放行** unknown, } /// 设备确权的业务层入口(登出重置、解绑清白名单、补校当前设备)。 /// /// 确权本身是各厂商插件在连接时就地做的(恒玄见 [BesDeviceAuth]),这里只把 /// 需要从业务层触发的几个动作聚在一起,业务层不用知道是哪家的实现。 class DeviceAuth { DeviceAuth._(); /// 登出:换账号后同一台设备要重新登记到新账号名下。**白名单不清**。 static void reset() { BesDeviceAuth.reset(); SmartcarDeviceAuth.reset(); // 换账号后同一台设备要重新登记到新账号名下(服务端会接管) DeviceBindRegistry.reset(); } /// 解绑:移出白名单,下次连上重新走服务端确权。 /// /// 两家都调:调用方只有一个 MAC,不知道它属于哪个厂商, /// 而各家白名单是按 MAC 存的、互不相干,多清一次无副作用。 static Future forget(String mac) async { await BesDeviceAuth.forget(mac); await SmartcarDeviceAuth.forget(mac); } /// 【调试/售后】清空白名单 static Future clearWhitelist() async { await BesDeviceAuth.clearWhitelist(); await SmartcarDeviceAuth.clearWhitelist(); } /// 补校当前连着的、需要确权的设备(顺带完成绑定登记)。 /// 「先连设备后登录」时连接事件里的登记会因未登录跳过,这里补一次。 /// /// ⚠️ **每接一种会绑定的设备都要在这里加一个分支**。漏掉的后果不是「少一层 /// 保险」,而是那种设备**永远补不上登记**:连接时若还没登录就跳过了, /// 而此后没有任何别的路径会再触发。支架(holder)原先就漏在这儿 —— /// 冷启动时它被动回连的速度常常快过登录态恢复,于是设备连着、 /// 「设备管理」页却一直是空的,且不报任何错。 static Future verifyConnected() async { final hub = DeviceHub.to; for (final s in hub.sessions.toList()) { if (!s.supports(DeviceCapability.macAddress)) continue; final p = hub.plugin(s.vendor); if (p is BesDevicePlugin) { await p.link.auth?.verifyConnected(); } else if (p is HolderDevicePlugin) { // 支架没有单独的确权类,登记就在服务自己身上(本进程内已成功的会去重) await p.link.bindConnectedToServer(); } else if (p is SmartcarDevicePlugin) { // Smartcar 的 MAC 来自广播,两端都拿得到(不像恒玄 iOS 只有 UUID) await SmartcarDeviceAuth.verify( mac: s.info.macAddress, name: s.info.name, ); } } } static Worker? _loginWorker; /// App 启动时挂一次:登录态一变化就对当前连着的设备补报一次绑定登记。 /// /// ⚠️ 不能只靠「连接时登记」+「进设备管理页补一次」这两条:设备是被动回连的, /// 冷启动时它上线常常早于登录态恢复,那次登记直接被「未登录」跳过; /// 而用户不一定会去点设备管理页 —— 于是设备连着、服务端却没有绑定记录, /// 「我的设备」空着还不报错。登录完成后补这一次,把这个时序洞堵上。 static void bindLoginRetry() { _loginWorker?.dispose(); _lastLoggedIn = User.isLoggedIn(); _loginWorker = ever(User.refreshTick, (_) { final now = User.isLoggedIn(); final was = _lastLoggedIn; _lastLoggedIn = now; // 只在**登录态从无到有**那一次补报。refreshTick 在用户信息每次刷新时都会 +1, // 不收敛的话会对「服务端拒绝」的设备反复打 binddevice(成功的那些有 _bound 去重, // 失败的没有)——那正是还没导入 MAC 的设备最常见的状态。 if (now && !was) unawaited(verifyConnected()); }); } static bool _lastLoggedIn = false; }