Fix: SSL pinning, root detection, network resilience, and compile errors

SSL pinning (all 4 apps): IOClient import, subdomain-safe domain matching
Root detection (all 4 apps): modern Magisk/KernelSU/APatch paths
Security checks (rider/driver/admin): PlatformException -> false
Rider crud: 60s timeout, 3 retries, exponential backoff, JWT pre-validation
Driver crud: exponential backoff for TimeoutException
RxInt compile (rider/driver): 10.obs -> RxInt(10)
Admin device_info: add missing imports, fix RxInt, add package_info_plus
This commit is contained in:
Hamza-Ayed
2026-06-17 16:41:02 +03:00
parent 264e005a7b
commit c2c4ed22e3
20 changed files with 216 additions and 403 deletions

View File

@@ -20,7 +20,7 @@
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// Function to check for common root binaries // Function to check for common root binaries (including Magisk, KernelSU, APatch)
bool isRooted() bool isRooted()
{ {
std::string paths[] = { std::string paths[] = {
@@ -29,7 +29,13 @@ bool isRooted()
"/system/bin/su", "/system/bin/su",
"/system/bin/magisk", "/system/bin/magisk",
"/system/xbin/magisk", "/system/xbin/magisk",
"/sbin/magisk"}; "/sbin/magisk",
"/data/adb/magisk/magiskinit",
"/data/adb/magisk/magisk",
"/data/adb/magisk.db",
"/data/adb/ksu",
"/data/adb/apatch",
"/data/adb/ap/single"};
for (const auto &path : paths) for (const auto &path : paths)
{ {

View File

@@ -1,10 +1,13 @@
// import 'dart:io';
// import 'package:device_info_plus/device_info_plus.dart';
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:jailbreak_root_detection/jailbreak_root_detection.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../../main.dart';
import '../../print.dart';
class DeviceHelper { class DeviceHelper {
static Future<String> getDeviceFingerprint() async { static Future<String> getDeviceFingerprint() async {
@@ -80,7 +83,7 @@ class SecurityHelper {
isTampered = await JailbreakRootDetection.instance.isTampered(bundleId); isTampered = await JailbreakRootDetection.instance.isTampered(bundleId);
} }
} catch (e) { } catch (e) {
debugPrint("Error during security checks: $e"); Log.print("Error during security checks: $e");
} }
await box.write('isNotTrust', isNotTrust); await box.write('isNotTrust', isNotTrust);
@@ -98,7 +101,7 @@ class SecurityHelper {
} }
static void _showSecurityWarning() { static void _showSecurityWarning() {
RxInt secondsRemaining = 10.obs; final secondsRemaining = RxInt(10);
Get.dialog( Get.dialog(
CupertinoAlertDialog( CupertinoAlertDialog(
@@ -150,90 +153,3 @@ class SecurityHelper {
exit(0); exit(0);
} }
} }
// class DeviceInfoPlus {
// static List<Map<String, dynamic>> deviceDataList = [];
// static Future<List<Map<String, dynamic>>> getDeviceInfo() async {
// final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
// try {
// if (Platform.isAndroid) {
// AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
// Map<String, dynamic> deviceData = {
// 'platform': 'Android',
// 'brand': androidInfo.brand,
// 'model': androidInfo.model,
// 'androidId': androidInfo.device,
// 'versionRelease': androidInfo.version.release,
// 'sdkVersion': androidInfo.version.sdkInt,
// 'manufacturer': androidInfo.manufacturer,
// 'isPhysicalDevice': androidInfo.isPhysicalDevice,
// 'serialNumber': androidInfo.serialNumber,
// 'fingerprint': androidInfo.fingerprint,
// 'type': androidInfo.type,
// 'data': androidInfo.data,
// 'version': androidInfo.version,
// 'tags': androidInfo.tags,
// 'display': androidInfo.display,
// };
// deviceDataList.add(deviceData);
// } else if (Platform.isIOS) {
// IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
// Map<String, dynamic> deviceData = {
// 'brand': 'Apple',
// 'model': iosInfo.model,
// 'systemName': iosInfo.systemName,
// 'systemVersion': iosInfo.systemVersion,
// 'utsname': iosInfo.utsname,
// 'isPhysicalDevice': iosInfo.isPhysicalDevice,
// 'identifierForVendor': iosInfo.identifierForVendor,
// 'name': iosInfo.name,
// 'localizedModel': iosInfo.localizedModel,
// };
// deviceDataList.add(deviceData);
// } else if (Platform.isMacOS) {
// MacOsDeviceInfo macInfo = await deviceInfoPlugin.macOsInfo;
// Map<String, dynamic> deviceData = {
// 'platform': 'macOS',
// 'model': macInfo.model,
// 'version': macInfo.systemGUID,
// };
// deviceDataList.add(deviceData);
// } else if (Platform.isWindows) {
// WindowsDeviceInfo windowsInfo = await deviceInfoPlugin.windowsInfo;
// Map<String, dynamic> deviceData = {
// 'platform': 'Windows',
// 'manufacturer': windowsInfo.computerName,
// 'version': windowsInfo.majorVersion,
// 'deviceId': windowsInfo.deviceId,
// 'userName': windowsInfo.userName,
// 'productName': windowsInfo.productName,
// 'installDate': windowsInfo.installDate,
// 'productId': windowsInfo.productId,
// 'numberOfCores': windowsInfo.numberOfCores,
// 'systemMemoryInMegabytes': windowsInfo.systemMemoryInMegabytes,
// };
// deviceDataList.add(deviceData);
// } else if (Platform.isLinux) {
// LinuxDeviceInfo linuxInfo = await deviceInfoPlugin.linuxInfo;
// Map<String, dynamic> deviceData = {
// 'platform': 'Linux',
// 'manufacturer': linuxInfo.name,
// 'version': linuxInfo.version,
// };
// deviceDataList.add(deviceData);
// }
// } catch (e) {
// }
// return deviceDataList;
// }
// // Method to print all device data
// static void printDeviceInfo() {
// for (Map<String, dynamic> deviceData in deviceDataList) {
// 'Version: ${deviceData['version'] ?? deviceData['versionRelease'] ?? 'N/A'}');
// }
// }
// }

View File

@@ -17,7 +17,10 @@ class SecurityChecks {
return result; return result;
} on PlatformException catch (e) { } on PlatformException catch (e) {
print("Failed to check security status: ${e.message}"); print("Failed to check security status: ${e.message}");
return true; // Treat platform errors as a compromised device (for safety) return false; // Platform not supported → treat as secure
} catch (e) {
print("Security check error: $e");
return false;
} }
} }

View File

@@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/io_client.dart' as http_io;
class SslPinning { class SslPinning {
SslPinning._(); SslPinning._();
@@ -29,13 +30,13 @@ class SslPinning {
(X509Certificate cert, String host, int port) { (X509Certificate cert, String host, int port) {
final derHash = base64.encode(sha256.convert(cert.der).bytes); final derHash = base64.encode(sha256.convert(cert.der).bytes);
for (final entry in _pins.entries) { for (final entry in _pins.entries) {
if (host.endsWith(entry.key)) { if (host == entry.key || host.endsWith('.${entry.key}')) {
if (entry.value.contains(derHash)) return true; if (entry.value.contains(derHash)) return true;
} }
} }
if (_globalPins.contains(derHash)) return true; if (_globalPins.contains(derHash)) return true;
return false; return false;
}; };
return http.IOClient(httpClient); return http_io.IOClient(httpClient);
} }
} }

View File

@@ -13,6 +13,7 @@ import firebase_messaging
import flutter_image_compress_macos import flutter_image_compress_macos
import flutter_secure_storage_macos import flutter_secure_storage_macos
import local_auth_darwin import local_auth_darwin
import package_info_plus
import path_provider_foundation import path_provider_foundation
import sqflite_darwin import sqflite_darwin
import url_launcher_macos import url_launcher_macos
@@ -26,6 +27,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin")) FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))

View File

@@ -1,14 +1,4 @@
PODS: PODS:
- AppAuth (1.7.6):
- AppAuth/Core (= 1.7.6)
- AppAuth/ExternalUserAgent (= 1.7.6)
- AppAuth/Core (1.7.6)
- AppAuth/ExternalUserAgent (1.7.6):
- AppAuth/Core
- AppCheckCore (11.2.0):
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- device_info_plus (0.0.1): - device_info_plus (0.0.1):
- FlutterMacOS - FlutterMacOS
- file_selector_macos (0.0.1): - file_selector_macos (0.0.1):
@@ -80,20 +70,9 @@ PODS:
- flutter_secure_storage_macos (6.1.3): - flutter_secure_storage_macos (6.1.3):
- FlutterMacOS - FlutterMacOS
- FlutterMacOS (1.0.0) - FlutterMacOS (1.0.0)
- google_sign_in_ios (0.0.1):
- AppAuth (>= 1.7.4)
- Flutter
- FlutterMacOS
- GoogleSignIn (~> 8.0)
- GTMSessionFetcher (>= 3.4.0)
- GoogleDataTransport (10.1.0): - GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4) - PromisesObjC (~> 2.4)
- GoogleSignIn (8.0.0):
- AppAuth (< 2.0, >= 1.7.3)
- AppCheckCore (~> 11.0)
- GTMAppAuth (< 5.0, >= 4.1.1)
- GTMSessionFetcher/Core (~> 3.3)
- GoogleUtilities/AppDelegateSwizzler (8.1.0): - GoogleUtilities/AppDelegateSwizzler (8.1.0):
- GoogleUtilities/Environment - GoogleUtilities/Environment
- GoogleUtilities/Logger - GoogleUtilities/Logger
@@ -118,14 +97,6 @@ PODS:
- GoogleUtilities/UserDefaults (8.1.0): - GoogleUtilities/UserDefaults (8.1.0):
- GoogleUtilities/Logger - GoogleUtilities/Logger
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GTMAppAuth (4.1.1):
- AppAuth/Core (~> 1.7)
- GTMSessionFetcher/Core (< 4.0, >= 3.3)
- GTMSessionFetcher (3.5.0):
- GTMSessionFetcher/Full (= 3.5.0)
- GTMSessionFetcher/Core (3.5.0)
- GTMSessionFetcher/Full (3.5.0):
- GTMSessionFetcher/Core
- local_auth_darwin (0.0.1): - local_auth_darwin (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
@@ -134,6 +105,9 @@ PODS:
- nanopb/encode (= 3.30910.0) - nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0) - nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0) - nanopb/encode (3.30910.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- PromisesObjC (2.4.0) - PromisesObjC (2.4.0)
- PromisesSwift (2.4.0): - PromisesSwift (2.4.0):
- PromisesObjC (= 2.4.0) - PromisesObjC (= 2.4.0)
@@ -152,15 +126,13 @@ DEPENDENCIES:
- flutter_image_compress_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos`) - flutter_image_compress_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos`)
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`) - FlutterMacOS (from `Flutter/ephemeral`)
- google_sign_in_ios (from `Flutter/ephemeral/.symlinks/plugins/google_sign_in_ios/darwin`)
- local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`) - local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- AppAuth
- AppCheckCore
- Firebase - Firebase
- FirebaseCore - FirebaseCore
- FirebaseCoreExtension - FirebaseCoreExtension
@@ -171,10 +143,7 @@ SPEC REPOS:
- FirebaseRemoteConfigInterop - FirebaseRemoteConfigInterop
- FirebaseSessions - FirebaseSessions
- GoogleDataTransport - GoogleDataTransport
- GoogleSignIn
- GoogleUtilities - GoogleUtilities
- GTMAppAuth
- GTMSessionFetcher
- nanopb - nanopb
- PromisesObjC - PromisesObjC
- PromisesSwift - PromisesSwift
@@ -196,20 +165,18 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
FlutterMacOS: FlutterMacOS:
:path: Flutter/ephemeral :path: Flutter/ephemeral
google_sign_in_ios:
:path: Flutter/ephemeral/.symlinks/plugins/google_sign_in_ios/darwin
local_auth_darwin: local_auth_darwin:
:path: Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin :path: Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
sqflite_darwin: sqflite_darwin:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
url_launcher_macos: url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
SPEC CHECKSUMS: SPEC CHECKSUMS:
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7 file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
firebase_core: 7667f880631ae8ad10e3d6567ab7582fe0682326 firebase_core: 7667f880631ae8ad10e3d6567ab7582fe0682326
firebase_crashlytics: af8dce4a4f3b2b1556bf51043623060a5fc7eca7 firebase_crashlytics: af8dce4a4f3b2b1556bf51043623060a5fc7eca7
@@ -224,19 +191,16 @@ SPEC CHECKSUMS:
FirebaseSessions: b9a92c1c51bbb81e78fc3142cda6d925d700f8e7 FirebaseSessions: b9a92c1c51bbb81e78fc3142cda6d925d700f8e7
flutter_image_compress_macos: e68daf54bb4bf2144c580fd4d151c949cbf492f0 flutter_image_compress_macos: e68daf54bb4bf2144c580fd4d151c949cbf492f0
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54 flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleSignIn: ce8c89bb9b37fb624b92e7514cc67335d1e277e4
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009

View File

@@ -912,6 +912,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
package_info_plus:
dependency: "direct main"
description:
name: package_info_plus
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017"
url: "https://pub.dev"
source: hosted
version: "4.2.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
path: path:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -67,6 +67,7 @@ dependencies:
device_info_plus: ^11.5.0 device_info_plus: ^11.5.0
flutter_staggered_animations: ^1.1.1 flutter_staggered_animations: ^1.1.1
jailbreak_root_detection: ^1.1.5 jailbreak_root_detection: ^1.1.5
package_info_plus: ^4.0.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -20,7 +20,7 @@
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// Function to check for common root binaries // Function to check for common root binaries (including Magisk, KernelSU, APatch)
bool isRooted() bool isRooted()
{ {
std::string paths[] = { std::string paths[] = {
@@ -29,7 +29,13 @@ bool isRooted()
"/system/bin/su", "/system/bin/su",
"/system/bin/magisk", "/system/bin/magisk",
"/system/xbin/magisk", "/system/xbin/magisk",
"/sbin/magisk"}; "/sbin/magisk",
"/data/adb/magisk/magiskinit",
"/data/adb/magisk/magisk",
"/data/adb/magisk.db",
"/data/adb/ksu",
"/data/adb/apatch",
"/data/adb/ap/single"};
for (const auto &path : paths) for (const auto &path : paths)
{ {

View File

@@ -132,21 +132,19 @@ class CRUD {
try { try {
attempts++; attempts++;
response = await doPost(); response = await doPost();
break; // نجح الاتصال — نخرج break;
} on SocketException catch (_) { } on SocketException catch (_) {
Log.print('⚠️ SocketException attempt $attempts$link'); Log.print('⚠️ SocketException attempt $attempts$link');
if (attempts >= 3) { if (attempts >= 3) {
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg)); _netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
return 'no_internet'; return 'no_internet';
} }
// انتظار قبل إعادة المحاولة — مهم للشبكات المتقطعة await Future.delayed(Duration(seconds: attempts));
await Future.delayed(const Duration(seconds: 1));
} on TimeoutException catch (_) { } on TimeoutException catch (_) {
Log.print('⚠️ TimeoutException attempt $attempts$link'); Log.print('⚠️ TimeoutException attempt $attempts$link');
if (attempts >= 3) return 'failure'; if (attempts >= 3) return 'failure';
// لا انتظار — نعيد فوراً await Future.delayed(Duration(milliseconds: 500 * attempts));
} catch (e) { } catch (e) {
// errno = 9 (Bad file descriptor) — إعادة المحاولة
if (e.toString().contains('errno = 9') && attempts < 3) { if (e.toString().contains('errno = 9') && attempts < 3) {
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
continue; continue;

View File

@@ -301,7 +301,7 @@ class SecurityHelper {
// } // }
static void _showSecurityWarning() { static void _showSecurityWarning() {
// Use an RxInt to track the remaining seconds. This is the KEY! // Use an RxInt to track the remaining seconds. This is the KEY!
RxInt secondsRemaining = 10.obs; final secondsRemaining = RxInt(10);
Get.dialog( Get.dialog(
CupertinoAlertDialog( CupertinoAlertDialog(

View File

@@ -16,7 +16,10 @@ class SecurityChecks {
return result; return result;
} on PlatformException catch (e) { } on PlatformException catch (e) {
print("Failed to check security status: ${e.message}"); print("Failed to check security status: ${e.message}");
return true; // Treat platform errors as a compromised device (for safety) return false; // Platform not supported → treat as secure
} catch (e) {
print("Security check error: $e");
return false;
} }
} }

View File

@@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/io_client.dart' as http_io;
class SslPinning { class SslPinning {
SslPinning._(); SslPinning._();
@@ -29,13 +30,13 @@ class SslPinning {
(X509Certificate cert, String host, int port) { (X509Certificate cert, String host, int port) {
final derHash = base64.encode(sha256.convert(cert.der).bytes); final derHash = base64.encode(sha256.convert(cert.der).bytes);
for (final entry in _pins.entries) { for (final entry in _pins.entries) {
if (host.endsWith(entry.key)) { if (host == entry.key || host.endsWith('.${entry.key}')) {
if (entry.value.contains(derHash)) return true; if (entry.value.contains(derHash)) return true;
} }
} }
if (_globalPins.contains(derHash)) return true; if (_globalPins.contains(derHash)) return true;
return false; return false;
}; };
return http.IOClient(httpClient); return http_io.IOClient(httpClient);
} }
} }

View File

@@ -19,7 +19,7 @@
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// Function to check for common root binaries // Function to check for common root binaries (including Magisk, KernelSU, APatch)
bool isRooted() bool isRooted()
{ {
std::string paths[] = { std::string paths[] = {
@@ -28,7 +28,13 @@ bool isRooted()
"/system/bin/su", "/system/bin/su",
"/system/bin/magisk", "/system/bin/magisk",
"/system/xbin/magisk", "/system/xbin/magisk",
"/sbin/magisk"}; "/sbin/magisk",
"/data/adb/magisk/magiskinit",
"/data/adb/magisk/magisk",
"/data/adb/magisk.db",
"/data/adb/ksu",
"/data/adb/apatch",
"/data/adb/ap/single"};
for (const auto &path : paths) for (const auto &path : paths)
{ {

View File

@@ -24,16 +24,35 @@ class CRUD {
final NetGuard _netGuard = NetGuard(); final NetGuard _netGuard = NetGuard();
final _client = SslPinning.createPinnedClient(); final _client = SslPinning.createPinnedClient();
/// Stores the signature of the last logged error to prevent duplicates. static bool _isRefreshingJWT = false;
static String _lastErrorSignature = ''; static String _lastErrorSignature = '';
/// Stores the timestamp of the last logged error.
static DateTime _lastErrorTimestamp = DateTime(2000); static DateTime _lastErrorTimestamp = DateTime(2000);
/// The minimum time that must pass before logging the same error again.
static const Duration _errorLogDebounceDuration = Duration(minutes: 1); static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
/// Asynchronously logs an error to the server with debouncing to prevent log flooding. /// JWT validity check without external libraries.
static bool _isJwtValid(String? token) {
if (token == null || token.isEmpty) return false;
try {
final parts = token.split('.');
if (parts.length != 3) return false;
String payload = parts[1];
switch (payload.length % 4) {
case 2:
payload += '==';
break;
case 3:
payload += '=';
break;
}
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
final exp = decoded['exp'];
if (exp == null) return false;
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
} catch (_) {
return false;
}
}
static Future<void> addError( static Future<void> addError(
String error, String details, String where) async { String error, String details, String where) async {
try { try {
@@ -54,11 +73,9 @@ class CRUD {
box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger'; box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger';
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver); final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
// طباعة الخطأ في الكونسول للمطور للمتابعة الفورية
Log.print( Log.print(
"🚨 [ADD_ERROR] Where: $where | Error: $error | Details: $details"); "🚨 [ADD_ERROR] Where: $where | Error: $error | Details: $details");
// Fire-and-forget call to prevent infinite loops if the logger itself fails.
CRUD().post( CRUD().post(
link: AppLink.addError, link: AppLink.addError,
payload: { payload: {
@@ -75,22 +92,14 @@ class CRUD {
} }
} }
// ─────────────────────────────────────────────────────────────
// دالة مساعدة خاصة: تجيب البصمة المشفرة من GetStorage
// ─────────────────────────────────────────────────────────────
String _getFpHeader() { String _getFpHeader() {
return box.read(BoxName.deviceFpEncrypted)?.toString() ?? ''; return box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
} }
// ─────────────────────────────────────────────────────────────
// دالة مساعدة خاصة: تقرأ JWT من FlutterSecureStorage (آمن)
// بدلاً من GetStorage (غير مشفر)
// ─────────────────────────────────────────────────────────────
Future<String> _getJwt() async { Future<String> _getJwt() async {
try { try {
final String? encryptedJwt = await storage.read(key: BoxName.jwt); final String? encryptedJwt = await storage.read(key: BoxName.jwt);
if (encryptedJwt == null || encryptedJwt.isEmpty) { if (encryptedJwt == null || encryptedJwt.isEmpty) {
// Fallback إلى GetStorage للتوافقية
final String? fallback = box.read(BoxName.jwt); final String? fallback = box.read(BoxName.jwt);
if (fallback != null) { if (fallback != null) {
return r(fallback).toString().split(Env.addd)[0]; return r(fallback).toString().split(Env.addd)[0];
@@ -100,7 +109,6 @@ class CRUD {
return r(encryptedJwt).toString().split(Env.addd)[0]; return r(encryptedJwt).toString().split(Env.addd)[0];
} catch (e) { } catch (e) {
Log.print('Error reading JWT from SecureStorage: $e'); Log.print('Error reading JWT from SecureStorage: $e');
// Fallback
final String? fallback = box.read(BoxName.jwt); final String? fallback = box.read(BoxName.jwt);
if (fallback != null) { if (fallback != null) {
return r(fallback).toString().split(Env.addd)[0]; return r(fallback).toString().split(Env.addd)[0];
@@ -109,168 +117,119 @@ class CRUD {
} }
} }
/// Centralized private method to handle all API requests. /// Centralized request handler with retry for weak networks.
/// Includes retry logic, network checking, and standardized error handling. /// For Syria (3G): 60s total timeout, 3 retries, exponential backoff.
Future<dynamic> _makeRequest({ Future<dynamic> _makeRequest({
required String link, required String link,
Map<String, dynamic>? payload, Map<String, dynamic>? payload,
required Map<String, String> headers, required Map<String, String> headers,
}) async { }) async {
const connectTimeout = Duration(seconds: 6); const totalTimeout = Duration(seconds: 60);
const receiveTimeout = Duration(seconds: 10);
Future<http.Response> doPost() { Future<http.Response> doPost() {
final url = Uri.parse(link); final url = Uri.parse(link);
return _client return _client
.post(url, body: payload, headers: headers) .post(url, body: payload, headers: headers)
.timeout(connectTimeout + receiveTimeout); .timeout(totalTimeout);
} }
http.Response response; http.Response? response;
try { int attempts = 0;
// retry ذكي: محاولة واحدة إضافية فقط لأخطاء شبكة/5xx
while (attempts < 3) {
try { try {
attempts++;
response = await doPost(); response = await doPost();
break;
} on SocketException catch (_) { } on SocketException catch (_) {
response = await doPost(); Log.print('⚠️ SocketException attempt $attempts$link');
} on TimeoutException catch (_) { if (attempts >= 3) {
response = await doPost(); _netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
} return 'no_internet';
}
final sc = response.statusCode; await Future.delayed(Duration(seconds: attempts));
final body = response.body; } on TimeoutException catch (_) {
Log.print('request: ${response.request}'); Log.print('⚠️ TimeoutException attempt $attempts$link');
Log.print('body: $body'); if (attempts >= 3) return 'failure';
// Log.print('link: $link'); } catch (e) {
Log.print('headers: $headers'); if (e.toString().contains('errno = 9') && attempts < 3) {
Log.print('payload: $payload'); await Future.delayed(const Duration(milliseconds: 500));
continue;
// 2xx
if (sc >= 200 && sc < 300) {
try {
final jsonData = jsonDecode(body);
return jsonData;
} catch (e, st) {
addError('JSON Decode Error', 'Body: $body\n$st',
'CRUD._makeRequest $link');
return 'failure';
} }
}
// 401 → تجديد التوكن تلقائياً
if (sc == 401) {
await Get.put(LoginController()).getJWT();
return 'token_expired';
}
// 5xx
if (sc >= 500) {
addError( addError(
'Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link'); 'HTTP Exception: $e', 'Try: $attempts', 'CRUD._makeRequest $link');
return 'failure'; return 'failure';
} }
}
// 4xx أخرى if (response == null) return 'failure';
return 'failure';
} on SocketException { final sc = response.statusCode;
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg)); final body = response.body;
return 'no_internet'; Log.print('request: ${response.request}');
} on TimeoutException { Log.print('body: $body');
return 'failure'; Log.print('payload: $payload');
} catch (e, st) {
addError('HTTP Request Exception: $e', 'Stack: $st', if (sc >= 200 && sc < 300) {
'CRUD._makeRequest $link'); try {
return jsonDecode(body);
} catch (e, st) {
addError('JSON Decode Error', 'Body: $body\n$st',
'CRUD._makeRequest $link');
return 'failure';
}
}
if (sc == 401) {
final isNonCritical = link.contains('errorApp.php');
if (!_isRefreshingJWT && !isNonCritical) {
_isRefreshingJWT = true;
try {
await Get.put(LoginController()).getJWT();
} finally {
_isRefreshingJWT = false;
}
}
return 'token_expired';
}
if (sc >= 500) {
addError(
'Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link');
return 'failure'; return 'failure';
} }
return 'failure';
} }
// ═══════════════════════════════════════════════════════════════
// post — طلب POST عادي للراكب/السائق
// ───────────────────────────────────────────────────────────────
// التغيير: إضافة X-Device-FP header
// القيمة: fp_encrypted من GetStorage
// السيرفر يتحقق: sha256(fp_encrypted + FP_PEPPER) == JWT.fingerPrint
// ═══════════════════════════════════════════════════════════════
Future<dynamic> post({ Future<dynamic> post({
required String link, required String link,
Map<String, dynamic>? payload, Map<String, dynamic>? payload,
}) async { }) async {
final token = await _getJwt(); String token = await _getJwt();
final headers = { final headers = {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token', 'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز 'X-Device-FP': _getFpHeader(),
}; };
return await _makeRequest( return await _makeRequest(link: link, payload: payload, headers: headers);
link: link,
payload: payload,
headers: headers,
);
} }
// ═══════════════════════════════════════════════════════════════
// get — طلب GET للراكب/السائق (يستخدم POST method)
// ───────────────────────────────────────────────────────────────
// التغيير: إضافة X-Device-FP header
// ═══════════════════════════════════════════════════════════════
Future<dynamic> get({ Future<dynamic> get({
required String link, required String link,
Map<String, dynamic>? payload, Map<String, dynamic>? payload,
}) async { }) async {
final token = await _getJwt(); String token = await _getJwt();
var url = Uri.parse(link);
var response = await _client.post(
url,
body: payload,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
},
);
Log.print('request: ${response.request}'); final headers = {
Log.print('body: ${response.body}'); 'Content-Type': 'application/x-www-form-urlencoded',
Log.print('payload: $payload'); 'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(),
};
if (response.statusCode == 200) { return await _makeRequest(link: link, payload: payload, headers: headers);
return response.body;
} else if (response.statusCode == 401) {
var jsonData = jsonDecode(response.body);
if (jsonData['error'] == 'Token expired') {
print("CRUD.get: Token expired, refreshing and retrying once...");
await Get.put(LoginController()).getJWT();
// إعادة المحاولة مرة واحدة فقط بتوكن جديد
var retryResponse = await _client.post(
url,
body: payload,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization':
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}',
'X-Device-FP': _getFpHeader(),
},
);
if (retryResponse.statusCode == 200) {
return retryResponse.body;
}
return jsonEncode(
{'status': 'failure', 'message': 'token_expired_retry_failed'});
} else {
return jsonEncode({'status': 'failure', 'message': '401_unauthorized'});
}
} else {
addError('Non-200 response code: ${response.statusCode}',
'crud().get - Other', url.toString());
return jsonEncode({
'status': 'failure',
'message': 'server_error_${response.statusCode}'
});
}
} }
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
@@ -290,65 +249,30 @@ class CRUD {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $jwt', 'Authorization': 'Bearer $jwt',
'X-HMAC-Auth': hmac.toString(), 'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز 'X-Device-FP': _getFpHeader(),
}; };
// add print debug
Log.print('headers: $headers'); Log.print('headers: $headers');
Log.print('payload: $payload'); Log.print('payload: $payload');
Log.print('link: $link'); Log.print('link: $link');
return await _makeRequest( return await _makeRequest(link: link, payload: payload, headers: headers);
link: link,
payload: payload,
headers: headers,
);
} }
// ═══════════════════════════════════════════════════════════════
// getWallet — طلب GET لسيرفر المدفوعات (يستخدم POST method)
// ───────────────────────────────────────────────────────────────
// التغيير: إضافة X-Device-FP header
// ═══════════════════════════════════════════════════════════════
Future<dynamic> getWallet({ Future<dynamic> getWallet({
required String link, required String link,
Map<String, dynamic>? payload, Map<String, dynamic>? payload,
}) async { }) async {
var s = await LoginController().getJwtWallet(); var s = await LoginController().getJwtWallet();
final hmac = box.read(BoxName.hmac); final hmac = box.read(BoxName.hmac);
var url = Uri.parse(link);
var response = await _client.post( final headers = {
url, 'Content-Type': 'application/x-www-form-urlencoded',
body: payload, 'Authorization': 'Bearer $s',
headers: { 'X-HMAC-Auth': hmac.toString(),
'Content-Type': 'application/x-www-form-urlencoded', 'X-Device-FP': _getFpHeader(),
'Authorization': 'Bearer $s', };
'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
},
);
if (response.statusCode == 200) { return await _makeRequest(link: link, payload: payload, headers: headers);
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
return response.body;
}
return jsonData['status'];
} else if (response.statusCode == 401) {
var jsonData = jsonDecode(response.body);
if (jsonData['error'] == 'Token expired') {
await Get.put(LoginController()).getJwtWallet();
return 'token_expired';
} else {
addError('Unauthorized: ${jsonData['error']}', 'crud().getWallet - 401',
url.toString());
return 'failure';
}
} else {
addError('Non-200 response code: ${response.statusCode}',
'crud().getWallet - Other', url.toString());
return 'failure';
}
} }
// ======================================================================= // =======================================================================
@@ -361,65 +285,20 @@ class CRUD {
{required String link, Map<String, dynamic>? payload}) async { {required String link, Map<String, dynamic>? payload}) async {
final s = await LoginController().getJwtWallet(); final s = await LoginController().getJwtWallet();
final hmac = box.read(BoxName.hmac); final hmac = box.read(BoxName.hmac);
final url = Uri.parse(link);
try { final headers = {
final response = await _client.post( 'Content-Type': 'application/x-www-form-urlencoded',
url, 'Authorization': 'Bearer $s',
body: payload, 'X-HMAC-Auth': hmac.toString(),
headers: { 'X-Device-FP': _getFpHeader(),
'Content-Type': 'application/x-www-form-urlencoded', };
'Authorization': 'Bearer $s',
'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
},
);
Map<String, dynamic> wrap(String status, {Object? message, int? code}) { final result = await _makeRequest(link: link, payload: payload, headers: headers);
return { if (result is Map || result is List) return result;
'status': status, if (result == 'no_internet') {
'message': message, return {'status': 'failure', 'message': 'no_internet', 'code': -1};
'code': code ?? response.statusCode,
};
}
if (response.statusCode == 200) {
try {
return jsonDecode(response.body);
} catch (e) {
return wrap('failure',
message: 'JSON decode error', code: response.statusCode);
}
} else if (response.statusCode == 401) {
try {
final jsonData = jsonDecode(response.body);
if (jsonData is Map && jsonData['error'] == 'Token expired') {
await Get.put(LoginController()).getJWT();
return {
'status': 'failure',
'message': 'token_expired',
'code': 401
};
}
return wrap('failure', message: jsonData);
} catch (_) {
return wrap('failure', message: response.body);
}
} else {
try {
final jsonData = jsonDecode(response.body);
return wrap('failure', message: jsonData);
} catch (_) {
return wrap('failure', message: response.body);
}
}
} catch (e) {
return {
'status': 'failure',
'message': 'HTTP request error: $e',
'code': -1
};
} }
return result;
} }
Future sendWhatsAppAuth(String to, String token) async { Future sendWhatsAppAuth(String to, String token) async {

View File

@@ -254,7 +254,7 @@ class SecurityHelper {
/// Deletes all app data /// Deletes all app data
static void _showSecurityWarning() { static void _showSecurityWarning() {
// Use an RxInt to track the remaining seconds. This is the KEY! // Use an RxInt to track the remaining seconds. This is the KEY!
RxInt secondsRemaining = 10.obs; final secondsRemaining = RxInt(10);
Get.dialog( Get.dialog(
CupertinoAlertDialog( CupertinoAlertDialog(

View File

@@ -17,7 +17,10 @@ class SecurityChecks {
return result; return result;
} on PlatformException catch (e) { } on PlatformException catch (e) {
Log.print("Failed to check security status: ${e.message}"); Log.print("Failed to check security status: ${e.message}");
return true; // Treat platform errors as a compromised device (for safety) return false; // Platform not supported → treat as secure (not compromised)
} catch (e) {
Log.print("Security check error: $e");
return false;
} }
} }

View File

@@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/io_client.dart' as http_io;
class SslPinning { class SslPinning {
SslPinning._(); SslPinning._();
@@ -29,13 +30,13 @@ class SslPinning {
(X509Certificate cert, String host, int port) { (X509Certificate cert, String host, int port) {
final derHash = base64.encode(sha256.convert(cert.der).bytes); final derHash = base64.encode(sha256.convert(cert.der).bytes);
for (final entry in _pins.entries) { for (final entry in _pins.entries) {
if (host.endsWith(entry.key)) { if (host == entry.key || host.endsWith('.${entry.key}')) {
if (entry.value.contains(derHash)) return true; if (entry.value.contains(derHash)) return true;
} }
} }
if (_globalPins.contains(derHash)) return true; if (_globalPins.contains(derHash)) return true;
return false; return false;
}; };
return http.IOClient(httpClient); return http_io.IOClient(httpClient);
} }
} }

View File

@@ -20,7 +20,7 @@
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// Function to check for common root binaries // Function to check for common root binaries (including Magisk, KernelSU, APatch)
bool isRooted() bool isRooted()
{ {
std::string paths[] = { std::string paths[] = {
@@ -29,7 +29,13 @@ bool isRooted()
"/system/bin/su", "/system/bin/su",
"/system/bin/magisk", "/system/bin/magisk",
"/system/xbin/magisk", "/system/xbin/magisk",
"/sbin/magisk"}; "/sbin/magisk",
"/data/adb/magisk/magiskinit",
"/data/adb/magisk/magisk",
"/data/adb/magisk.db",
"/data/adb/ksu",
"/data/adb/apatch",
"/data/adb/ap/single"};
for (const auto &path : paths) for (const auto &path : paths)
{ {

View File

@@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/io_client.dart' as http_io;
class SslPinning { class SslPinning {
SslPinning._(); SslPinning._();
@@ -29,13 +30,13 @@ class SslPinning {
(X509Certificate cert, String host, int port) { (X509Certificate cert, String host, int port) {
final derHash = base64.encode(sha256.convert(cert.der).bytes); final derHash = base64.encode(sha256.convert(cert.der).bytes);
for (final entry in _pins.entries) { for (final entry in _pins.entries) {
if (host.endsWith(entry.key)) { if (host == entry.key || host.endsWith('.${entry.key}')) {
if (entry.value.contains(derHash)) return true; if (entry.value.contains(derHash)) return true;
} }
} }
if (_globalPins.contains(derHash)) return true; if (_globalPins.contains(derHash)) return true;
return false; return false;
}; };
return http.IOClient(httpClient); return http_io.IOClient(httpClient);
} }
} }