import 'dart:io' show Platform; import 'package:android_id/android_id.dart'; import 'package:android_oaid/android_oaid.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter_paid/flutter_paid.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:platform_metadata/platform_metadata.dart'; import 'package:uuid/uuid.dart'; import '../storage/storage_keys.dart'; import '../storage/storage_service.dart'; /// 请求公共 + 平台身份 header 来源(完全对齐研值契约)。 /// AuthInterceptor 每个请求注入 commonHeaders() + platformIdentityHeaders(), /// token 另由拦截器注入。空值 header 一律过滤。 /// /// 注:`User-Agent` 这里不表示浏览器 UA,而是沿用后端请求头契约中的平台标识, /// 值同:iOS/Android/Web/Other。 /// /// 广告/设备标识(OAID/IMEI/MAC/IDFA/Paid/DeviceToken)由各自 SDK/ATT 流程 /// 写入 StorageService,这里读出注入;Android 标识仅在用户同意隐私后采集。 class DeviceHeaderService { DeviceHeaderService._(); static final DeviceHeaderService to = DeviceHeaderService._(); static const _storage = FlutterSecureStorage(); static const _kDeviceIdKey = 'device_id'; // static const _platformMetadataChannel = MethodChannel('platform_metadata'); // 渠道:优先编译期 --dart-define=UMENG_CHANNEL=xxx;Android 其次读 manifest, static const _umengChannel = String.fromEnvironment('UMENG_CHANNEL'); String _deviceId = ''; String _version = ''; String _build = ''; String _deviceModel = ''; String _userChannel = ''; String _iosIdfv = ''; String _iosPaid = ''; String _androidId = ''; String _androidOaid = ''; String _androidImei = ''; String _androidMac = ''; IosDeviceInfo? iosDeviceInfo; AndroidDeviceInfo? androidDeviceInfo; Future? _baseInitialization; Future? _identityInitialization; Future ensureInitialized() async { _baseInitialization ??= _initializeBase(); await _baseInitialization; // Align with tradingagents-flutter-app: Android only initializes device // information after the user has accepted the privacy policy. if (!kIsWeb && Platform.isAndroid && !StorageService.to.getBool(storagePrivacyAgreed)) { return; } _identityInitialization ??= _initializeIdentity(); await _identityInitialization; } Future _initializeBase() async { try { final info = await PackageInfo.fromPlatform(); _version = info.version; _build = info.buildNumber; } catch (_) {} _userChannel = await _resolveUserChannel(); } Future _initializeIdentity() async { _deviceId = await _resolveDeviceId(); _androidOaid = StorageService.to.getString(storageAndroidOaid) ?? ''; _androidImei = StorageService.to.getString(storageAndroidImei) ?? ''; _androidMac = StorageService.to.getString(storageAndroidMac) ?? ''; _androidId = StorageService.to.getString(storageAndroidId) ?? ''; _iosPaid = StorageService.to.getString(storageIosPaid) ?? ''; await _resolvePlatformIdentity(); } /// X-Device-Id:iOS 优先 IDFV(per-vendor 稳定),否则 UUIDv4; /// 首次生成后写入 secure_storage,后续复用。 Future _resolveDeviceId() async { final stored = await _storage.read(key: _kDeviceIdKey); if (stored != null && stored.isNotEmpty) return stored; var id = ''; if (!kIsWeb && Platform.isIOS) { id = await _resolveIdfv(); } if (id.isEmpty) id = const Uuid().v4(); await _storage.write(key: _kDeviceIdKey, value: id); return id; } Future _resolveIdfv() async { try { final idfv = (await DeviceInfoPlugin().iosInfo).identifierForVendor; return idfv ?? ''; } catch (_) { return ''; } } /// 设备型号 + 平台身份标识。Android 标识需用户同意隐私后才采集。 Future _resolvePlatformIdentity() async { if (kIsWeb) return; try { final plugin = DeviceInfoPlugin(); if (Platform.isAndroid) { _deviceModel = (await plugin.androidInfo).model; await _collectAndroidIdIfAgreed(); await _collectAndroidOaidIfAgreed(); await _collectAndroidImeiIfAgreed(); await _collectAndroidMacIfAgreed(); } else if (Platform.isIOS) { _deviceModel = (await plugin.iosInfo).utsname.machine; _iosIdfv = await _resolveIdfv(); await _collectIosPaidIfMissing(); } await getDeviceInfo(); } catch (_) {} } Future getDeviceInfo() async { try { final deviceInfoPlugin = DeviceInfoPlugin(); if (Platform.isIOS) { iosDeviceInfo = await deviceInfoPlugin.iosInfo; } else if (Platform.isAndroid) { androidDeviceInfo = await deviceInfoPlugin.androidInfo; } } catch (e) { print('$e'); } } Future _collectAndroidIdIfAgreed() async { if (_androidId.isNotEmpty) return; if (!StorageService.to.getBool(storagePrivacyAgreed)) return; // 隐私同意后才采集 try { _androidId = await const AndroidId().getId() ?? ''; if (_androidId.isNotEmpty) { await StorageService.to.setString(storageAndroidId, _androidId); } } catch (_) {} } Future _collectAndroidOaidIfAgreed() async { if (_androidOaid.isNotEmpty) return; if (!StorageService.to.getBool(storagePrivacyAgreed)) return; try { _androidOaid = await const AndroidOaidId().getOaid() ?? ''; if (_androidOaid.isNotEmpty) { await StorageService.to.setString(storageAndroidOaid, _androidOaid); } } catch (_) {} } Future _collectAndroidImeiIfAgreed() async { if (_androidImei.isNotEmpty) return; if (!StorageService.to.getBool(storagePrivacyAgreed)) return; try { _androidImei = await const AndroidId().getImei() ?? ''; if (_androidImei.isNotEmpty) { await StorageService.to.setString(storageAndroidImei, _androidImei); } } catch (_) {} } Future _collectAndroidMacIfAgreed() async { if (_androidMac.isNotEmpty) return; if (!StorageService.to.getBool(storagePrivacyAgreed)) return; try { _androidMac = await const AndroidId().getMaAddress() ?? ''; if (_androidMac.isNotEmpty) { await StorageService.to.setString(storageAndroidMac, _androidMac); } } catch (_) {} } Future _collectIosPaidIfMissing() async { if (_iosPaid.isNotEmpty) return; try { _iosPaid = await FlutterPaid().paid() ?? ''; if (_iosPaid.isNotEmpty) { await StorageService.to.setString(storageIosPaid, _iosPaid); } } catch (_) {} } /// 用户同意隐私协议后调用。设备信息由随后调用的 ensureInitialized 补采。 Future markPrivacyAgreed() async { await StorageService.to.setBool(storagePrivacyAgreed, true); } /// 是否需要首启隐私合规确认。**仅 Android**(合规要求:同意前不得采集设备 /// 标识)。由 go_router redirect 调用,未同意则强制跳隐私页。 bool get privacyConsentRequired => !kIsWeb && Platform.isAndroid && !StorageService.to.getBool(storagePrivacyAgreed); /// iOS 首次启动页:仅在首次安装后展示一次。 bool get iosStartupRequired => !kIsWeb && Platform.isIOS && !StorageService.to.getBool(storageIosStartupSeen); Future markIosStartupSeen() async { if (kIsWeb || !Platform.isIOS) return; await StorageService.to.setBool(storageIosStartupSeen, true); } String get deviceId => _deviceId; // User-Platform:平台标识(替代研值的 User-Agent)。 String _platform() { if (kIsWeb) return 'Web'; if (Platform.isIOS) return 'iOS'; if (Platform.isAndroid) return 'Android'; return 'Other'; } Future _resolveUserChannel() async { if (_umengChannel.isNotEmpty) return _umengChannel; if (!kIsWeb && Platform.isAndroid) { try { final manifestChannel = await PlatformMetadata.getMetaDataValue( 'UMENG_CHANNEL', ); if (manifestChannel != null && manifestChannel.isNotEmpty) { return manifestChannel; } } catch (_) {} } if (kIsWeb) return 'web'; if (Platform.isIOS) return 'ios'; if (Platform.isAndroid) return 'android'; return 'other'; } String _s(String key) => StorageService.to.getString(key) ?? ''; /// 公共 header(不含 token)。11 Map commonHeaders() => _compact({ 'X-Device-Id': _deviceId, 'User-Version': _version, 'User-BuildID': _build, 'User-Platform': _platform(), 'User-Channel': _userChannel, 'User-DeviceType': _deviceModel, 'User-DeviceToken': privacyConsentRequired ? '' : _s(storageDeviceToken), }); /// 平台身份 / 广告标识 header(按平台)。 Map platformIdentityHeaders() { if (kIsWeb) return const {}; if (Platform.isAndroid) { if (!StorageService.to.getBool(storagePrivacyAgreed)) return const {}; return _compact({ 'User-Android-ID': _androidId, 'User-Android-OAID': _androidOaid, 'User-Android-IMEI': _androidImei, 'User-Android-Mac': _androidMac, }); } if (Platform.isIOS) { return _compact({ 'User-iOS-IDFV': _iosIdfv, 'User-iOS-IDFA': _s(storageIosIdfa), 'User-iOS-Paid': _iosPaid, }); } return const {}; } } Map _compact(Map headers) { final out = {}; headers.forEach((k, v) { if (v.trim().isNotEmpty) out[k] = v; }); return out; }