import 'dart:convert'; import 'package:analytics_sdk/utils/device_fingerprint_util.dart'; import 'package:analytics_sdk/utils/logger.dart'; import 'package:crypto/crypto.dart'; import 'package:flutter/foundation.dart'; /// SDK 全局上下文:持有所有公共字段、提供 event_id 计算。 /// /// 作为 SDK 内部的单一状态来源(SSOT),所有事件构造时从此处读取公共字段。 /// 不对外暴露,接入方无需直接使用。 class SdkContext { /// SDK 版本号(写死,与 pubspec.yaml 保持一致) static const String kSdkVersion = '1.4.6'; /// appVersion 公共字段默认值。 /// 接入方未传 `appVersion`、或 release 模式下传入非法值时均使用该值, /// 保证事件 payload 与 `AnalyticsSdk.getParams()` 返回值保持一致。 static const String kDefaultAppVersion = '1.0.0'; /// 客户端事件签名规则版本。 static const String kEventSignatureVersion = '2.0.0'; static String _eventSignatureHmacKey = ''; static String _eventSignatureVersion = kEventSignatureVersion; static String? _appId; static String? _channel; static String? _uid; static String? _appVersion; static String? _device; static String? _deviceId; static String? _deviceBrand; static String? _deviceModel; static String? _userAgent; static String? _systemName; static String? _systemVersion; static String? _deviceFingerprint; static String _fingerprintVersion = DeviceFingerprintUtil.kVersion; static Map _environmentFields = _defaultEnvironmentFields(); /// SID 提供者(由 AnalyticsSdk.init() 注入)。 /// 调用时同时返回当前 SID 并记录用户行为,避免 entity 层直接依赖 SessionManager。 static String Function()? sidProvider; /// 配置公共字段 /// /// 所有参数均为可选,非 null 值才会覆盖现有值。 /// 通常在 [AnalyticsSdk.init] 中调用,也可在 init 之后按需更新。 /// deviceFingerprint / fingerprintVersion 不再支持从外部传入。 static void configure({ String? appId, String? channel, String? uid, String? appVersion, String? device, String? deviceId, String? deviceBrand, String? deviceModel, String? userAgent, String? systemName, String? systemVersion, String? deviceFingerprint, String? fingerprintVersion, Map? environmentFields, }) { if (appId != null) _appId = appId; if (channel != null) _channel = channel; if (uid != null) _uid = uid; if (appVersion != null) { if (RegExp(r'^\d+\.\d+\.\d+$').hasMatch(appVersion)) { _appVersion = appVersion; } else if (kDebugMode) { Logger.analyticsSdk('appVersion 格式不正确,应为 x.y.z,当前值:$appVersion,已忽略', level: LogLevel.error); } else { _appVersion = kDefaultAppVersion; } } if (device != null) _device = device; if (deviceId != null) _deviceId = deviceId; if (deviceBrand != null) _deviceBrand = deviceBrand; if (deviceModel != null) _deviceModel = deviceModel; if (userAgent != null) _userAgent = userAgent; if (systemName != null) _systemName = systemName; if (systemVersion != null) _systemVersion = systemVersion; if (environmentFields != null) { _environmentFields = { ..._defaultEnvironmentFields(), ..._normalizeEnvironmentFields(environmentFields), }; } } /// SDK 内部调用:重置自动生成的设备指纹状态。 static void resetGeneratedDeviceFingerprint() { _deviceFingerprint = null; _fingerprintVersion = DeviceFingerprintUtil.kVersion; } /// SDK 内部调用:写入已生成的设备指纹。 static void configureGeneratedDeviceFingerprint(String deviceFingerprint) { final normalized = deviceFingerprint.trim(); _deviceFingerprint = normalized.isEmpty ? null : normalized; _fingerprintVersion = DeviceFingerprintUtil.kVersion; } /// 更新用户 ID(登录或切换账号时调用) static void setUid(String newUid) { _uid = newUid; } /// 更新渠道(渠道变更时调用) static void setChannel(String channel) { _channel = channel; } /// 增量更新系统、网络、硬件和浏览器环境快照字段。 /// /// 用于网络、电量、充电状态等运行期可能变化的字段刷新。 static void updateEnvironmentFields(Map fields) { if (fields.isEmpty) return; _environmentFields = { ..._environmentFields, ..._normalizeEnvironmentFields(fields), }; } /// 配置客户端事件签名密钥。 /// /// 密钥来自加密配置解密后的 `signKey`,不写入事件、payload、日志或缓存。 static void configureEventSignature({ String? signKey, String? version, }) { if (signKey != null) { _eventSignatureHmacKey = signKey.trim(); } final normalizedVersion = version?.trim(); if (normalizedVersion != null && normalizedVersion.isNotEmpty) { _eventSignatureVersion = normalizedVersion; } } /// 重置所有公共字段为 null(SDK 重新初始化时使用) /// /// 与 [configure] 不同,此方法会将所有字段清为 null, /// 而 configure 中传 null 表示"不修改"。 static void reset() { _appId = null; _channel = null; _uid = null; _appVersion = null; _device = null; _deviceId = null; _deviceBrand = null; _deviceModel = null; _userAgent = null; _systemName = null; _systemVersion = null; _deviceFingerprint = null; _fingerprintVersion = DeviceFingerprintUtil.kVersion; _environmentFields = _defaultEnvironmentFields(); _eventSignatureHmacKey = ''; _eventSignatureVersion = kEventSignatureVersion; sidProvider = null; } // ──────── Getters(调试 / 外部复用) ──────────────────────────────────── static String? get appId => _appId; static String? get channel => _channel; static String? get uid => _uid; static String? get appVersion => _appVersion; static String? get deviceId => _deviceId; static String? get deviceBrand => _deviceBrand; static String? get deviceModel => _deviceModel; static String get deviceFingerprint => _deviceFingerprint ?? ''; static String get fingerprintVersion => _fingerprintVersion; static Map get environmentFields => Map.from(_environmentFields); /// 获取设备公共字段(供产品方在服务端上报事件时复用) /// /// 返回的 Map 包含:sid、device、device_id、device_brand、device_model、 /// user_agent、system_name、system_version、device_fingerprint、fp_version。 /// 未配置的字段返回空字符串。 static Map getDeviceCommonFields() { String sid = ''; try { sid = sidProvider?.call() ?? ''; } catch (_) { // 未初始化或 SID 获取异常时保持为空,确保该读取 API 不抛异常。 } return { 'sid': sid, 'device': _device ?? '', 'device_id': _deviceId ?? '', 'device_brand': _deviceBrand ?? '', 'device_model': _deviceModel ?? '', 'user_agent': _userAgent ?? '', 'system_name': _systemName ?? '', 'system_version': _systemVersion ?? '', 'device_fingerprint': _deviceFingerprint ?? '', 'fp_version': _fingerprintVersion, }; } /// 获取系统、网络、硬件和浏览器环境快照字段。 /// /// 仅供 SDK 内部构建 `sdk_init` 启动快照和少量汇总事件使用,普通事件公共字段 /// 不再重复携带这些环境快照。 static Map getEnvironmentSnapshotFields() { return Map.from(_environmentFields); } /// 给 SDK 客户端事件补充外层签名字段。 /// /// 历史版本曾把轻量环境签名写入 payload;这里会清理这些保留字段, /// 避免同一事件同时出现新旧两套签名。 static Map attachDeviceEnvIfNeeded( Map event, ) { final eventName = event['event']?.toString() ?? ''; final isServerOnlyEvent = _serverOnlyEvents.contains(eventName); final result = Map.from(event); final existingPayload = result['payload']; final existingDeviceEnv = result.remove('device_env'); if (existingPayload is Map || existingPayload == null) { final payload = { if (existingPayload is Map) ...Map.from(existingPayload), if (existingDeviceEnv is Map) ...Map.from(existingDeviceEnv), }; payload.removeWhere( (key, _) => _reservedPayloadSignatureFields.contains(key)); result['payload'] = payload; } if (!isServerOnlyEvent && _eventSignatureHmacKey.isNotEmpty) { final eventForSignature = Map.from(result); result['event_sig_version'] = _eventSignatureVersion; result['event_sig'] = _eventSignature(eventForSignature); } return result; } /// 生成公共字段 Map /// /// 包含所有客户端事件上报所需的公共字段: /// event、channel、app_id、uid、sid、client_ts、 /// device、device_id、device_brand、device_model、 /// user_agent、system_name、system_version、 /// device_fingerprint、fp_version、sdk_version、app_version。 /// /// 本方法永远不会抛出异常。 static Map generateCommonFields(String event) { final now = DateTime.now(); final clientTs = now.millisecondsSinceEpoch ~/ 1000; String sid; try { sid = sidProvider?.call() ?? 'fallback_${now.millisecondsSinceEpoch}'; } catch (_) { sid = 'fallback_${now.millisecondsSinceEpoch}'; } return { 'event': event, 'channel': _channel ?? '', 'app_id': _appId ?? '', 'uid': _uid ?? '', 'sid': sid, 'client_ts': clientTs, 'device': _device ?? '', 'device_id': _deviceId ?? '', 'device_brand': _deviceBrand ?? '', 'device_model': _deviceModel ?? '', 'user_agent': _userAgent ?? '', 'system_name': _systemName ?? '', 'system_version': _systemVersion ?? '', 'device_fingerprint': _deviceFingerprint ?? '', 'fp_version': _fingerprintVersion, 'sdk_version': kSdkVersion, 'app_version': _appVersion ?? kDefaultAppVersion, }; } /// 根据字段列表生成 event_id /// /// 计算规则: /// 1. 对每个字段值单独做 MD5 /// 2. 将所有 MD5 用 `|` 拼接 /// 3. 对拼接结果再做一次 MD5,作为最终 event_id /// /// **注意**:`device_fingerprint` 不参与 event_id 计算。 /// 本方法永远不会抛出异常,失败时返回基于时间戳的备选 ID。 static String generateEventId(List fields) { try { final fieldDigests = []; for (final value in fields) { final v = value; final bytes = utf8.encode(v); final digest = md5.convert(bytes); fieldDigests.add(digest.toString()); } final joined = fieldDigests.join('|'); final finalDigest = md5.convert(utf8.encode(joined)); return finalDigest.toString(); } catch (e) { try { final content = fields.join('|'); return 'h${(content.hashCode & 0x7FFFFFFF).toRadixString(16)}'; } catch (_) { return 't${DateTime.now().millisecondsSinceEpoch}'; } } } static Map _defaultEnvironmentFields() => { 'language': '', 'timezone': '', 'package_name': '', 'screen_width': -1, 'screen_height': -1, 'screen_dpi': -1, 'device_pixel_ratio': -1, 'orientation': '', 'screen_brightness': -1, 'volume': -1, 'is_screen_on': -1, 'network_type': 'unknown', 'battery_level': -1, 'is_charging': -1, 'battery_state': 'unknown', 'cpu_cores': -1, 'device_memory_gb': -1, 'browser_name': '', 'browser_version': '', 'cookie_enabled': -1, 'local_storage_enabled': -1, 'touch_support': -1, 'max_touch_points': -1, 'color_depth': -1, 'is_pwa': -1, }; static Map _normalizeEnvironmentFields( Map fields, ) { final normalized = Map.from(fields); if (normalized.containsKey('device_pixel_ratio')) { normalized['device_pixel_ratio'] = _normalizeDevicePixelRatio(normalized['device_pixel_ratio']); } return normalized; } static int _normalizeDevicePixelRatio(dynamic value) { if (value == null) return -1; final ratio = value is num ? value.toDouble() : double.tryParse(value.toString()); if (ratio == null || !ratio.isFinite) return -1; if (ratio > 10) return ratio.round(); return (ratio * 100).round(); } static const Set _serverOnlyEvents = { 'user_register', 'user_login', 'realtime_online', 'order_created', 'order_paid', 'coin_consume', 'video_status_change', }; static const Set _reservedPayloadSignatureFields = { 'sdk_type', 'client_type', 'id_type', 'event_sig', 'event_sig_version', 'security_key_version', 'sign_algorithm', 'sign_ts', }; static String _eventSignature( Map event, ) { try { if (_eventSignatureHmacKey.isEmpty) return ''; final factors = [ event['event']?.toString() ?? '', event['event_id']?.toString() ?? '', event['app_id']?.toString() ?? '', event['client_ts']?.toString() ?? '', event['device_fingerprint']?.toString() ?? '', ]; final signatureInput = factors.join('|'); final signature = Hmac(sha256, utf8.encode(_eventSignatureHmacKey)) .convert(utf8.encode(signatureInput)) .toString(); Logger.analyticsSdk( 'event_sig input="$signatureInput" ' 'event_sig=$signature ' 'event_sig_version=$_eventSignatureVersion', ); return signature; } catch (_) { return ''; } } /// 从公共字段 Map 和业务特有字段列表生成 event_id。 /// /// 本方法永远不会抛出异常。 static String generateEventIdFromCommonFields( Map commonFields, List extraFields) { return generateEventId([ commonFields['event'] as String? ?? '', commonFields['app_id'] as String? ?? '', commonFields['channel'] as String? ?? '', commonFields['uid'] as String? ?? '', commonFields['device'] as String? ?? '', commonFields['device_id'] as String? ?? '', commonFields['device_brand'] as String? ?? '', commonFields['device_model'] as String? ?? '', (commonFields['client_ts'] ?? '').toString(), commonFields['sid'] as String? ?? '', ...extraFields, ]); } }