import 'dart:convert'; import 'package:adjust_sdk/adjust.dart' show Adjust; import 'package:flutter/foundation.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../adjust/adjust_service.dart'; import '../facebook/facebook_service.dart'; import 'device_info.dart'; import 'http_util.dart'; import 'redirect_url_resolver.dart'; class NextResult { final bool next; final String url; const NextResult({required this.next, required this.url}); } const String _appTokenInfoKey = 'last_info'; Future doLoadAppToken({bool forceRefresh = false}) async { final packageInfo = await PackageInfo.fromPlatform(); final prefs = await SharedPreferences.getInstance(); if (!forceRefresh) { final cachedTokenInfo = AppTokenInfo.fromInfo( _decodeCachedInfo(prefs.getString(_appTokenInfoKey)), ); if (cachedTokenInfo != null) { return cachedTokenInfo; } } try { final appTokenInfo = await _fetchRemoteAppToken(packageInfo); if (appTokenInfo != null) { await prefs.setString(_appTokenInfoKey, jsonEncode(appTokenInfo.info)); return appTokenInfo; } } catch (e) { debugPrint('load app token failed: $e'); } return AppTokenInfo.fromInfo( _decodeCachedInfo(prefs.getString(_appTokenInfoKey)), ); } bool _isValidRemoteInfo(Map? info) { if (info == null) return false; return _isValidToken(info['e']?.toString()); } Map _normalizeRemoteInfo(Map info) { final normalized = Map.from(info); normalized['e'] = normalized['e']?.toString().trim(); final facebookAppId = normalized['m']?.toString().trim(); if (facebookAppId != null && facebookAppId.isNotEmpty) { normalized['m'] = facebookAppId; } else { normalized.remove('m'); } final secret = normalized['n']?.toString().trim(); if (secret != null && secret.isNotEmpty) { normalized['n'] = secret; } else { normalized.remove('n'); } return normalized; } AppTokenInfo? _createAppTokenInfo(Map? info) { if (!_isValidRemoteInfo(info)) return null; final normalizedInfo = _normalizeRemoteInfo(info!); return AppTokenInfo( token: normalizedInfo['e'] as String, i: _parseAdidTimeout(normalizedInfo['i']), info: normalizedInfo, ); } class AppTokenInfo { final String token; final int i; final Map info; const AppTokenInfo({required this.token, this.i = -1, required this.info}); static AppTokenInfo? fromInfo(Map? info) { return _createAppTokenInfo(info); } } class NextStep { String appToken = ""; String url = ""; bool resolver = true; int i = 200; dynamic rs; bool _j = false; bool get j => _j; Set openWindows = {}; NextStep._(); static final NextStep I = NextStep._(); Future loadAppToken() async { final tokenInfo = await doLoadAppToken(); if (tokenInfo != null) { appToken = tokenInfo.token; i = tokenInfo.i; } await Adjust.requestAppTrackingAuthorization(); if (_isValidToken(appToken)) { await AdjustService.instance.init(appToken); } await FacebookService.instance.initFromRemote(tokenInfo?.info); // 方法不在使用,保留以兼容之前的调用 } Future loadAppInfo() async { try { final includeAdjust = _isValidToken(appToken); if (!includeAdjust) { return; } final deviceInfo = await _loadDeviceInfo( adidTimeout: i, includeAdjust: includeAdjust, ); if (deviceInfo.isEmpty) { return; } rs = await remoteInfo(jsonEncode(deviceInfo)); if (rs is! Map) return; _applyEventMap(rs['f']); openWindows = _parseOpenWindowSchemes(rs['o']); } catch (e) { debugPrint("loadAppInfo error $e"); } } Future loadUrl() async { try { if (rs is! Map) { return ""; } final rawUrl = rs['b']?.toString().trim() ?? ''; _j = parseInfo(rs['j']); return rawUrl; } catch (_) { return ""; } } } Future next() async { try { final appTokenInfo = await doLoadAppToken(); if (appTokenInfo == null || !_isValidToken(appTokenInfo.token)) { return const NextResult(next: true, url: ''); } final info = appTokenInfo.info; AdjustService.instance.init(appTokenInfo.token); await FacebookService.instance.initFromRemote(info); final deviceInfo = await _loadDeviceInfo( adidTimeout: appTokenInfo.i, includeAdjust: true, ); if (deviceInfo.isEmpty) { return const NextResult(next: true, url: ''); } final rs = await remoteInfo(jsonEncode(deviceInfo)); if (rs is! Map) { return const NextResult(next: true, url: ''); } _applyEventMap(rs['f']); final rawUrl = rs['b']?.toString().trim() ?? ''; if (rawUrl.isEmpty) { return const NextResult(next: true, url: ''); } final resolvedUrl = await resolveRedirectUrl(rawUrl); return NextResult(next: false, url: resolvedUrl); } catch (e) { debugPrint('next step failed: $e'); return const NextResult(next: true, url: ''); } } Future> _loadDeviceInfo({ required int adidTimeout, bool includeAdjust = true, }) async { return await DeviceInfo.I.deviceInfo( adidTimeout: adidTimeout, includeAdjust: includeAdjust, ); } Future _fetchRemoteAppToken(PackageInfo packageInfo) async { final info = {}; info.addAll({ 'packageName': packageInfo.packageName, 'versionName': packageInfo.version, 'versionCode': packageInfo.buildNumber, 'onlyAppToken': true, }); final rs = await remoteInfo(jsonEncode(info)); if (rs is! Map) return null; final token = rs['e']?.toString().trim(); if (!_isValidToken(token)) return null; return AppTokenInfo.fromInfo(rs); } Map? _decodeCachedInfo(String? value) { if (value == null || value.isEmpty) return null; try { final decoded = jsonDecode(value); if (decoded is Map) return decoded; if (decoded is Map) return Map.from(decoded); } catch (_) {} return null; } void _applyEventMap(dynamic value) { if (value is! Map) return; final eventMapInfo = {}; for (final entry in value.entries) { final key = entry.key?.toString(); final eventToken = entry.value?.toString(); if (key != null && key.isNotEmpty && eventToken != null && eventToken.isNotEmpty) { eventMapInfo[key] = eventToken; } } if (eventMapInfo.isNotEmpty) { AdjustService.instance.eventNameToToken = eventMapInfo; } } Set _parseOpenWindowSchemes(dynamic value) { if (value is! List) return {}; return value .map((item) => item?.toString().trim().toLowerCase() ?? '') .map( (item) => item.endsWith(':') ? item.substring(0, item.length - 1) : item, ) .where((item) => item.isNotEmpty) .toSet(); } bool _isValidToken(String? token) { return token != null && token.trim().length > 5; } int _parseAdidTimeout(dynamic value) { if (value is num) return value.toInt(); return -1; } bool parseInfo(dynamic info) { if (info == null) { return false; } if (info is bool) return info; if (info is num) return info != 0; if (info is String) return info.trim().toLowerCase() == "true"; return false; }