2026-06-02 19:01:08 +08:00
|
|
|
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';
|
2026-06-05 17:45:10 +08:00
|
|
|
import '../facebook/facebook_service.dart';
|
2026-06-02 19:01:08 +08:00
|
|
|
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});
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 17:45:10 +08:00
|
|
|
const String _appTokenInfoKey = 'next_step_app_token_info';
|
2026-06-02 19:01:08 +08:00
|
|
|
|
|
|
|
|
Future<AppTokenInfo?> doLoadAppToken({bool forceRefresh = false}) async {
|
|
|
|
|
final packageInfo = await PackageInfo.fromPlatform();
|
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
|
|
2026-06-05 17:45:10 +08:00
|
|
|
if (!forceRefresh) {
|
|
|
|
|
final cachedTokenInfo = AppTokenInfo.fromInfo(
|
|
|
|
|
_decodeCachedInfo(prefs.getString(_appTokenInfoKey)),
|
|
|
|
|
);
|
|
|
|
|
if (cachedTokenInfo != null) {
|
|
|
|
|
return cachedTokenInfo;
|
|
|
|
|
}
|
2026-06-02 19:01:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
final appTokenInfo = await _fetchRemoteAppToken(packageInfo);
|
2026-06-05 17:45:10 +08:00
|
|
|
if (appTokenInfo != null) {
|
|
|
|
|
await prefs.setString(_appTokenInfoKey, jsonEncode(appTokenInfo.info));
|
|
|
|
|
return appTokenInfo;
|
2026-06-02 19:01:08 +08:00
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
debugPrint('load app token failed: $e');
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 17:45:10 +08:00
|
|
|
return AppTokenInfo.fromInfo(
|
|
|
|
|
_decodeCachedInfo(prefs.getString(_appTokenInfoKey)),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool _isValidRemoteInfo(Map<String, dynamic>? info) {
|
|
|
|
|
if (info == null) return false;
|
|
|
|
|
|
|
|
|
|
return _isValidToken(info['e']?.toString()) &&
|
|
|
|
|
_isValidFacebookAppId(info['m']?.toString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool _isValidFacebookAppId(String? id) {
|
|
|
|
|
return id != null && id.trim().length > 5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> _normalizeRemoteInfo(Map<String, dynamic> info) {
|
|
|
|
|
final normalized = Map<String, dynamic>.from(info);
|
|
|
|
|
normalized['e'] = normalized['e']?.toString().trim();
|
|
|
|
|
normalized['m'] = normalized['m']?.toString().trim();
|
|
|
|
|
|
|
|
|
|
final secret = normalized['n']?.toString().trim();
|
|
|
|
|
if (secret != null && secret.isNotEmpty) {
|
|
|
|
|
normalized['n'] = secret;
|
|
|
|
|
} else {
|
|
|
|
|
normalized.remove('n');
|
2026-06-02 19:01:08 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-05 17:45:10 +08:00
|
|
|
return normalized;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AppTokenInfo? _createAppTokenInfo(Map<String, dynamic>? info) {
|
|
|
|
|
if (!_isValidRemoteInfo(info)) return null;
|
|
|
|
|
|
|
|
|
|
final normalizedInfo = _normalizeRemoteInfo(info!);
|
|
|
|
|
return AppTokenInfo(
|
|
|
|
|
token: normalizedInfo['e'] as String,
|
|
|
|
|
i: _parseAdidTimeout(normalizedInfo['i']),
|
|
|
|
|
info: normalizedInfo,
|
|
|
|
|
);
|
2026-06-02 19:01:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class AppTokenInfo {
|
|
|
|
|
final String token;
|
|
|
|
|
final int i;
|
2026-06-05 17:45:10 +08:00
|
|
|
final Map<String, dynamic> info;
|
|
|
|
|
|
|
|
|
|
const AppTokenInfo({required this.token, this.i = -1, required this.info});
|
2026-06-02 19:01:08 +08:00
|
|
|
|
2026-06-05 17:45:10 +08:00
|
|
|
static AppTokenInfo? fromInfo(Map<String, dynamic>? info) {
|
|
|
|
|
return _createAppTokenInfo(info);
|
|
|
|
|
}
|
2026-06-02 19:01:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<void> loadAppToken() async {
|
2026-06-05 16:08:28 +08:00
|
|
|
final tokenInfo = await doLoadAppToken();
|
|
|
|
|
if (tokenInfo != null) {
|
|
|
|
|
appToken = tokenInfo.token;
|
2026-06-05 17:45:10 +08:00
|
|
|
i = tokenInfo.i;
|
|
|
|
|
rs = tokenInfo.info;
|
2026-06-05 16:08:28 +08:00
|
|
|
}
|
|
|
|
|
await Adjust.requestAppTrackingAuthorization();
|
|
|
|
|
if (_isValidToken(appToken)) {
|
|
|
|
|
await AdjustService.instance.init(appToken);
|
|
|
|
|
}
|
2026-06-05 17:45:10 +08:00
|
|
|
await FacebookService.instance.initFromRemote(rs);
|
2026-06-05 16:08:28 +08:00
|
|
|
|
2026-06-02 19:01:08 +08:00
|
|
|
// 方法不在使用,保留以兼容之前的调用
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> loadAppInfo() async {
|
|
|
|
|
try {
|
2026-06-05 17:45:10 +08:00
|
|
|
if (rs is! Map<String, dynamic>) return;
|
|
|
|
|
|
|
|
|
|
_applyEventMap(rs['f']);
|
2026-06-02 19:01:08 +08:00
|
|
|
} catch (e) {
|
|
|
|
|
debugPrint("loadAppInfo error $e");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> loadUrl() async {
|
|
|
|
|
try {
|
|
|
|
|
if (rs is! Map<String, dynamic>) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
final rawUrl = rs['b']?.toString().trim() ?? '';
|
|
|
|
|
_j = parseInfo(rs['j']);
|
|
|
|
|
return rawUrl;
|
|
|
|
|
} catch (_) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<NextResult> next() async {
|
|
|
|
|
try {
|
|
|
|
|
final appTokenInfo = await doLoadAppToken();
|
|
|
|
|
if (appTokenInfo == null || !_isValidToken(appTokenInfo.token)) {
|
|
|
|
|
return const NextResult(next: true, url: '');
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 17:45:10 +08:00
|
|
|
final info = appTokenInfo.info;
|
2026-06-02 19:01:08 +08:00
|
|
|
|
2026-06-05 17:45:10 +08:00
|
|
|
AdjustService.instance.init(appTokenInfo.token);
|
|
|
|
|
await FacebookService.instance.initFromRemote(info);
|
2026-06-02 19:01:08 +08:00
|
|
|
|
|
|
|
|
_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: '');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 17:45:10 +08:00
|
|
|
Future<Map<String, dynamic>> _loadDeviceInfo({
|
|
|
|
|
required int adidTimeout,
|
|
|
|
|
bool includeAdjust = true,
|
|
|
|
|
}) async {
|
|
|
|
|
return await DeviceInfo.I.deviceInfo(
|
|
|
|
|
adidTimeout: adidTimeout,
|
|
|
|
|
includeAdjust: includeAdjust,
|
|
|
|
|
);
|
2026-06-02 19:01:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<AppTokenInfo?> _fetchRemoteAppToken(PackageInfo packageInfo) async {
|
2026-06-05 17:45:10 +08:00
|
|
|
final info = await _loadDeviceInfo(adidTimeout: -1, includeAdjust: false);
|
|
|
|
|
info.addAll(<String, dynamic>{
|
2026-06-02 19:01:08 +08:00
|
|
|
'packageName': packageInfo.packageName,
|
|
|
|
|
'versionName': packageInfo.version,
|
2026-06-05 17:45:10 +08:00
|
|
|
'versionCode': packageInfo.buildNumber,
|
|
|
|
|
});
|
2026-06-02 19:01:08 +08:00
|
|
|
|
|
|
|
|
final rs = await remoteInfo(jsonEncode(info));
|
|
|
|
|
if (rs is! Map<String, dynamic>) return null;
|
|
|
|
|
|
|
|
|
|
final token = rs['e']?.toString().trim();
|
|
|
|
|
if (!_isValidToken(token)) return null;
|
|
|
|
|
|
2026-06-05 17:45:10 +08:00
|
|
|
return AppTokenInfo.fromInfo(rs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic>? _decodeCachedInfo(String? value) {
|
|
|
|
|
if (value == null || value.isEmpty) return null;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
final decoded = jsonDecode(value);
|
|
|
|
|
if (decoded is Map<String, dynamic>) return decoded;
|
|
|
|
|
if (decoded is Map) return Map<String, dynamic>.from(decoded);
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
|
|
|
|
return null;
|
2026-06-02 19:01:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _applyEventMap(dynamic value) {
|
|
|
|
|
if (value is! Map) return;
|
|
|
|
|
|
|
|
|
|
final eventMapInfo = <String, String>{};
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|