Update: 2026-08-08 13:33:53

This commit is contained in:
Hamza-Ayed
2026-08-08 13:33:54 +03:00
parent fae0e4a38a
commit c8e8e481a6
7 changed files with 362 additions and 141 deletions
+40 -28
View File
@@ -28,31 +28,42 @@ class CRUD {
// ── فحص صلاحية JWT بدون مكتبات خارجية ──────────────────────
static bool _isJwtValid(String? token) {
if (token == null || token.isEmpty) return false;
try {
final parts = token.split('.');
if (parts.length != 3) return false;
// فك تشفير الـ payload (الجزء الثاني)
String payload = parts[1];
// إضافة padding للـ base64
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;
// نعتبر التوكن منتهي قبل 30 ثانية من انتهاء الصلاحية (buffer)
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
} catch (_) {
final decoded = LoginDriverController.decodeJwtPayload(token);
if (decoded == null) return false;
final exp = decoded['exp'];
if (exp is! int) return false;
// نعتبر التوكن منتهي قبل 30 ثانية من انتهاء الصلاحية (buffer)
if (DateTime.now().millisecondsSinceEpoch >= (exp * 1000 - 30000)) {
return false;
}
// توكن التسجيل صالح زمنياً لكن الباك إند يرفضه بـ 403 على أي endpoint
// محمي. ما دام عندنا driverID فالتسجيل انتهى ويلزمنا توكن driver.
if (LoginDriverController.isRegistrationToken(decoded) &&
(box.read(BoxName.driverID)?.toString().isNotEmpty ?? false)) {
return false;
}
return true;
}
/// الباك إند يردّ 403 بهذه الرسالة تحديداً عندما يكون التوكن من نوع
/// registration على endpoint محمي — وهي الحالة الوحيدة من حالات الـ 403
/// التي يحلّها تجديد التوكن (بقيّتها: بصمة جهاز / HMAC / سلامة التطبيق).
static bool _isUnauthorizedTokenType(String body) =>
body.contains('Token not authorized for this action');
/// endpoints عامة لا يتحقق فيها الباك إند من التوكن (طلب/تحقق OTP،
/// تسجيل الدخول، التسجيل، تسجيل الأخطاء). حجب الطلب عنها عند غياب
/// التوكن يقفل بالضبط المسار الذي يستعيد به التطبيق توكناً صالحاً —
/// وهو ما كان يترك جهازاً جديداً عالقاً بلا مخرج.
static bool _isPublicEndpoint(String link) =>
link.contains('/auth/otp/') ||
link.contains('login') ||
link.contains('register') ||
link.contains('errorApp.php');
static Future<void> addError(
String error, String details, String where) async {
try {
@@ -204,8 +215,9 @@ class CRUD {
return 'rate_limited';
}
// 401 → تجديد التوكن مرة واحدة ثم إعادة الطلب مرة واحدة فقط
if (sc == 401) {
// 401 (منتهي) أو 403 بتوكن تسجيل (بلا صلاحيات) →
// تجديد التوكن مرة واحدة ثم إعادة الطلب مرة واحدة فقط
if (sc == 401 || (sc == 403 && _isUnauthorizedTokenType(body))) {
// تخطي تجديد التوكن لـ endpoints غير حرجة (مثل تسجيل الأخطاء)
final isNonCritical = link.contains('errorApp.php');
if (isNonCritical || !allowRefresh) return 'token_expired';
@@ -245,11 +257,9 @@ class CRUD {
Map<String, dynamic>? payload,
}) async {
String token = await _ensureJwt();
if (token.isEmpty) {
if (token.isEmpty && !_isPublicEndpoint(link)) {
// إذا فشل الحصول على توكن، لا ترسل الطلب للباك إند لأنّه سيرفض حتماً.
if (!link.contains('login') && !link.contains('errorApp.php')) {
return 'token_expired';
}
return 'token_expired';
}
final headers = {
@@ -292,7 +302,9 @@ class CRUD {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') return response.body;
return jsonData['status'];
} else if (response.statusCode == 401) {
} else if (response.statusCode == 401 ||
(response.statusCode == 403 &&
_isUnauthorizedTokenType(response.body))) {
// تجديد التوكن مرة واحدة ثم إعادة الطلب مرة واحدة فقط
// (allowRefresh: false تمنع أي تكرار إضافي) — نفس سلوك _makeRequest.
if (!allowRefresh) return 'token_expired';