// device_identity.dart — 设备身份:稳定 device_id + 名称/平台/客户端版本上报 // // device_id 首次启动随机生成(UUID v4)、写入 flutter_secure_storage 持久化,之后 // 每次读取复用 → 同一安装跨重启恒定(替换早期硬编码 'mac-001')。名称/平台/版本 // 取自系统。随 登录/注册/连接 上报给控制面,用于「我的设备」登记与会话绑定。 import 'dart:io' show Platform; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:uuid/uuid.dart'; /// 设备元数据,随认证/连接请求上报。 class DeviceMeta { const DeviceMeta({ required this.id, required this.name, required this.platform, required this.clientVersion, }); final String id; final String name; final String platform; // ios | android | windows | macos | linux final String clientVersion; Map toJson() => { 'id': id, 'name': name, 'platform': platform, 'client_version': clientVersion, }; } /// 极简键值存储接缝(便于单测注入;默认走 flutter_secure_storage)。 abstract class SecureKV { Future read(String key); Future write(String key, String value); } class _SecureStorageKV implements SecureKV { // 与 TokenStore 一致:macOS 文件式 keychain,避免未签名 app 报 -34018。 static const _s = FlutterSecureStorage( mOptions: MacOsOptions(useDataProtectionKeyChain: false), ); @override Future read(String key) => _s.read(key: key); @override Future write(String key, String value) => _s.write(key: key, value: value); } /// 设备身份服务。device_id 持久于安全存储;其余字段取自系统(缓存)。 class DeviceIdentity { DeviceIdentity({SecureKV? store}) : _kv = store ?? _SecureStorageKV(); final SecureKV _kv; static const _kDeviceId = 'pangolin_device_id'; static const _uuid = Uuid(); String? _cachedId; DeviceMeta? _cachedMeta; /// 稳定 device_id。读到→复用;读到空→生成并持久化; /// 读**失败**(平台异常)≠ 不存在 → 退回进程内临时 id,**不写库**,避免冲掉真实 id。 Future deviceId() async { if (_cachedId != null) return _cachedId!; String? existing; try { existing = await _kv.read(_kDeviceId); } catch (_) { return _cachedId ??= _uuid.v4(); // 临时,不持久化 } if (existing != null && existing.isNotEmpty) return _cachedId = existing; final id = _uuid.v4(); try { await _kv.write(_kDeviceId, id); } catch (_) { // 写失败:本次用内存值,下次再尝试持久化。 } return _cachedId = id; } /// 完整设备元数据(缓存,首次解析后复用)。 Future meta() async { if (_cachedMeta != null) return _cachedMeta!; final m = DeviceMeta( id: await deviceId(), name: await _name(), platform: currentPlatform(), clientVersion: await _clientVersion(), ); return _cachedMeta = m; } /// 当前平台标识(与服务端 devices.platform 枚举对齐)。 static String currentPlatform() { if (Platform.isIOS) return 'ios'; if (Platform.isAndroid) return 'android'; if (Platform.isMacOS) return 'macos'; if (Platform.isWindows) return 'windows'; if (Platform.isLinux) return 'linux'; return 'unknown'; } /// 平台简写(默认名前缀,保持短)。 static String _shortPlatform(String p) => switch (p) { 'windows' => 'Win', 'macos' => 'Mac', 'linux' => 'Linux', 'ios' => 'iOS', 'android' => 'Andr', _ => p, }; // 默认名 = 平台简写·主机名(桌面用主机名,移动用机型);主机名截到 8、总长≈12,尽量短。 // 用户可在「我的设备」改名覆盖。 Future _name() async { var host = ''; try { if (Platform.isIOS) { final i = await DeviceInfoPlugin().iosInfo; host = i.name.isNotEmpty ? i.name : i.utsname.machine; } else if (Platform.isAndroid) { final a = await DeviceInfoPlugin().androidInfo; host = a.model; } else { host = Platform.localHostname; } } catch (_) { host = ''; } host = host.trim(); if (host.length > 8) host = host.substring(0, 8); final short = _shortPlatform(currentPlatform()); return host.isEmpty ? short : '$short·$host'; } Future _clientVersion() async { try { final info = await PackageInfo.fromPlatform(); return 'v${info.version}'; } catch (_) { return ''; } } } /// 单例 DeviceIdentity(测试可 override)。 final deviceIdentityProvider = Provider((ref) => DeviceIdentity()); /// 本机 device_id(供「本机」高亮;未就绪时为空)。 final localDeviceIdProvider = FutureProvider((ref) => ref.read(deviceIdentityProvider).deviceId());