Fix: update destination limits to 3 and sync with Redis

This commit is contained in:
Hamza-Ayed
2026-06-27 17:49:03 +03:00
parent 5fed555e44
commit 43e3f8c939
13 changed files with 221 additions and 130 deletions

View File

@@ -104,7 +104,8 @@ class LoginDriverController extends GetxController {
isPhoneVerified() async {
// If the phone is already verified locally and we have a driver ID, skip the API check
// This prevents asking for OTP again if the user restarts the app during registration.
if (box.read(BoxName.phoneVerified) == '1' && box.read(BoxName.driverID) != null) {
if (box.read(BoxName.phoneVerified) == '1' &&
box.read(BoxName.driverID) != null) {
Get.offAll(() => RegistrationView());
return;
}
@@ -177,6 +178,34 @@ class LoginDriverController extends GetxController {
getJWT() async {
await EncryptionHelper.initialize();
// 1. Check secure storage first to avoid redundant API calls
String? secureJwt = await storage.read(key: BoxName.jwt);
if (secureJwt != null && secureJwt.isNotEmpty) {
bool isTokenValid = false;
try {
final parts = secureJwt.split('.');
if (parts.length == 3) {
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) {
// Check if token is valid with a 30-second buffer
isTokenValid = DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
}
}
} catch (_) {}
if (isTokenValid) {
Log.print('🔑 Valid JWT found in secure storage. Skipping generation.');
return;
}
}
dev = Platform.isAndroid ? 'android' : 'ios';
Log.print(
'box.read(BoxName.firstTimeLoadKey): ${box.read(BoxName.firstTimeLoadKey)}');
@@ -209,8 +238,8 @@ class LoginDriverController extends GetxController {
}
if (jwt != null) {
box.write(BoxName.jwt, c(jwt));
await storage.write(key: BoxName.jwt, value: c(jwt));
// box.write(BoxName.jwt, c(jwt));
await storage.write(key: BoxName.jwt, value: jwt);
}
// ✅ بعد التأكد أن كل المفاتيح موجودة
@@ -249,8 +278,8 @@ class LoginDriverController extends GetxController {
}
if (jwt != null) {
await box.write(BoxName.jwt, c(jwt));
await storage.write(key: BoxName.jwt, value: c(jwt));
// await box.write(BoxName.jwt, c(jwt));
await storage.write(key: BoxName.jwt, value: jwt);
}
// await AppInitializer().getKey();
@@ -266,32 +295,6 @@ class LoginDriverController extends GetxController {
update();
}
String generateUniqueIdFromEmail(String email) {
// Step 1: Extract the local part of the email
String localPart = email.split('@')[0];
// Step 2: Replace invalid characters (if any)
String cleanLocalPart = localPart.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '');
// Step 3: Ensure it does not exceed 24 characters
if (cleanLocalPart.length > 24) {
cleanLocalPart = cleanLocalPart.substring(0, 24);
}
// Step 4: Generate a random suffix if needed
String suffix = generateRandomSuffix(24 - cleanLocalPart.length);
return cleanLocalPart + suffix;
}
String generateRandomSuffix(int length) {
const String chars =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
Random random = Random();
return List.generate(length, (index) => chars[random.nextInt(chars.length)])
.join('');
}
bool isInviteDriverFound = false;
Future updateInvitationCodeFromRegister() async {
@@ -335,7 +338,8 @@ class LoginDriverController extends GetxController {
// await SecurityHelper.performSecurityChecks();
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
// await getJWT();
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID});
var res = await CRUD().get(
link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID});
Log.print('loginDriver: ${res}');
if (res == 'failure') {
await isPhoneVerified();
@@ -391,10 +395,10 @@ class LoginDriverController extends GetxController {
// ✅ الحصول على توكن access بدل registration قبل أي طلبات بعد تسجيل الدخول
Log.print('🔑 Getting access token after login...');
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
await storage.write(
key: BoxName.fingerPrint, value: fingerPrint.toString());
await getJWT();
Log.print('🔑 Access token obtained.');
// await storage.write(
// key: BoxName.fingerPrint, value: fingerPrint.toString());
// await getJWT();
// Log.print('🔑 Access token obtained.');
// add invitations
if (box.read(BoxName.isInstall) == null ||
@@ -416,10 +420,15 @@ class LoginDriverController extends GetxController {
// '963992952235@intaleqapp.com') {
if (token != 'failure') {
var serverData = jsonDecode(token);
if ((serverData['data'][0]['token'].toString()) !=
box.read(BoxName.tokenDriver).toString() ||
serverData['data'][0]['fingerPrint'].toString() !=
fingerPrint.toString()) {
final serverToken = serverData['data'][0]['token'].toString();
final storedToken =
box.read(BoxName.tokenDriver)?.toString() ?? '';
final serverFp = serverData['data'][0]['fingerPrint'].toString();
final tokenMismatch =
storedToken.isNotEmpty && serverToken != storedToken;
Log.print(
'🔍 [FP_COMPARE] serverFp: $serverFp | localFp: ${fingerPrint.toString()} | tokenMismatch: $tokenMismatch | serverToken: $serverToken | storedToken: $storedToken');
if (tokenMismatch) {
Get.defaultDialog(
barrierDismissible: false,
title: 'Device Change Detected'.tr,
@@ -485,7 +494,8 @@ class LoginDriverController extends GetxController {
// await SecurityHelper.performSecurityChecks();
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID});
var res = await CRUD().get(
link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID});
// print('res is $res');
// if (res == 'failure') {
@@ -594,7 +604,7 @@ class LoginDriverController extends GetxController {
box.write(BoxName.jwt, c(jwt));
await storage.write(key: BoxName.jwt, value: c(jwt));
}
box.write(BoxName.emailDriver, (d['email']));
box.write(BoxName.driverID, (d['id']));
box.write(BoxName.isTest, '1');

View File

@@ -11,6 +11,7 @@ import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:siro_driver/env/env.dart';
import 'package:siro_driver/print.dart';
import 'package:siro_driver/controller/functions/package_info.dart';
import '../../constant/api_key.dart';
import '../../views/widgets/error_snakbar.dart';
@@ -21,7 +22,6 @@ import 'ssl_pinning.dart';
class CRUD {
final NetGuard _netGuard = NetGuard();
final _client = SslPinning.createPinnedClient();
static bool _isRefreshingJWT = false;
static String _lastErrorSignature = '';
static DateTime _lastErrorTimestamp = DateTime(2000);
@@ -93,15 +93,15 @@ class CRUD {
// نفس القيمة المرسلة عند login وعُملها hash في JWT
// السيرفر يتحقق: sha256(X-Device-FP + FP_PEPPER) == JWT.fingerPrint
// ─────────────────────────────────────────────────────────────
String _getFpHeader() {
return box.read(BoxName.deviceFingerprint)?.toString() ?? '';
Future<String> _getFpHeader() async {
return box.read(BoxName.deviceFingerprint)?.toString() ?? await DeviceHelper.getDeviceFingerprint();
}
String _getJwt() {
Future<String> _getJwt() async {
try {
final jwt = box.read(BoxName.jwt);
final jwt = await storage.read(key: BoxName.jwt);
if (jwt == null || jwt.toString().isEmpty) return '';
return r(jwt).toString().split(Env.addd)[0];
return jwt;
} catch (_) {
return '';
}
@@ -172,6 +172,7 @@ class CRUD {
final body = response.body;
Log.print('📥 [RES-$requestId] [$sc] $link');
Log.print('payload: $payload');
Log.print('📄 [BODY-$requestId] $body');
// 2xx
@@ -217,14 +218,14 @@ class CRUD {
required String link,
Map<String, dynamic>? payload,
}) async {
String token = _getJwt();
String token = await _getJwt();
// فحص صلاحية التوكن قبل الإرسال — تجنب طلب مضمون الرفض
if (!_isJwtValid(token) && !_isRefreshingJWT) {
_isRefreshingJWT = true;
try {
await Get.put(LoginDriverController()).getJWT();
token = _getJwt();
token = await _getJwt();
} finally {
_isRefreshingJWT = false;
}
@@ -233,7 +234,7 @@ class CRUD {
final headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز
};
return await _makeRequest(link: link, payload: payload, headers: headers);
@@ -249,12 +250,12 @@ class CRUD {
}) async {
try {
// فحص صلاحية التوكن قبل الإرسال
String token = _getJwt();
String token = await _getJwt();
if (!_isJwtValid(token) && !_isRefreshingJWT) {
_isRefreshingJWT = true;
try {
await Get.put(LoginDriverController()).getJWT();
token = _getJwt();
token = await _getJwt();
} finally {
_isRefreshingJWT = false;
}
@@ -267,7 +268,7 @@ class CRUD {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(),
'X-Device-FP': await _getFpHeader(),
},
).timeout(const Duration(seconds: 60));
@@ -321,7 +322,7 @@ class CRUD {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $jwt',
'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز
};
return await _makeRequest(link: link, payload: payload, headers: headers);
@@ -346,7 +347,7 @@ class CRUD {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $s',
'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز
},
).timeout(const Duration(seconds: 60));
@@ -392,7 +393,7 @@ class CRUD {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $s',
'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز
},
).timeout(const Duration(seconds: 60));
@@ -571,17 +572,17 @@ class CRUD {
// ── sendEmail — إصلاح: استخدام r() بدل X.r() القديم ─────────
Future<void> sendEmail(String link, Map<String, String>? payload) async {
// r() هي نفس دالة فك التشفير الثلاثي المختصرة
String token = _getJwt();
String token = await _getJwt();
if (!_isJwtValid(token)) {
await LoginDriverController().getJWT();
token = _getJwt();
token = await _getJwt();
}
final headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
'X-Device-FP': await _getFpHeader(), // ← إثبات الجهاز
};
final request = http.Request('POST', Uri.parse(link));

View File

@@ -46,6 +46,15 @@ class EncryptionHelper {
debugPrint("EncryptionHelper initialized successfully.");
}
/// Encrypts a string using AES-256-CBC with constant IV (deterministic)
/// Same input always produces the same output
String encryptDataCbc(String plainText) {
final cbcEncrypter =
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
final encrypted = cbcEncrypter.encrypt(plainText, iv: iv);
return encrypted.base64;
}
/// ✅ FIX H-04: Encrypts a string using AES-256-GCM with a random IV
/// Format: "GCM:<base64_iv>:<base64_ciphertext>"
String encryptData(String plainText) {

View File

@@ -8,6 +8,7 @@ import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/colors.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/controller/functions/encrypt_decrypt.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -16,7 +17,6 @@ import 'package:url_launcher/url_launcher.dart';
import '../../constant/info.dart';
import '../../main.dart';
import '../../print.dart';
import 'encrypt_decrypt.dart';
Future<void> checkForUpdate(BuildContext context) async {
final packageInfo = await PackageInfo.fromPlatform();
@@ -168,13 +168,6 @@ void showUpdateDialog(BuildContext context) {
class DeviceHelper {
static Future<String> getDeviceFingerprint() async {
await EncryptionHelper.initialize();
final cached = box.read(BoxName.deviceFingerprint);
if (cached != null && cached.toString().isNotEmpty) {
return cached.toString();
}
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
var deviceData;
@@ -190,19 +183,20 @@ class DeviceHelper {
}
final String deviceId = Platform.isAndroid
? deviceData['id'] ?? deviceData['androidId'] ?? deviceData['fingerprint'] ?? 'unknown'
? deviceData['androidId'] ?? deviceData['fingerprint'] ?? 'unknown'
: deviceData['identifierForVendor'] ?? 'unknown';
final String deviceModel = deviceData['model'] ?? 'unknown';
final String fingerprint =
EncryptionHelper.instance.encryptData('${deviceId}_$deviceModel');
box.write(BoxName.deviceFingerprint, fingerprint);
return (fingerprint);
final String fingerprint = '${deviceId}_$deviceModel';
final String encryptedFp =
EncryptionHelper.instance.encryptDataCbc(fingerprint);
box.write(BoxName.deviceFpEncrypted, encryptedFp);
box.write(BoxName.deviceFingerprint, encryptedFp);
Log.print('fingerprint: $encryptedFp');
return encryptedFp;
} catch (e) {
debugPrint('Error generating device fingerprint: $e');
throw Exception('Failed to generate device fingerprint: $e');
throw Exception('Failed to generate device fingerprint');
}
}
}

View File

@@ -38,44 +38,9 @@ class AppInitializer {
List<Map<String, dynamic>> links = [];
Future<void> initializeApp() async {
if (box.read(BoxName.jwt) == null) {
String? secureJwt = await storage.read(key: BoxName.jwt);
if (secureJwt != null) {
box.write(BoxName.jwt, secureJwt);
}
}
if (box.read(BoxName.jwt) == null) {
await LoginDriverController().getJWT();
} else {
String token =
r(box.read(BoxName.jwt)).toString().split(AppInformation.addd)[0];
bool isTokenValid = false;
try {
final parts = token.split('.');
if (parts.length == 3) {
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) {
isTokenValid =
DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
}
}
} catch (_) {}
if (!isTokenValid) {
await LoginDriverController().getJWT();
}
}
// getJWT() now internally checks flutter_secure_storage and validates the token
// before making any network requests.
await LoginDriverController().getJWT();
// await getKey();
}

View File

@@ -21,6 +21,7 @@ import '../../functions/background_service.dart';
import '../../functions/crud.dart';
import '../../functions/location_background_controller.dart';
import '../../functions/location_controller.dart';
import '../../functions/package_info.dart';
import '../payment/captain_wallet_controller.dart';
class HomeCaptainController extends GetxController {
@@ -507,6 +508,7 @@ class HomeCaptainController extends GetxController {
// late SiroMapController mapHomeCaptainController;
IntaleqMapController? mapHomeCaptainController;
CameraPosition? currentCameraPosition;
LatLng? _lastCameraLoc; // لتتبع آخر موقع حرك الكاميرا
// --- FIX 2: Smart Map Creation ---
@@ -740,7 +742,7 @@ class HomeCaptainController extends GetxController {
}
addToken() async {
String? fingerPrint = await storage.read(key: BoxName.fingerPrint);
String? fingerPrint = await DeviceHelper.getDeviceFingerprint();
final payload = {
'token': (box.read(BoxName.tokenDriver)),
'captain_id': (box.read(BoxName.driverID)).toString(),