Update codebase
This commit is contained in:
@@ -1,26 +1,25 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:tripz_rider/services/geofencing_service.dart';
|
||||
import 'package:siro_rider/services/geofencing_service.dart';
|
||||
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:tripz_rider/constant/api_key.dart';
|
||||
import 'package:tripz_rider/controller/firebase/firbase_messge.dart';
|
||||
import 'package:tripz_rider/views/auth/otp_page.dart';
|
||||
import 'package:tripz_rider/views/widgets/error_snakbar.dart';
|
||||
import 'package:siro_rider/constant/api_key.dart';
|
||||
import 'package:siro_rider/controller/firebase/firbase_messge.dart';
|
||||
import 'package:siro_rider/views/auth/otp_page.dart';
|
||||
import 'package:siro_rider/views/widgets/error_snakbar.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:tripz_rider/constant/info.dart';
|
||||
import 'package:tripz_rider/controller/functions/add_error.dart';
|
||||
import 'package:tripz_rider/views/auth/login_page.dart';
|
||||
import 'package:siro_rider/constant/info.dart';
|
||||
import 'package:siro_rider/controller/functions/add_error.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tripz_rider/constant/box_name.dart';
|
||||
import 'package:tripz_rider/constant/links.dart';
|
||||
import 'package:tripz_rider/controller/functions/crud.dart';
|
||||
import 'package:tripz_rider/main.dart';
|
||||
import 'package:tripz_rider/views/home/map_page_passenger.dart';
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/functions/crud.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
import 'package:siro_rider/views/home/map_page_passenger.dart';
|
||||
|
||||
import 'package:location/location.dart' as loc;
|
||||
|
||||
@@ -82,26 +81,55 @@ class LoginController extends GetxController {
|
||||
// • firstTimeLoadKey != false ← أول مرة يفتح التطبيق → loginFirstTime
|
||||
// • firstTimeLoadKey == false ← مستخدم موجود → loginJwtRider
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
Future<void> getJWT({bool force = false}) async {
|
||||
// إذا كان التوكن الحالي لا يزال صالحاً، لا داعي لطلب واحد جديد
|
||||
static Future<bool>? _jwtFuture;
|
||||
static DateTime _jwtCooldownUntil = DateTime(2000);
|
||||
static int _jwtFailures = 0;
|
||||
|
||||
static Duration get jwtCooldownRemaining {
|
||||
final d = _jwtCooldownUntil.difference(DateTime.now());
|
||||
return d.isNegative ? Duration.zero : d;
|
||||
}
|
||||
|
||||
Future<bool> getJWT({bool force = false}) async {
|
||||
if (_jwtFuture != null) {
|
||||
Log.print('⏳ getJWT: تجديد قيد التنفيذ — إعادة استخدام نفس الـ future.');
|
||||
return _jwtFuture!;
|
||||
}
|
||||
_jwtFuture = _getJwtInternal(force: force).catchError((e) {
|
||||
return _onJwtFailure('exception: $e');
|
||||
});
|
||||
try {
|
||||
return await _jwtFuture!;
|
||||
} finally {
|
||||
_jwtFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _getJwtInternal({bool force = false}) async {
|
||||
if (!force && isTokenValid()) {
|
||||
Log.print("JWT is still valid. Skipping request.");
|
||||
return;
|
||||
_jwtFailures = 0;
|
||||
_jwtCooldownUntil = DateTime(2000);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (DateTime.now().isBefore(_jwtCooldownUntil)) {
|
||||
Log.print(
|
||||
'🛑 getJWT: بـ cooldown لمدة ${jwtCooldownRemaining.inSeconds}ث — تخطّي التجديد.');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
dev = Platform.isAndroid ? 'android' : 'ios';
|
||||
|
||||
// تأكد إن البصمة محدّثة قبل أي طلب
|
||||
await DeviceHelper.getDeviceFingerprint();
|
||||
final String fp = box.read(BoxName.deviceFpEncrypted) ?? '';
|
||||
|
||||
final passengerId = box.read(BoxName.passengerID);
|
||||
final isRegistering = passengerId == null || passengerId.toString().isEmpty;
|
||||
|
||||
if (box.read(BoxName.firstTimeLoadKey).toString() != 'false') {
|
||||
// ── أول تسجيل ─────────────────────────────────────────
|
||||
// نرسل البصمة المشفرة مع باقي البيانات
|
||||
// السيرفر سيعمل hash لها ويخزنها في JWT payload
|
||||
if (isRegistering && box.read(BoxName.firstTimeLoadKey).toString() != 'false') {
|
||||
var payload = {
|
||||
'id': box.read(BoxName.passengerID) ?? AK.newId,
|
||||
'id': passengerId ?? AK.newId,
|
||||
'password': AK.passnpassenger,
|
||||
'aud': '${AK.allowed}$dev',
|
||||
'fingerPrint': fp,
|
||||
@@ -110,12 +138,14 @@ class LoginController extends GetxController {
|
||||
var response = await http.post(
|
||||
Uri.parse(AppLink.loginFirstTime),
|
||||
body: payload,
|
||||
);
|
||||
Log.print('AppLink.loginFirstTime: ${AppLink.loginFirstTime}');
|
||||
|
||||
Log.print('payload: $payload');
|
||||
Log.print('response code: ${response.statusCode}');
|
||||
Log.print('response body: ${response.body}');
|
||||
).timeout(const Duration(seconds: 30));
|
||||
|
||||
if (response.statusCode == 429) {
|
||||
final retryAfter = int.tryParse(response.headers['retry-after'] ?? '') ?? 60;
|
||||
_jwtCooldownUntil = DateTime.now().add(Duration(seconds: retryAfter));
|
||||
Log.print('🛑 getJWT(firstTime): 429 — cooldown ${retryAfter}s');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decoded = jsonDecode(response.body);
|
||||
@@ -126,18 +156,20 @@ class LoginController extends GetxController {
|
||||
: decoded['jwt']);
|
||||
|
||||
if (jwt != null) {
|
||||
// نشفر الـ JWT بالتشفير الثلاثي قبل التخزين في GetStorage
|
||||
box.write(BoxName.jwt, c(jwt));
|
||||
storage.write(key: BoxName.jwt, value: c(jwt));
|
||||
await storage.write(key: BoxName.jwt, value: jwt);
|
||||
await EncryptionHelper.initialize();
|
||||
return _onJwtSuccess();
|
||||
}
|
||||
|
||||
await EncryptionHelper.initialize();
|
||||
return _onJwtFailure('firstTime: لا يوجد jwt بالرد');
|
||||
}
|
||||
return _onJwtFailure('firstTime: HTTP ${response.statusCode}');
|
||||
} else {
|
||||
// ── مستخدم موجود: تجديد التوكن
|
||||
if (isRegistering) {
|
||||
return _onJwtFailure('renew: لا يوجد passengerID');
|
||||
}
|
||||
|
||||
var payload = {
|
||||
'id': box.read(BoxName.passengerID),
|
||||
'id': passengerId,
|
||||
'fingerPrint': fp,
|
||||
'aud': '${AK.allowed}$dev',
|
||||
};
|
||||
@@ -145,11 +177,15 @@ class LoginController extends GetxController {
|
||||
var response = await http.post(
|
||||
Uri.parse(AppLink.loginJwtRider),
|
||||
body: payload,
|
||||
);
|
||||
Log.print('AppLink.loginJwtRider: ${AppLink.loginJwtRider}');
|
||||
).timeout(const Duration(seconds: 30));
|
||||
|
||||
if (response.statusCode == 429) {
|
||||
final retryAfter = int.tryParse(response.headers['retry-after'] ?? '') ?? 60;
|
||||
_jwtCooldownUntil = DateTime.now().add(Duration(seconds: retryAfter));
|
||||
Log.print('🛑 getJWT: 429 — cooldown ${retryAfter}ث');
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.print('payload: $payload');
|
||||
Log.print('response: ${response.body}');
|
||||
if (response.statusCode == 200) {
|
||||
final decoded = jsonDecode(response.body);
|
||||
final String? jwt = decoded['data'] != null
|
||||
@@ -159,16 +195,33 @@ class LoginController extends GetxController {
|
||||
: decoded['jwt']);
|
||||
|
||||
if (jwt != null) {
|
||||
box.write(BoxName.jwt, c(jwt));
|
||||
storage.write(key: BoxName.jwt, value: c(jwt));
|
||||
await storage.write(key: BoxName.jwt, value: jwt);
|
||||
return _onJwtSuccess();
|
||||
}
|
||||
return _onJwtFailure('renew: لا يوجد jwt بالرد');
|
||||
}
|
||||
return _onJwtFailure('renew: HTTP ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('Error in getJWT: $e');
|
||||
return _onJwtFailure('Error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
bool _onJwtSuccess() {
|
||||
_jwtFailures = 0;
|
||||
_jwtCooldownUntil = DateTime(2000);
|
||||
Log.print('✅ getJWT: تم توليد توكن جديد بنجاح.');
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _onJwtFailure(String reason) {
|
||||
_jwtFailures++;
|
||||
final seconds = _jwtFailures >= 6 ? 60 : (1 << _jwtFailures);
|
||||
_jwtCooldownUntil = DateTime.now().add(Duration(seconds: seconds));
|
||||
Log.print('❌ getJWT فشل ($reason) — محاولة #$_jwtFailures، cooldown ${seconds}ث');
|
||||
return false;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// التحقق من صلاحية التوكن يدوياً (بدون مكاتب خارجية)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
@@ -231,9 +284,67 @@ class LoginController extends GetxController {
|
||||
// • يرجع hmac مع الـ jwt ويخزنه في GetStorage
|
||||
// • الـ JWT لا يُشفَّر ثلاثياً (يُستخدم مباشرة في الـ header)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// • يُخزَّن التوكن ويُعاد استخدامه ما دام أمامه أكثر من 10 ثوانٍ
|
||||
// (عمره 300ث)، فلا نُسجّل دخولاً مع كل طلب محفظة.
|
||||
static Future<String?>? _walletJwtFuture;
|
||||
|
||||
Future<String?> getJwtWallet() async {
|
||||
// طلب قيد التنفيذ؟ شارك نتيجته بدل إطلاق طلب ثانٍ.
|
||||
if (_walletJwtFuture != null) {
|
||||
Log.print('⏳ getJwtWallet: طلب قيد التنفيذ — إعادة استخدام نفس الـ future.');
|
||||
return _walletJwtFuture!;
|
||||
}
|
||||
_walletJwtFuture = _getJwtWalletInternal();
|
||||
try {
|
||||
return await _walletJwtFuture!;
|
||||
} finally {
|
||||
_walletJwtFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// هل التوكن المخزَّن صالح ويخصّ الراكب الحالي؟
|
||||
/// نتحقق من `user_id` أيضاً كي لا يُعاد استخدام توكن حساب سابق
|
||||
/// على نفس الجهاز بعد تبديل المستخدم.
|
||||
bool _isWalletJwtUsable(String jwt, String passengerId) {
|
||||
try {
|
||||
final parts = jwt.split('.');
|
||||
if (parts.length != 3) return false;
|
||||
String payload = parts[1];
|
||||
while (payload.length % 4 != 0) {
|
||||
payload += '=';
|
||||
}
|
||||
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
|
||||
|
||||
if ((decoded['user_id'] ?? '').toString() != passengerId) return false;
|
||||
|
||||
final exp = decoded['exp'];
|
||||
if (exp == null) return false;
|
||||
// هامش 10 ثوانٍ حتى لا ينتهي التوكن أثناء الطلب نفسه
|
||||
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 10000);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _getJwtWalletInternal() async {
|
||||
dev = Platform.isAndroid ? 'android' : 'ios';
|
||||
|
||||
final String passengerId = box.read(BoxName.passengerID)?.toString() ?? '';
|
||||
|
||||
// ── إعادة استخدام التوكن المخزَّن إن كان صالحاً ────────────────
|
||||
final String cachedJwt = box.read(BoxName.walletJwt)?.toString() ?? '';
|
||||
final String cachedHmac = box.read(BoxName.hmac)?.toString() ?? '';
|
||||
if (cachedJwt.isNotEmpty &&
|
||||
cachedHmac.isNotEmpty &&
|
||||
passengerId.isNotEmpty &&
|
||||
_isWalletJwtUsable(cachedJwt, passengerId)) {
|
||||
Log.print('🔑 توكن محفظة صالح في التخزين — تخطّي طلب السيرفر.');
|
||||
return cachedJwt;
|
||||
}
|
||||
|
||||
// نعيد حساب البصمة أولاً كي لا نرسل قيمة GCM قديمة عالقة في التخزين
|
||||
// من نسخة سابقة من التطبيق (مثل getJWT تماماً).
|
||||
await DeviceHelper.getDeviceFingerprint();
|
||||
final String fp = box.read(BoxName.deviceFpEncrypted) ?? '';
|
||||
|
||||
var payload = {
|
||||
@@ -267,6 +378,11 @@ class LoginController extends GetxController {
|
||||
box.write(BoxName.hmac, hmac);
|
||||
}
|
||||
|
||||
// نخزّن التوكن ليُعاد استخدامه حتى قرب انتهائه بدل طلب جديد كل مرة
|
||||
if (jwt != null) {
|
||||
box.write(BoxName.walletJwt, jwt);
|
||||
}
|
||||
|
||||
return jwt;
|
||||
}
|
||||
|
||||
@@ -362,39 +478,39 @@ class LoginController extends GetxController {
|
||||
force: true); // Fetch access token after clearing firstTimeLoadKey
|
||||
|
||||
// ── 5. المقارنة: FCM token + fingerprint ──────────────────────
|
||||
if (email != '962798583052@tripzapp.com') {
|
||||
if (serverFCM == null &&
|
||||
tokenResp != null &&
|
||||
tokenResp != 'failure' &&
|
||||
tokenResp != 'error' &&
|
||||
tokenResp != 'token_expired') {
|
||||
final tokenJson =
|
||||
tokenResp is String ? jsonDecode(tokenResp) : tokenResp;
|
||||
final serverData = tokenJson['data'] ?? tokenJson['message'];
|
||||
if (serverData is Map) {
|
||||
serverFCM = serverData['token']?.toString() ?? '';
|
||||
serverFP = serverData['fingerPrint']?.toString() ?? '';
|
||||
}
|
||||
// ملاحظة: كان هنا استثناء لحساب اختبار بعينه يتخطى الفحص بالكامل.
|
||||
// حُذف — لا استثناءات على التحقق من تغيّر الجهاز.
|
||||
if (serverFCM == null &&
|
||||
tokenResp != null &&
|
||||
tokenResp != 'failure' &&
|
||||
tokenResp != 'error' &&
|
||||
tokenResp != 'token_expired') {
|
||||
final tokenJson =
|
||||
tokenResp is String ? jsonDecode(tokenResp) : tokenResp;
|
||||
final serverData = tokenJson['data'] ?? tokenJson['message'];
|
||||
if (serverData is Map) {
|
||||
serverFCM = serverData['token']?.toString() ?? '';
|
||||
serverFP = serverData['fingerPrint']?.toString() ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
if (serverFCM != null && serverFCM.isNotEmpty) {
|
||||
final localFCM = (box.read(BoxName.tokenFCM) ?? '').toString();
|
||||
if (serverFCM != null && serverFCM.isNotEmpty) {
|
||||
final localFCM = (box.read(BoxName.tokenFCM) ?? '').toString();
|
||||
|
||||
// ── اختلاف أي منهما = جهاز مختلف أو تثبيت جديد ─────────
|
||||
final fcmChanged = serverFCM != localFCM;
|
||||
final fpChanged =
|
||||
serverFP != null && serverFP.isNotEmpty && serverFP != localFP;
|
||||
// ── اختلاف أي منهما = جهاز مختلف أو تثبيت جديد ─────────
|
||||
final fcmChanged = serverFCM != localFCM;
|
||||
final fpChanged =
|
||||
serverFP != null && serverFP.isNotEmpty && serverFP != localFP;
|
||||
|
||||
if (fcmChanged || fpChanged) {
|
||||
mySnackbarInfo('Device Change Detected'.tr);
|
||||
await Get.to(() => OtpVerificationPage(
|
||||
phone: data['phone'].toString(),
|
||||
deviceToken: localFP,
|
||||
token: tokenResp ?? '',
|
||||
ptoken: serverFCM ?? '', // نمرر FCM القديم للـ OTP controller
|
||||
));
|
||||
return;
|
||||
}
|
||||
if (fcmChanged || fpChanged) {
|
||||
mySnackbarInfo('Device Change Detected'.tr);
|
||||
await Get.to(() => OtpVerificationPage(
|
||||
phone: data['phone'].toString(),
|
||||
deviceToken: localFP,
|
||||
token: tokenResp ?? '',
|
||||
ptoken: serverFCM ?? '', // نمرر FCM القديم للـ OTP controller
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,7 +598,7 @@ class LoginController extends GetxController {
|
||||
'aud': '${AK.allowed}${Platform.isAndroid ? 'android' : 'ios'}',
|
||||
};
|
||||
Log.print(
|
||||
"Tester Login Payload: email=${payload['email']}, password=${payload['password']}, aud=${payload['aud']}");
|
||||
"Tester Login Payload: email=${payload['email']}, aud=${payload['aud']}");
|
||||
|
||||
var response = await http.post(
|
||||
Uri.parse(AppLink.loginUsingCredentialsWithoutGooglePassenger),
|
||||
@@ -525,19 +641,27 @@ class LoginController extends GetxController {
|
||||
Get.offAll(() => const MapPagePassenger());
|
||||
} else {
|
||||
Log.print(
|
||||
"Tester Login Failed due to condition mismatch: status=${jsonDecoeded['status']}, verified=${jsonDecoeded['data']?[0]?['verified']}");
|
||||
Get.offAll(() => LoginPage());
|
||||
"Tester Login Condition mismatch: status=${jsonDecoeded['status']}, verified=${jsonDecoeded['data']?[0]?['verified']}");
|
||||
mySnackbarError(
|
||||
(jsonDecoeded['message'] ?? 'Login failed').toString().tr);
|
||||
isloading = false;
|
||||
update();
|
||||
}
|
||||
} else if (response.statusCode == 429) {
|
||||
Log.print("Tester Login rate limited: ${response.body}");
|
||||
mySnackbarError('Too many attempts. Please wait a minute.'.tr);
|
||||
isloading = false;
|
||||
update();
|
||||
} else {
|
||||
Log.print(
|
||||
"Tester Login Failed with status code: ${response.statusCode}, body: ${response.body}");
|
||||
"Tester Login HTTP error: ${response.statusCode}, body: ${response.body}");
|
||||
mySnackbarError('Server error'.tr);
|
||||
isloading = false;
|
||||
update();
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print("Tester Login Error: $e");
|
||||
Log.print("Tester Login Exception: $e");
|
||||
mySnackbarError('Network error'.tr);
|
||||
isloading = false;
|
||||
update();
|
||||
}
|
||||
@@ -594,7 +718,7 @@ class LoginController extends GetxController {
|
||||
|
||||
// Initialize geofencing now that we have permission
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
await TripzGeofencingService.initAndStart();
|
||||
await SiroGeofencingService.initAndStart();
|
||||
}
|
||||
|
||||
if (permissionGranted.toString() == 'PermissionStatus.granted' ||
|
||||
|
||||
Reference in New Issue
Block a user