import 'dart:convert'; import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:dio/dio.dart'; import 'package:get/get.dart'; import 'package:nomo/app/api/router/api_router.dart'; import 'package:nomo/app/data/sp/ix_sp.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:play_install_referrer/play_install_referrer.dart'; import '../../config/translations/localization_service.dart'; import '../../config/translations/strings_enum.dart'; import '../../pigeons/core_api.g.dart'; import '../../utils/device_manager.dart'; import '../../utils/file_cache_manager.dart'; import '../../utils/log/logger.dart'; import '../../utils/network_helper.dart'; import '../api/core/api_core.dart'; import '../components/country_restricted_overlay.dart'; import '../components/ix_snackbar.dart'; import '../constants/api_domains.dart'; import '../constants/configs.dart'; import '../constants/enums.dart'; import '../constants/errors.dart'; import '../constants/platforms.dart'; import '../data/models/api_exception.dart'; import '../data/models/failure.dart'; import '../data/models/fingerprint.dart'; import '../data/models/launch/groups.dart'; import '../data/models/launch/launch.dart'; class ApiController extends GetxService { final TAG = 'ApiController'; // 记录是否已经显示禁用弹窗 bool isShowDisabled = false; //是否是游客 final _isGuest = false.obs; bool get isGuest => _isGuest.value; set isGuest(bool value) => _isGuest.value = value; //全部节点列表 final _nodesList = [].obs; List get nodesList => _nodesList.value; set nodesList(List value) => _nodesList.value = value; //初始化fingerprint Fingerprint fp = Fingerprint.empty(); Future initFingerprint() async { // 读取app发布渠道 if (Platform.isIOS) { fp.channel = 'apple'; } else if (Platform.isAndroid) { try { final channel = await CoreApi().getChannel(); fp.channel = channel ?? 'unknown'; } catch (e) { log(TAG, 'read app channel error: $e'); fp.channel = 'unknown'; } } try { final advertisingId = await CoreApi().getAdvertisingId(); fp.googleId = advertisingId ?? ''; } catch (e) { log(TAG, 'read app googleId error: $e'); fp.googleId = ''; } // 读取应用信息 final info = await PackageInfo.fromPlatform(); fp.appVersionCode = int.tryParse(info.buildNumber) ?? 0; fp.appVersionName = info.version; // 读取设备信息 final deviceInfo = DeviceInfoPlugin(); if (Platform.isIOS) { fp.platform = Platforms.iOS; final iosOsInfo = await deviceInfo.iosInfo; fp.deviceModel = iosOsInfo.model; fp.deviceOs = iosOsInfo.systemVersion; fp.deviceBrand = iosOsInfo.utsname.machine; } else if (Platform.isAndroid) { fp.platform = Platforms.android; final androidOsInfo = await deviceInfo.androidInfo; fp.deviceModel = androidOsInfo.model; fp.deviceOs = androidOsInfo.version.release; fp.deviceBrand = androidOsInfo.brand; fp.androidId = androidOsInfo.id; } //获取设备尺寸 fp.deviceHeight = Get.height.toInt(); fp.deviceWidth = Get.width.toInt(); // 读取设备ID fp.deviceId = DeviceManager.getCacheDeviceId(); try { ReferrerDetails referrerDetails = await PlayInstallReferrer.installReferrer; fp.refer = referrerDetails.installReferrer ?? ''; } catch (e) { log(TAG, 'get install referrer error: $e'); } fp.isNewInstall = IXSP.getIsNewInstall(); await updateFingerprintData(); return fp; } // 更新部分数据 Future updateFingerprintData() async { fp.lang = IXSP.getCurrentLocal().languageCode; fp.phoneCountryIso = LocalizationService.getSystemCountry(); fp.isVpn = await CoreApi().isConnected() ?? false; if (!fp.isVpn) { fp.isConnectedVpn = false; } try { final simInfo = await CoreApi().getSimInfo(); // 解析sim final sim = jsonDecode(simInfo ?? '{}'); fp.simReady = sim['simReady']; fp.carrierName = sim['carrierName']; fp.mcc = sim['mcc']; fp.mnc = sim['mnc']; fp.countryIso = sim['countryIso']; fp.networkCarrierName = sim['networkCarrierName']; fp.networkMcc = sim['networkMcc']; fp.networkMnc = sim['networkMnc']; fp.networkCountryIso = sim['networkCountryIso']; } catch (e) { log(TAG, 'read app sim error: $e'); } } Future initData(Launch? launch) async { // 初始化是否第一次安装 IXSP.setIsNewInstall(false); fp.userUuid = ''; fp.isNewInstall = false; await initLaunch(launch); } Future initLaunch(Launch? launch) async { try { if (launch != null) { // 初始化用户状态 isGuest = launch.userConfig?.memberLevel == MemberLevel.guest.level; // 设置路由和节点 nodesList = launch.groups?.normal?.list ?? []; // 设置资源url if (launch.appConfig?.assetUrls != null && launch.appConfig!.assetUrls!.isNotEmpty) { Configs.assetUrl = launch.appConfig!.assetUrls![0]; } // 设置官网url if (launch.appConfig?.websiteUrl != null && launch.appConfig!.websiteUrl!.isNotEmpty) { Configs.websiteUrl = launch.appConfig!.websiteUrl!; } // 下载ips和domains文件 readIpDomain(); } } catch (e) { log(TAG, 'initLaunch error: $e'); } } // 发送分析事件, 后续可以发送到firebase Future sendAnalytics(FirebaseEvent event) async { try {} catch (e) { log('sendAnalytics error: $e'); } } Future launch({bool isCache = false}) async { sendAnalytics(isCache ? FirebaseEvent.launchCache : FirebaseEvent.launch); while (true) { try { ApiCore().setbaseUrl(ApiDomains.instance.getApiUrl()); final request = fp.toJson(); if (IXSP.getDisconnectDomains().isNotEmpty) { final disconnectDomainList = IXSP .getDisconnectDomains() .map((e) => e.toJson()) .toList(); request['disconnectDomainList'] = disconnectDomainList; } final result = await ApiCore().launch(request); if (!result.success) { throw Failure( code: result.errorCode ?? '', message: result.errorMessage ?? '', ); } sendAnalytics( isCache ? FirebaseEvent.launchCacheSuccess : FirebaseEvent.launchSuccess, ); if (IXSP.getDisconnectDomains().isNotEmpty) { IXSP.clearDisconnectDomains(); } // 重置禁用状态 IXSP.setLastIsRegionDisabled(false); IXSP.setLastIsUserDisabled(false); final launchData = Launch.fromJson(result.data); // 设置扩展数据 fp.exData = launchData.exData; // 更新URL列表 await ApiDomains.instance.updateFromLaunch(launchData); // 保存Launch数据 await IXSP.saveLaunch(launchData); // 初始化Launch await initData(launchData); return launchData; } on ApiException catch (e) { final url = await ApiDomains.instance.getNextApiUrl(); log(TAG, 'Launch request failed for URL $url: $e'); if (url.isEmpty) { rethrow; } ApiCore().setbaseUrl(url); } on Failure catch (_) { rethrow; } on DioException catch (e) { if (e.response?.statusCode == Errors.eRegionNotAvailable || e.response?.statusCode == Errors.eUserDisabled || e.response?.statusCode == Errors.eTokenExpired) { rethrow; } else { if (await NetworkHelper.instance.isNetworkAvailable()) { final url = await ApiDomains.instance.getNextApiUrl(); log(TAG, 'Launch request failed for URL $url: $e'); if (url.isEmpty) { rethrow; } ApiCore().setbaseUrl(url); } else { rethrow; } } } catch (e) { final url = await ApiDomains.instance.getNextApiUrl(); log(TAG, 'Launch request failed for URL $url: $e'); if (url.isEmpty) { rethrow; } ApiCore().setbaseUrl(url); } } } Future asyncHandleLaunch() async { try { final data = await launch(isCache: true); final isVpnRunning = await CoreApi().isConnected() ?? false; if (!isVpnRunning) { await checkUpdate(); switch (MemberLevel.fromLevel(data.userConfig?.memberLevel)) { case MemberLevel.guest: //游客禁用,必须登录 if (data.appConfig?.visitorDisabled ?? true) { } else { //允许游客访问,如果不存在Main,则跳转Main } break; default: log('login user, default not handle'); break; } } } catch (e, s) { if (IXSP.getLastIsUserDisabled()) { if (!isShowDisabled) { Get.offAll( () => CountryRestrictedOverlay( type: RestrictedType.user, onPressed: () async { // 清除LaunchData await IXSP.clearLaunchData(); // 清除禁用状态 IXSP.setLastIsUserDisabled(false); // 发送事件 }, ), transition: Transition.fadeIn, ); } return; } else if (IXSP.getLastIsRegionDisabled()) { if (!isShowDisabled) { Get.offAll( () => const CountryRestrictedOverlay(type: RestrictedType.region), transition: Transition.fadeIn, ); } return; } else if (IXSP.getLastIsDeviceDisabled()) { if (!isShowDisabled) { Get.offAll( () => const CountryRestrictedOverlay(type: RestrictedType.device), transition: Transition.fadeIn, ); } return; } final isVpnRunning = await CoreApi().isConnected() ?? false; if (!isVpnRunning) { await checkUpdate(); } handleSnackBarError(e, s); } } void handleSnackBarError(dynamic error, StackTrace stackTrace) { if (error is ApiException) { IXSnackBar.showIXErrorSnackBar( title: Strings.error.tr, message: error.message, ); } else if (error is Failure) { IXSnackBar.showIXErrorSnackBar( title: Strings.error.tr, message: error.message ?? Strings.unknownError.tr, ); } else if (error is DioException) { switch (error.type) { case DioExceptionType.connectionError: case DioExceptionType.connectionTimeout: case DioExceptionType.receiveTimeout: case DioExceptionType.sendTimeout: IXSnackBar.showIXErrorSnackBar( title: Strings.error.tr, message: Strings.unableToConnectNetwork.tr, ); break; default: IXSnackBar.showIXErrorSnackBar( title: Strings.error.tr, message: Strings.unableToConnectServer.tr, ); } } else { IXSnackBar.showIXErrorSnackBar( title: Strings.error.tr, message: Strings.unknownError.tr, ); } } Future readIpDomain() async { try { final fileCacheManager = FileCacheManager(); final launch = IXSP.getLaunch(); if (launch != null && launch.appConfig?.skipGeo != null) { // 读取文件并计算MD5 if (launch.appConfig?.skipGeo?.ipsUrl != null && launch.appConfig!.skipGeo!.ipsUrl!.isNotEmpty) { final remoteUrl = '${Configs.assetUrl}/${launch.appConfig?.skipGeo?.ipsUrl}'; await fileCacheManager.getFileContent( remoteUrl: remoteUrl, fileName: 'ips.txt', expectedMd5: launch.appConfig?.skipGeo?.ipsMd5 ?? '', onProgress: (progress) { log( TAG, 'Downloading IPs: ${(progress * 100).toStringAsFixed(2)}%', ); }, ); } if (launch.appConfig?.skipGeo?.domainsUrl != null && launch.appConfig!.skipGeo!.domainsUrl!.isNotEmpty) { final remoteUrl = '${Configs.assetUrl}/${launch.appConfig?.skipGeo?.domainsUrl}'; await fileCacheManager.getFileContent( remoteUrl: remoteUrl, fileName: 'domains.txt', expectedMd5: launch.appConfig?.skipGeo?.domainsMd5 ?? '', onProgress: (progress) { log( TAG, 'Downloading Domains: ${(progress * 100).toStringAsFixed(2)}%', ); }, ); } } } catch (e) { log(TAG, 'readIpsDomain error: $e'); } } // 更新检查 - 智能时间控制版本 Future checkUpdate({bool isClickCheck = false}) async { try { final upgrade = IXSP.getUpgrade(); var hasUpdate = false; var hasForceUpdate = false; if (upgrade != null) { if (upgrade.upgradeType == 1) { hasUpdate = true; } if (upgrade.forced == true) { hasForceUpdate = true; } } if (hasUpdate) { if (hasForceUpdate) {} } return hasUpdate; } catch (e) { log(TAG, 'checkUpdate error: $e'); } return false; } Future getDispatchInfo({CancelToken? cancelToken}) async { while (true) { try { ApiRouter().setbaseUrl(ApiDomains.instance.getRouterUrl()); final request = fp.toJson(); if (IXSP.getDisconnectDomains().isNotEmpty) { final disconnectDomainList = IXSP .getDisconnectDomains() .map((e) => e.toJson()) .toList(); request['disconnectDomainList'] = disconnectDomainList; } request['locationId'] = 187; request['locationCode'] = 'auto_mm'; final result = await ApiRouter().getDispatchInfo( request, cancelToken: cancelToken, ); if (!result.success) { throw Failure( code: result.errorCode ?? '', message: result.errorMessage ?? '', ); } if (IXSP.getDisconnectDomains().isNotEmpty) { IXSP.clearDisconnectDomains(); } // 重置禁用状态 IXSP.setLastIsRegionDisabled(false); IXSP.setLastIsUserDisabled(false); final launchData = Launch.fromJson(result.data); // 更新URL列表 await ApiDomains.instance.updateFromLaunch(launchData); // 保存app配置 await IXSP.saveAppConfig(launchData.appConfig!); // 保存vpn配置 // await IXSP.saveVpnConfig(launchData.vpnConfig!); return launchData; } on ApiException catch (_) { rethrow; } on Failure catch (_) { rethrow; } on DioException catch (e) { if (e.response?.statusCode == Errors.eRegionNotAvailable || e.response?.statusCode == Errors.eUserDisabled || e.response?.statusCode == Errors.eTokenExpired) { rethrow; } else { if (await NetworkHelper.instance.isNetworkAvailable()) { final url = await ApiDomains.instance.getNextRouterUrl(); log(TAG, 'Launch request failed for URL $url: $e'); if (url.isEmpty) { rethrow; } ApiRouter().setbaseUrl(url); } else { rethrow; } } } catch (e) { final url = await ApiDomains.instance.getNextRouterUrl(); log(TAG, 'Launch request failed for URL $url: $e'); if (url.isEmpty) { rethrow; } ApiRouter().setbaseUrl(url); } } } }