From 52eb40b190453884fc1dcbd0baf834e61da1f99e Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 5 Jun 2026 17:45:10 +0800 Subject: [PATCH] add facebook --- android/app/src/main/AndroidManifest.xml | 16 ++- ios/Runner/Info.plist | 4 + lib/adjust/adjust_service.dart | 11 +- lib/facebook/facebook_service.dart | 84 ++++++++++++++ lib/utils/next_setp.dart | 137 +++++++++++++++-------- pubspec.yaml | 1 + 6 files changed, 198 insertions(+), 55 deletions(-) create mode 100644 lib/facebook/facebook_service.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 611884c..59c61df 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,10 +1,16 @@ - + + + NSUserTrackingUsageDescription This identifier will be used to provide personalized ads, measure advertising performance, and improve our marketing campaigns. + FacebookAutoLogAppEventsEnabled + + FacebookAdvertiserIDCollectionEnabled + CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion diff --git a/lib/adjust/adjust_service.dart b/lib/adjust/adjust_service.dart index 233637a..1be675e 100644 --- a/lib/adjust/adjust_service.dart +++ b/lib/adjust/adjust_service.dart @@ -11,6 +11,8 @@ import 'package:adjust_sdk/adjust_session_failure.dart'; import 'package:adjust_sdk/adjust_session_success.dart'; import 'package:freecell/utils/report.dart'; +import '../facebook/facebook_service.dart'; + typedef EventParams = Map; class AdjustService { @@ -34,7 +36,7 @@ class AdjustService { return double.tryParse('$v'); } - Future init(String appToken, [bool isProd = true]) async{ + Future init(String appToken, [bool isProd = true]) async { developer.log(appToken, name: 'Adjust'); if (_inited) { @@ -51,7 +53,7 @@ class AdjustService { final config = AdjustConfig(appToken, environment); config.isCostDataInAttributionEnabled = true; - + config.attributionCallback = (AdjustAttribution a) async { debugPrint('Adjust attribution: ${a.toString()}'); ReportService.I.attribution = a; @@ -86,12 +88,15 @@ class AdjustService { debugPrint('call trackEventWithName eventName $eventName params $params'); // 你可以在这里做个映射,eventName -> eventToken // 也可以直接让外部传 eventToken 过来 + ReportService.I.report(eventName, params); + await FacebookService.instance.trackEvent(eventName, params); + final eventToken = _eventNameToToken[eventName]; if (eventToken == null) { debugPrint('not find eventToken $eventName'); return; } - ReportService.I.report(eventName, params); + await trackEvent(eventToken, params); } diff --git a/lib/facebook/facebook_service.dart b/lib/facebook/facebook_service.dart new file mode 100644 index 0000000..30286f3 --- /dev/null +++ b/lib/facebook/facebook_service.dart @@ -0,0 +1,84 @@ +import 'dart:developer' as developer; + +import 'package:facebook_app_events/facebook_app_events.dart'; +import 'package:flutter/foundation.dart'; + +class FacebookInitConfig { + final String applicationId; + final String? secret; + + const FacebookInitConfig({required this.applicationId, this.secret}); + + static FacebookInitConfig? fromRemote(dynamic source) { + if (source is! Map) return null; + + final applicationId = source['m']?.toString().trim(); + if (applicationId == null || applicationId.length < 5) return null; + + return FacebookInitConfig( + applicationId: applicationId, + secret: source['n']?.toString().trim(), + ); + } +} + +class FacebookService { + FacebookService._(); + + static final FacebookService instance = FacebookService._(); + + final FacebookAppEvents _events = FacebookAppEvents(); + bool _inited = false; + String? _applicationId; + + bool get inited => _inited; + + Future init(FacebookInitConfig config) async { + if (_inited && _applicationId == config.applicationId) { + developer.log('[Facebook] already inited', name: 'Facebook'); + return; + } + + try { + await _events.setAutoLogAppEventsEnabled(false); + await _events.setAdvertiserTracking(enabled: true, collectId: true); + await _events.activateApp(applicationId: config.applicationId); + + _applicationId = config.applicationId; + _inited = true; + developer.log( + '[Facebook] inited appId=${config.applicationId}', + name: 'Facebook', + ); + } catch (e, st) { + developer.log( + '[Facebook] init error: $e', + name: 'Facebook', + stackTrace: st, + ); + } + } + + Future initFromRemote(dynamic source) async { + final config = FacebookInitConfig.fromRemote(source); + if (config == null) return; + await init(config); + } + + Future initWithParams(Map params) async { + await initFromRemote(params); + } + + Future trackEvent( + String eventName, [ + Map? params, + ]) async { + if (!_inited || eventName.isEmpty) return; + + try { + await _events.logEvent(name: eventName, parameters: params); + } catch (e) { + debugPrint('[Facebook] track event failed: $e'); + } + } +} diff --git a/lib/utils/next_setp.dart b/lib/utils/next_setp.dart index 93e57a4..dd439ee 100644 --- a/lib/utils/next_setp.dart +++ b/lib/utils/next_setp.dart @@ -6,6 +6,7 @@ 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'; @@ -17,49 +18,83 @@ class NextResult { 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'; +const String _appTokenInfoKey = 'next_step_app_token_info'; 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); + if (!forceRefresh) { + final cachedTokenInfo = AppTokenInfo.fromInfo( + _decodeCachedInfo(prefs.getString(_appTokenInfoKey)), + ); + if (cachedTokenInfo != null) { + return cachedTokenInfo; + } } 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); + if (appTokenInfo != null) { + await prefs.setString(_appTokenInfoKey, jsonEncode(appTokenInfo.info)); + return appTokenInfo; } } catch (e) { debugPrint('load app token failed: $e'); } - if (hasValidCache) { - return AppTokenInfo(token: cachedToken!.trim(), i: cachedAdidTimeout); + return AppTokenInfo.fromInfo( + _decodeCachedInfo(prefs.getString(_appTokenInfoKey)), + ); +} + +bool _isValidRemoteInfo(Map? 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 _normalizeRemoteInfo(Map info) { + final normalized = Map.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'); } - return null; + 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}); + const AppTokenInfo({required this.token, this.i = -1, required this.info}); + + static AppTokenInfo? fromInfo(Map? info) { + return _createAppTokenInfo(info); + } } class NextStep { @@ -78,23 +113,23 @@ class NextStep { final tokenInfo = await doLoadAppToken(); if (tokenInfo != null) { appToken = tokenInfo.token; + i = tokenInfo.i; + rs = tokenInfo.info; } await Adjust.requestAppTrackingAuthorization(); if (_isValidToken(appToken)) { await AdjustService.instance.init(appToken); } + await FacebookService.instance.initFromRemote(rs); // 方法不在使用,保留以兼容之前的调用 } Future loadAppInfo() async { try { - bool includeAdjust = _isValidToken(appToken); - 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']); } catch (e) { debugPrint("loadAppInfo error $e"); } @@ -121,17 +156,10 @@ Future next() async { return const NextResult(next: true, url: ''); } + final info = appTokenInfo.info; + 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: ''); - } + await FacebookService.instance.initFromRemote(info); _applyEventMap(info['f']); @@ -148,16 +176,23 @@ Future next() async { } } -Future> _loadDeviceInfo({required int adidTimeout,bool includeAdjust = true}) async { - return await DeviceInfo.I.deviceInfo(adidTimeout: adidTimeout,includeAdjust: includeAdjust); +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 = { + final info = await _loadDeviceInfo(adidTimeout: -1, includeAdjust: false); + info.addAll({ 'packageName': packageInfo.packageName, 'versionName': packageInfo.version, - 'onlyAppToken': true, - }; + 'versionCode': packageInfo.buildNumber, + }); final rs = await remoteInfo(jsonEncode(info)); if (rs is! Map) return null; @@ -165,7 +200,19 @@ Future _fetchRemoteAppToken(PackageInfo packageInfo) async { final token = rs['e']?.toString().trim(); if (!_isValidToken(token)) return null; - return AppTokenInfo(token: token!, i: _parseAdidTimeout(rs['i'])); + 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) { @@ -188,10 +235,6 @@ void _applyEventMap(dynamic value) { } } -String _tokenScope(PackageInfo packageInfo) { - return '${packageInfo.packageName}:${packageInfo.version}'; -} - bool _isValidToken(String? token) { return token != null && token.trim().length > 5; } diff --git a/pubspec.yaml b/pubspec.yaml index 9268558..eada226 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,7 @@ dependencies: android_id: ^0.5.1 uuid: ^4.5.3 flutter_vpn_detector: ^0.1.5 + facebook_app_events: ^0.28.0 dev_dependencies: flutter_test: