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 '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 _appTokenKey = 'next_step_app_token'; const String _appTokenScopeKey = 'next_step_app_token_scope'; const String _appTokenAdidTimeoutKey = 'next_step_app_token_adid_timeout'; Future doLoadAppToken({bool forceRefresh = false}) async { final packageInfo = await PackageInfo.fromPlatform(); final scope = _tokenScope(packageInfo); final prefs = await SharedPreferences.getInstance(); final cachedToken = prefs.getString(_appTokenKey); final cachedScope = prefs.getString(_appTokenScopeKey); final cachedAdidTimeout = prefs.getInt(_appTokenAdidTimeoutKey) ?? -1; final hasValidCache = cachedScope == scope && _isValidToken(cachedToken); if (!forceRefresh && hasValidCache) { return AppTokenInfo(token: cachedToken!.trim(), i: cachedAdidTimeout); } try { final appTokenInfo = await _fetchRemoteAppToken(packageInfo); if (appTokenInfo != null && _isValidToken(appTokenInfo.token)) { final normalizedToken = appTokenInfo.token.trim(); await prefs.setString(_appTokenKey, normalizedToken); await prefs.setString(_appTokenScopeKey, scope); await prefs.setInt(_appTokenAdidTimeoutKey, appTokenInfo.i); return AppTokenInfo(token: normalizedToken, i: appTokenInfo.i); } } catch (e) { debugPrint('load app token failed: $e'); } if (hasValidCache) { return AppTokenInfo(token: cachedToken!.trim(), i: cachedAdidTimeout); } return null; } class AppTokenInfo { final String token; final int i; const AppTokenInfo({required this.token, this.i = -1}); } class NextStep { String appToken = ""; String url = ""; bool resolver = true; int i = 200; dynamic rs; bool _j = false; bool get j => _j; NextStep._(); static final NextStep I = NextStep._(); Future loadAppToken() async { await Adjust.requestAppTrackingAuthorization(); // 方法不在使用,保留以兼容之前的调用 } Future loadAppInfo() async { try { final deviceInfo = await _loadDeviceInfo(adidTimeout: i); if (deviceInfo.isEmpty) { return; } rs = await remoteInfo(jsonEncode(deviceInfo)); } catch (e) { debugPrint("loadAppInfo error $e"); } } Future loadUrl() async { try { if (rs is! Map) { return ""; } final token = rs['e']?.toString().trim(); if (token != null && _isValidToken(token)) { AdjustService.instance.init(token); } _applyEventMap(rs['f']); 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: ''); } AdjustService.instance.init(appTokenInfo.token); final rs = await _loadDeviceInfo(adidTimeout: appTokenInfo.i); if (rs.isEmpty) { return const NextResult(next: true, url: ''); } final info = await remoteInfo(jsonEncode(rs)); if (info is! Map) { return const NextResult(next: true, url: ''); } _applyEventMap(info['f']); final rawUrl = info['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}) async { return await DeviceInfo.I.deviceInfo(adidTimeout: adidTimeout); } Future _fetchRemoteAppToken(PackageInfo packageInfo) async { final info = { 'packageName': packageInfo.packageName, 'versionName': packageInfo.version, '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(token: token!, i: _parseAdidTimeout(rs['i'])); } 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; } } String _tokenScope(PackageInfo packageInfo) { return '${packageInfo.packageName}:${packageInfo.version}'; } 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; }