api_controller.dart 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:device_info_plus/device_info_plus.dart';
  4. import 'package:dio/dio.dart';
  5. import 'package:get/get.dart';
  6. import 'package:nomo/app/api/router/api_router.dart';
  7. import 'package:nomo/app/data/sp/ix_sp.dart';
  8. import 'package:package_info_plus/package_info_plus.dart';
  9. import 'package:play_install_referrer/play_install_referrer.dart';
  10. import '../../config/translations/localization_service.dart';
  11. import '../../config/translations/strings_enum.dart';
  12. import '../../pigeons/core_api.g.dart';
  13. import '../../utils/device_manager.dart';
  14. import '../../utils/geo_downloader.dart';
  15. import '../../utils/log/logger.dart';
  16. import '../../utils/network_helper.dart';
  17. import '../api/core/api_core.dart';
  18. import '../components/country_restricted_overlay.dart';
  19. import '../components/ix_snackbar.dart';
  20. import '../constants/api_domains.dart';
  21. import '../constants/configs.dart';
  22. import '../constants/enums.dart';
  23. import '../constants/errors.dart';
  24. import '../constants/platforms.dart';
  25. import '../data/models/api_exception.dart';
  26. import '../data/models/failure.dart';
  27. import '../data/models/fingerprint.dart';
  28. import '../data/models/launch/groups.dart';
  29. import '../data/models/launch/launch.dart';
  30. import '../dialog/all_dialog.dart';
  31. class ApiController extends GetxService {
  32. final TAG = 'ApiController';
  33. // 记录是否已经显示禁用弹窗
  34. bool isShowDisabled = false;
  35. //是否是游客
  36. final _isGuest = false.obs;
  37. bool get isGuest => _isGuest.value;
  38. set isGuest(bool value) => _isGuest.value = value;
  39. //全部节点列表
  40. final _nodesList = <LocationList>[].obs;
  41. List<LocationList> get nodesList => _nodesList.value;
  42. set nodesList(List<LocationList> value) => _nodesList.value = value;
  43. //初始化fingerprint
  44. Fingerprint fp = Fingerprint.empty();
  45. Future<Fingerprint> initFingerprint() async {
  46. // 读取app发布渠道
  47. if (Platform.isIOS) {
  48. fp.channel = 'apple';
  49. } else if (Platform.isAndroid) {
  50. try {
  51. final channel = await CoreApi().getChannel();
  52. fp.channel = channel ?? 'unknown';
  53. } catch (e) {
  54. log(TAG, 'read app channel error: $e');
  55. fp.channel = '';
  56. }
  57. try {
  58. final advertisingId = await CoreApi().getAdvertisingId();
  59. fp.googleId = advertisingId ?? '';
  60. } catch (e) {
  61. log(TAG, 'read app googleId error: $e');
  62. fp.googleId = '';
  63. }
  64. try {
  65. ReferrerDetails referrerDetails =
  66. await PlayInstallReferrer.installReferrer;
  67. fp.refer = referrerDetails.installReferrer ?? '';
  68. } catch (e) {
  69. log(TAG, 'get install referrer error: $e');
  70. fp.refer = '';
  71. }
  72. }
  73. // 读取应用信息
  74. final info = await PackageInfo.fromPlatform();
  75. fp.appVersionCode = int.tryParse(info.buildNumber) ?? 0;
  76. fp.appVersionName = info.version;
  77. // 读取设备信息
  78. final deviceInfo = DeviceInfoPlugin();
  79. if (Platform.isIOS) {
  80. fp.platform = Platforms.iOS;
  81. final iosOsInfo = await deviceInfo.iosInfo;
  82. fp.deviceModel = iosOsInfo.model;
  83. fp.deviceOs = iosOsInfo.systemVersion;
  84. fp.deviceBrand = iosOsInfo.utsname.machine;
  85. } else if (Platform.isAndroid) {
  86. fp.platform = Platforms.android;
  87. final androidOsInfo = await deviceInfo.androidInfo;
  88. fp.deviceModel = androidOsInfo.model;
  89. fp.deviceOs = androidOsInfo.version.release;
  90. fp.deviceBrand = androidOsInfo.brand;
  91. fp.androidId = androidOsInfo.id;
  92. }
  93. //获取设备尺寸
  94. fp.deviceHeight = Get.height.toInt();
  95. fp.deviceWidth = Get.width.toInt();
  96. // 读取设备ID
  97. fp.deviceId = DeviceManager.getCacheDeviceId();
  98. fp.isNewInstall = IXSP.getIsNewInstall();
  99. await updateFingerprintData();
  100. return fp;
  101. }
  102. // 更新部分数据
  103. Future<void> updateFingerprintData() async {
  104. fp.lang = IXSP.getCurrentLocal().languageCode;
  105. fp.phoneCountryIso = LocalizationService.getSystemCountry();
  106. fp.isVpn = await CoreApi().isConnected() ?? false;
  107. if (!fp.isVpn) {
  108. fp.isConnectedVpn = false;
  109. }
  110. try {
  111. final simInfo = await CoreApi().getSimInfo();
  112. // 解析sim
  113. final sim = jsonDecode(simInfo ?? '{}');
  114. fp.simReady = sim['simReady'];
  115. fp.carrierName = sim['carrierName'];
  116. fp.mcc = sim['mcc'];
  117. fp.mnc = sim['mnc'];
  118. fp.countryIso = sim['countryIso'];
  119. fp.networkCarrierName = sim['networkCarrierName'];
  120. fp.networkMcc = sim['networkMcc'];
  121. fp.networkMnc = sim['networkMnc'];
  122. fp.networkCountryIso = sim['networkCountryIso'];
  123. } catch (e) {
  124. log(TAG, 'read app sim error: $e');
  125. }
  126. }
  127. Future<void> initData(Launch? launch) async {
  128. // 初始化是否第一次安装
  129. IXSP.setIsNewInstall(false);
  130. fp.userUuid = '';
  131. fp.isNewInstall = false;
  132. await initLaunch(launch);
  133. }
  134. Future<void> initLaunch(Launch? launch) async {
  135. try {
  136. if (launch != null) {
  137. // 初始化用户状态
  138. isGuest = launch.userConfig?.memberLevel == MemberLevel.guest.level;
  139. // 设置路由和节点
  140. nodesList = launch.groups?.normal?.list ?? [];
  141. // 设置资源url
  142. if (launch.appConfig?.assetUrls != null &&
  143. launch.appConfig!.assetUrls!.isNotEmpty) {
  144. Configs.assetUrl = launch.appConfig!.assetUrls![0];
  145. }
  146. // 设置官网url
  147. if (launch.appConfig?.websiteUrl != null &&
  148. launch.appConfig!.websiteUrl!.isNotEmpty) {
  149. Configs.websiteUrl = launch.appConfig!.websiteUrl!;
  150. }
  151. }
  152. } catch (e) {
  153. log(TAG, 'initLaunch error: $e');
  154. }
  155. }
  156. // 发送分析事件, 后续可以发送到firebase
  157. Future<void> sendAnalytics(FirebaseEvent event) async {
  158. try {} catch (e) {
  159. log('sendAnalytics error: $e');
  160. }
  161. }
  162. Future<Launch> launch({bool isCache = false}) async {
  163. sendAnalytics(isCache ? FirebaseEvent.launchCache : FirebaseEvent.launch);
  164. while (true) {
  165. try {
  166. ApiCore().setbaseUrl(ApiDomains.instance.getApiUrl());
  167. final request = fp.toJson();
  168. if (IXSP.getDisconnectDomains().isNotEmpty) {
  169. final disconnectDomainList = IXSP
  170. .getDisconnectDomains()
  171. .map((e) => e.toJson())
  172. .toList();
  173. request['disconnectDomainList'] = disconnectDomainList;
  174. }
  175. final result = await ApiCore().launch(request);
  176. if (!result.success) {
  177. throw Failure(
  178. code: result.errorCode ?? '',
  179. message: result.errorMessage ?? '',
  180. );
  181. }
  182. sendAnalytics(
  183. isCache
  184. ? FirebaseEvent.launchCacheSuccess
  185. : FirebaseEvent.launchSuccess,
  186. );
  187. if (IXSP.getDisconnectDomains().isNotEmpty) {
  188. IXSP.clearDisconnectDomains();
  189. }
  190. // 重置禁用状态
  191. IXSP.setLastIsRegionDisabled(false);
  192. IXSP.setLastIsUserDisabled(false);
  193. final launchData = Launch.fromJson(result.data);
  194. // 设置扩展数据
  195. fp.exData = launchData.exData;
  196. // 更新URL列表
  197. await ApiDomains.instance.updateFromLaunch(launchData);
  198. // 保存Launch数据
  199. await IXSP.saveLaunch(launchData);
  200. // 初始化Launch
  201. await initData(launchData);
  202. return launchData;
  203. } on ApiException catch (e) {
  204. final url = await ApiDomains.instance.getNextApiUrl();
  205. log(TAG, 'Launch request failed for URL $url: $e');
  206. if (url.isEmpty) {
  207. rethrow;
  208. }
  209. ApiCore().setbaseUrl(url);
  210. } on Failure catch (_) {
  211. rethrow;
  212. } on DioException catch (e) {
  213. if (e.response?.statusCode == Errors.eRegionNotAvailable ||
  214. e.response?.statusCode == Errors.eUserDisabled ||
  215. e.response?.statusCode == Errors.eTokenExpired) {
  216. rethrow;
  217. } else {
  218. if (await NetworkHelper.instance.isNetworkAvailable()) {
  219. final url = await ApiDomains.instance.getNextApiUrl();
  220. log(TAG, 'Launch request failed for URL $url: $e');
  221. if (url.isEmpty) {
  222. rethrow;
  223. }
  224. ApiCore().setbaseUrl(url);
  225. } else {
  226. rethrow;
  227. }
  228. }
  229. } catch (e) {
  230. final url = await ApiDomains.instance.getNextApiUrl();
  231. log(TAG, 'Launch request failed for URL $url: $e');
  232. if (url.isEmpty) {
  233. rethrow;
  234. }
  235. ApiCore().setbaseUrl(url);
  236. }
  237. }
  238. }
  239. Future<void> asyncHandleLaunch() async {
  240. try {
  241. final data = await launch(isCache: true);
  242. final isVpnRunning = await CoreApi().isConnected() ?? false;
  243. if (!isVpnRunning) {
  244. await checkUpdate();
  245. // 下载smartgeo文件
  246. GeoDownloader().downloadSmartGeo(smartGeo: data.appConfig!.smartGeo!);
  247. }
  248. } catch (e, s) {
  249. if (IXSP.getLastIsUserDisabled()) {
  250. if (!isShowDisabled) {
  251. Get.offAll(
  252. () => CountryRestrictedOverlay(
  253. type: RestrictedType.user,
  254. onPressed: () async {
  255. // 清除LaunchData
  256. await IXSP.clearLaunchData();
  257. // 清除禁用状态
  258. IXSP.setLastIsUserDisabled(false);
  259. // 发送事件
  260. },
  261. ),
  262. transition: Transition.fadeIn,
  263. );
  264. }
  265. return;
  266. } else if (IXSP.getLastIsRegionDisabled()) {
  267. if (!isShowDisabled) {
  268. Get.offAll(
  269. () => const CountryRestrictedOverlay(type: RestrictedType.region),
  270. transition: Transition.fadeIn,
  271. );
  272. }
  273. return;
  274. } else if (IXSP.getLastIsDeviceDisabled()) {
  275. if (!isShowDisabled) {
  276. Get.offAll(
  277. () => const CountryRestrictedOverlay(type: RestrictedType.device),
  278. transition: Transition.fadeIn,
  279. );
  280. }
  281. return;
  282. }
  283. final isVpnRunning = await CoreApi().isConnected() ?? false;
  284. if (!isVpnRunning) {
  285. await checkUpdate();
  286. }
  287. handleSnackBarError(e, s);
  288. }
  289. }
  290. void handleSnackBarError(dynamic error, StackTrace stackTrace) {
  291. if (error is ApiException) {
  292. IXSnackBar.showIXErrorSnackBar(
  293. title: Strings.error.tr,
  294. message: error.message,
  295. );
  296. } else if (error is Failure) {
  297. IXSnackBar.showIXErrorSnackBar(
  298. title: Strings.error.tr,
  299. message: error.message ?? Strings.unknownError.tr,
  300. );
  301. } else if (error is DioException) {
  302. switch (error.type) {
  303. case DioExceptionType.connectionError:
  304. case DioExceptionType.connectionTimeout:
  305. case DioExceptionType.receiveTimeout:
  306. case DioExceptionType.sendTimeout:
  307. IXSnackBar.showIXErrorSnackBar(
  308. title: Strings.error.tr,
  309. message: Strings.unableToConnectNetwork.tr,
  310. );
  311. break;
  312. default:
  313. IXSnackBar.showIXErrorSnackBar(
  314. title: Strings.error.tr,
  315. message: Strings.unableToConnectServer.tr,
  316. );
  317. }
  318. } else {
  319. IXSnackBar.showIXErrorSnackBar(
  320. title: Strings.error.tr,
  321. message: error.toString(),
  322. );
  323. }
  324. }
  325. // 更新检查 - 智能时间控制版本
  326. Future<bool> checkUpdate({bool isClickCheck = false}) async {
  327. try {
  328. final upgrade = IXSP.getUpgrade();
  329. var hasUpdate = false;
  330. var hasForceUpdate = false;
  331. if (upgrade != null) {
  332. if (upgrade.upgradeType == 1) {
  333. hasUpdate = true;
  334. }
  335. if (upgrade.forced == true) {
  336. hasForceUpdate = true;
  337. }
  338. }
  339. if (hasUpdate) {
  340. AllDialog.showUpdate(hasForceUpdate: hasForceUpdate);
  341. }
  342. return hasUpdate;
  343. } catch (e) {
  344. log(TAG, 'checkUpdate error: $e');
  345. }
  346. return false;
  347. }
  348. Future<Launch> getDispatchInfo(
  349. int locationId,
  350. String locationCode, {
  351. CancelToken? cancelToken,
  352. }) async {
  353. while (true) {
  354. try {
  355. ApiRouter().setbaseUrl(ApiDomains.instance.getRouterUrl());
  356. final request = fp.toJson();
  357. if (IXSP.getDisconnectDomains().isNotEmpty) {
  358. final disconnectDomainList = IXSP
  359. .getDisconnectDomains()
  360. .map((e) => e.toJson())
  361. .toList();
  362. request['disconnectDomainList'] = disconnectDomainList;
  363. }
  364. request['locationId'] = locationId;
  365. request['locationCode'] = locationCode;
  366. final result = await ApiRouter().getDispatchInfo(
  367. request,
  368. cancelToken: cancelToken,
  369. );
  370. if (!result.success) {
  371. throw Failure(
  372. code: result.errorCode ?? '',
  373. message: result.errorMessage ?? '',
  374. );
  375. }
  376. if (IXSP.getDisconnectDomains().isNotEmpty) {
  377. IXSP.clearDisconnectDomains();
  378. }
  379. // 重置禁用状态
  380. IXSP.setLastIsRegionDisabled(false);
  381. IXSP.setLastIsUserDisabled(false);
  382. final launchData = Launch.fromJson(result.data);
  383. // 更新URL列表
  384. await ApiDomains.instance.updateFromLaunch(launchData);
  385. // 保存app配置
  386. await IXSP.saveAppConfig(launchData.appConfig!);
  387. return launchData;
  388. } on ApiException catch (_) {
  389. rethrow;
  390. } on Failure catch (_) {
  391. rethrow;
  392. } on DioException catch (e) {
  393. if (e.response?.statusCode == Errors.eRegionNotAvailable ||
  394. e.response?.statusCode == Errors.eUserDisabled ||
  395. e.response?.statusCode == Errors.eTokenExpired) {
  396. rethrow;
  397. } else {
  398. if (await NetworkHelper.instance.isNetworkAvailable()) {
  399. final url = await ApiDomains.instance.getNextRouterUrl();
  400. log(TAG, 'getDispatchInfo request failed for URL $url: $e');
  401. if (url.isEmpty) {
  402. rethrow;
  403. }
  404. ApiRouter().setbaseUrl(url);
  405. } else {
  406. rethrow;
  407. }
  408. }
  409. } catch (e) {
  410. final url = await ApiDomains.instance.getNextRouterUrl();
  411. log(TAG, 'getDispatchInfo request failed for URL $url: $e');
  412. if (url.isEmpty) {
  413. rethrow;
  414. }
  415. ApiRouter().setbaseUrl(url);
  416. }
  417. }
  418. }
  419. Future<Launch> register(Map<String, dynamic> params) async {
  420. while (true) {
  421. try {
  422. ApiCore().setbaseUrl(ApiDomains.instance.getApiUrl());
  423. final request = fp.toJson();
  424. request.addAll(params);
  425. final result = await ApiCore().register(request);
  426. if (!result.success) {
  427. throw Failure(
  428. code: result.errorCode ?? '',
  429. message: result.errorMessage ?? '',
  430. );
  431. }
  432. final launchData = Launch.fromJson(result.data);
  433. // 注册成功后上报firebase注册事件
  434. sendAnalytics(FirebaseEvent.register);
  435. // 保存 Launch 数据
  436. await IXSP.saveLaunch(launchData);
  437. // 初始化Launch
  438. await initData(launchData);
  439. return launchData;
  440. } on ApiException catch (_) {
  441. rethrow;
  442. } on Failure catch (_) {
  443. rethrow;
  444. } on DioException catch (e) {
  445. if (e.response?.statusCode == Errors.eRegionNotAvailable ||
  446. e.response?.statusCode == Errors.eUserDisabled ||
  447. e.response?.statusCode == Errors.eTokenExpired) {
  448. rethrow;
  449. } else {
  450. if (await NetworkHelper.instance.isNetworkAvailable()) {
  451. final url = await ApiDomains.instance.getNextApiUrl();
  452. log(TAG, 'Register request failed for URL $url: $e');
  453. if (url.isEmpty) {
  454. rethrow;
  455. }
  456. ApiCore().setbaseUrl(url);
  457. } else {
  458. rethrow;
  459. }
  460. }
  461. } catch (e) {
  462. final url = await ApiDomains.instance.getNextApiUrl();
  463. log(TAG, 'Register request failed for URL $url: $e');
  464. if (url.isEmpty) {
  465. rethrow;
  466. }
  467. ApiCore().setbaseUrl(url);
  468. }
  469. }
  470. }
  471. Future<Launch> login(Map<String, dynamic> params) async {
  472. while (true) {
  473. try {
  474. ApiCore().setbaseUrl(ApiDomains.instance.getApiUrl());
  475. final request = fp.toJson();
  476. request.addAll(params);
  477. final result = await ApiCore().login(request);
  478. if (!result.success) {
  479. throw Failure(
  480. code: result.errorCode ?? '',
  481. message: result.errorMessage ?? '',
  482. );
  483. }
  484. final launchData = Launch.fromJson(result.data);
  485. // 注册成功后上报firebase注册事件
  486. sendAnalytics(FirebaseEvent.login);
  487. // 保存 Launch 数据
  488. await IXSP.saveLaunch(launchData);
  489. // 初始化Launch
  490. await initData(launchData);
  491. return launchData;
  492. } on ApiException catch (_) {
  493. rethrow;
  494. } on Failure catch (_) {
  495. rethrow;
  496. } on DioException catch (e) {
  497. if (e.response?.statusCode == Errors.eRegionNotAvailable ||
  498. e.response?.statusCode == Errors.eUserDisabled ||
  499. e.response?.statusCode == Errors.eTokenExpired) {
  500. rethrow;
  501. } else {
  502. if (await NetworkHelper.instance.isNetworkAvailable()) {
  503. final url = await ApiDomains.instance.getNextApiUrl();
  504. log(TAG, 'Login request failed for URL $url: $e');
  505. if (url.isEmpty) {
  506. rethrow;
  507. }
  508. ApiCore().setbaseUrl(url);
  509. } else {
  510. rethrow;
  511. }
  512. }
  513. } catch (e) {
  514. final url = await ApiDomains.instance.getNextApiUrl();
  515. log(TAG, 'Login request failed for URL $url: $e');
  516. if (url.isEmpty) {
  517. rethrow;
  518. }
  519. ApiCore().setbaseUrl(url);
  520. }
  521. }
  522. }
  523. Future<Launch> logout() async {
  524. try {
  525. final request = fp.toJson();
  526. final result = await ApiCore().logout(request);
  527. if (!result.success) {
  528. throw Failure(
  529. code: result.errorCode ?? '',
  530. message: result.errorMessage ?? '',
  531. );
  532. }
  533. final launchData = Launch.fromJson(result.data);
  534. // 登出成功后上报firebase登出事件
  535. sendAnalytics(FirebaseEvent.logout);
  536. // 保存 Launch 数据
  537. await IXSP.saveLaunch(launchData);
  538. return launchData;
  539. } catch (e) {
  540. rethrow;
  541. }
  542. }
  543. Future<Launch> deleteAccount() async {
  544. try {
  545. final request = fp.toJson();
  546. final result = await ApiCore().deleteAccount(request);
  547. if (!result.success) {
  548. throw Failure(
  549. code: result.errorCode ?? '',
  550. message: result.errorMessage ?? '',
  551. );
  552. }
  553. final launchData = Launch.fromJson(result.data);
  554. // 登出成功后上报firebase登出事件
  555. sendAnalytics(FirebaseEvent.deleteAccount);
  556. // 保存 Launch 数据
  557. await IXSP.saveLaunch(launchData);
  558. return launchData;
  559. } catch (e) {
  560. rethrow;
  561. }
  562. }
  563. Future<String> changePassword(Map<String, dynamic> params) async {
  564. try {
  565. final request = fp.toJson();
  566. request.addAll(params);
  567. final result = await ApiCore().changePassword(request);
  568. if (!result.success) {
  569. throw Failure(
  570. code: result.errorCode ?? '',
  571. message: result.errorMessage ?? '',
  572. );
  573. }
  574. return result.errorMessage ?? '';
  575. } catch (e) {
  576. rethrow;
  577. }
  578. }
  579. Future<Groups> getLocations() async {
  580. while (true) {
  581. try {
  582. ApiCore().setbaseUrl(ApiDomains.instance.getApiUrl());
  583. final request = fp.toJson();
  584. final result = await ApiCore().getLocations(request);
  585. if (!result.success) {
  586. throw Failure(
  587. code: result.errorCode ?? '',
  588. message: result.errorMessage ?? '',
  589. );
  590. }
  591. final groups = Groups.fromJson(result.data);
  592. return groups;
  593. } on ApiException catch (_) {
  594. rethrow;
  595. } on Failure catch (_) {
  596. rethrow;
  597. } on DioException catch (e) {
  598. if (e.response?.statusCode == Errors.eRegionNotAvailable ||
  599. e.response?.statusCode == Errors.eUserDisabled ||
  600. e.response?.statusCode == Errors.eTokenExpired) {
  601. rethrow;
  602. } else {
  603. if (await NetworkHelper.instance.isNetworkAvailable()) {
  604. final url = await ApiDomains.instance.getNextApiUrl();
  605. log(TAG, 'getLocations request failed for URL $url: $e');
  606. if (url.isEmpty) {
  607. rethrow;
  608. }
  609. ApiCore().setbaseUrl(url);
  610. } else {
  611. rethrow;
  612. }
  613. }
  614. } catch (e) {
  615. final url = await ApiDomains.instance.getNextApiUrl();
  616. log(TAG, 'getLocations request failed for URL $url: $e');
  617. if (url.isEmpty) {
  618. rethrow;
  619. }
  620. ApiCore().setbaseUrl(url);
  621. }
  622. }
  623. }
  624. }