import 'dart:async'; import 'dart:convert'; import 'package:adjust_sdk/adjust_attribution.dart'; import 'package:adjust_sdk/adjust.dart'; import 'package:flutter/foundation.dart'; import 'package:play_install_referrer/play_install_referrer.dart'; /// 归因信息服务(优先从 Adjust 获取,fallback 使用 Play Install Referrer) class ReferrerService { ReferrerService._(); static String? _cachedReferrer; static String _referrerSource = 'gg'; static final Completer _completer = Completer(); /// attribution 回调的 Completer,与 getAttributionWithTimeout 竞速,谁先返回用谁 static final Completer _attributionCallbackCompleter = Completer(); static const int _adjustTimeoutMs = 15000; /// 由 Adjust attributionCallback 调用,首次安装时归因往往通过此回调返回(晚于 getAttributionWithTimeout) static void receiveAttributionFromCallback(AdjustAttribution attribution) { if (!_attributionCallbackCompleter.isCompleted) { _attributionCallbackCompleter.complete(attribution); } } /// 归因来源:Adjust 为 android_adjust,Play Install Referrer 为 gg static String get referrerSource => _referrerSource; /// JSON 可编码的 costAmount:保留字段,非有限数字用字符串避免 encode 抛错 static Object? _jsonEncodableCostAmount(num? v) { if (v == null) return null; if (v is double && !v.isFinite) return v.toString(); return v; } /// 将完整 AdjustAttribution 序列化为 JSON(供 digest / Base64),不省略任何键 static String _attributionToDigest(AdjustAttribution attr) { final map = { 'trackerToken': attr.trackerToken, 'trackerName': attr.trackerName, 'network': attr.network, 'campaign': attr.campaign, 'adgroup': attr.adgroup, 'creative': attr.creative, 'clickLabel': attr.clickLabel, 'costType': attr.costType, 'costAmount': _jsonEncodableCostAmount(attr.costAmount), 'costCurrency': attr.costCurrency, 'jsonResponse': attr.jsonResponse, 'fbInstallReferrer': attr.fbInstallReferrer, }; try { return jsonEncode(map); } catch (_) { return ''; } } /// 获取 referrer/digest,优先从 Adjust 归因获取,fallback 使用 Play Install Referrer /// 同时监听 attributionCallback,首次安装时归因往往通过回调返回(可能晚于 getAttributionWithTimeout) static Future getReferrer() async { if (_cachedReferrer != null) return _cachedReferrer; if (_completer.isCompleted) return _completer.future; var digest = ''; try { // 竞速:getAttributionWithTimeout 与 attribution 回调,谁先返回用谁 final attribution = await Future.any([ (() async { try { return await Adjust.getAttributionWithTimeout(_adjustTimeoutMs); } catch (_) { return null; } })(), _attributionCallbackCompleter.future, ]); if (attribution != null) { final raw = _attributionToDigest(attribution); if (raw.isNotEmpty) { _referrerSource = 'android_adjust'; digest = base64Encode(utf8.encode(raw)); } } } catch (_) { digest = ''; } if (digest.isEmpty) { _referrerSource = 'gg'; if (defaultTargetPlatform == TargetPlatform.android) { try { final details = await PlayInstallReferrer.installReferrer; digest = details.installReferrer ?? ''; } catch (_) { digest = ''; } } } _cachedReferrer = digest; if (!_completer.isCompleted) _completer.complete(_cachedReferrer); return _cachedReferrer; } /// 初始化并缓存 referrer(建议在 app 启动时调用) static Future init() async { await getReferrer(); } /// 仅 Adjust 归因 digest(Base64 JSON),用于 /v1/user/referrer accolade=android_adjust static Future getAdjustReferrerDigest() async { try { final attr = await Adjust.getAttribution(); final raw = _attributionToDigest(attr); if (raw.isEmpty) return ''; return base64Encode(utf8.encode(raw)); } catch (_) { return ''; } } /// 仅 Google Play Install Referrer 字符串,用于 /v1/user/referrer accolade=gg static Future getGgReferrerDigest() async { if (defaultTargetPlatform != TargetPlatform.android) return ''; try { final details = await PlayInstallReferrer.installReferrer; return details.installReferrer ?? ''; } catch (_) { return ''; } } }