From 49899da6b2c5506da9f83d8f43ee492267b8c0d3 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Tue, 16 Jun 2026 04:29:16 +0300 Subject: [PATCH] Update: 2026-06-16 04:29:16 --- .../lib/controller/functions/crud.dart | 39 ++-- .../controller/functions/session_manager.dart | 206 ++++++++++++++++++ siro_service/lib/main.dart | 27 +++ 3 files changed, 257 insertions(+), 15 deletions(-) create mode 100644 siro_service/lib/controller/functions/session_manager.dart diff --git a/siro_service/lib/controller/functions/crud.dart b/siro_service/lib/controller/functions/crud.dart index 884768e..82561c2 100644 --- a/siro_service/lib/controller/functions/crud.dart +++ b/siro_service/lib/controller/functions/crud.dart @@ -10,13 +10,13 @@ import 'package:siro_service/constant/links.dart'; import 'package:siro_service/controller/functions/encrypt_decrypt.dart'; import 'package:siro_service/env/env.dart'; import 'package:siro_service/controller/functions/security_helper.dart'; +import 'package:siro_service/controller/functions/session_manager.dart'; import 'package:siro_service/main.dart'; import 'package:siro_service/print.dart'; import '../../constant/api_key.dart'; class CRUD { - static bool _isRefreshingJWT = false; static String? _appSignature; static String _lastErrorSignature = ''; @@ -24,7 +24,7 @@ class CRUD { static const Duration _errorLogDebounceDuration = Duration(minutes: 1); // ── JWT Validity Check (No external libs) ────────────────────── - static bool _isJwtValid(String? token) { + static bool isJwtValid(String? token) { if (token == null || token.isEmpty) return false; try { final parts = token.split('.'); @@ -192,13 +192,16 @@ class CRUD { } if (sc == 401) { - if (!_isRefreshingJWT && !link.contains('errorApp.php')) { - _isRefreshingJWT = true; - try { - await getJWT(); - } finally { - _isRefreshingJWT = false; + // استخدام SessionManager لتجديد الجلسة عند 401 + if (Get.isRegistered()) { + final sessionManager = Get.find(); + if (!sessionManager.isRefreshing.value && + !link.contains('errorApp.php')) { + await sessionManager.refreshSession(silent: false); } + } else { + // fallback للتجديد القديم + await getJWT(); } return 'token_expired'; } @@ -220,15 +223,21 @@ class CRUD { }) async { String token = r(box.read(BoxName.jwt) ?? '').toString().split(Env.addd)[0]; - if (!_isJwtValid(token) && - !_isRefreshingJWT && - !link.contains('login.php')) { - _isRefreshingJWT = true; - try { + // استخدام SessionManager للتحقق من صلاحية الجلسة قبل كل طلب + bool isRefreshing = false; + if (Get.isRegistered()) { + isRefreshing = Get.find().isRefreshing.value; + } + + if (!isJwtValid(token) && !isRefreshing && !link.contains('login.php')) { + if (Get.isRegistered()) { + final sessionManager = Get.find(); + await sessionManager.refreshSession(silent: true); + token = r(box.read(BoxName.jwt) ?? '').toString().split(Env.addd)[0]; + } else { + // fallback: تجديد يدوي await getJWT(); token = r(box.read(BoxName.jwt) ?? '').toString().split(Env.addd)[0]; - } finally { - _isRefreshingJWT = false; } } diff --git a/siro_service/lib/controller/functions/session_manager.dart b/siro_service/lib/controller/functions/session_manager.dart new file mode 100644 index 0000000..85366eb --- /dev/null +++ b/siro_service/lib/controller/functions/session_manager.dart @@ -0,0 +1,206 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:siro_service/constant/box_name.dart'; +import 'package:siro_service/controller/functions/crud.dart'; +import 'package:siro_service/main.dart'; +import 'package:siro_service/print.dart'; + +/// مدير الجلسة - مسؤول عن مراقبة صلاحية الجلسة +/// وتجديدها تلقائياً عند انتهائها +class SessionManager extends GetxController { + static SessionManager get instance => Get.find(); + + /// حالة الجلسة الحالية + final Rx status = SessionStatus.valid.obs; + + /// هل يتم تجديد الجلسة حالياً؟ + final RxBool isRefreshing = false.obs; + + /// عدد مرات إعادة المحاولة في حال فشل التجديد + static const int _maxRetries = 3; + + @override + void onInit() { + super.onInit(); + Log.print('🔐 SessionManager initialized'); + } + + /// التحقق من صلاحية الجلسة الحالية + SessionStatus checkSessionValidity() { + try { + final token = box.read(BoxName.jwt)?.toString() ?? ''; + if (token.isEmpty) { + status.value = SessionStatus.expired; + return SessionStatus.expired; + } + + final isValid = CRUD.isJwtValid(token); + if (!isValid) { + status.value = SessionStatus.expired; + return SessionStatus.expired; + } + + status.value = SessionStatus.valid; + return SessionStatus.valid; + } catch (e) { + Log.print('⚠️ Session check error: $e'); + return SessionStatus.unknown; + } + } + + /// محاولة تجديد الجلسة تلقائياً مع إظهار إشعار للمستخدم + Future refreshSession({bool silent = false}) async { + // إذا كان التجديد قيد التنفيذ بالفعل، ننتظر + if (isRefreshing.value) { + Log.print('🔄 Session refresh already in progress, waiting...'); + // ننتظر حتى ينتهي التجديد الحالي + while (isRefreshing.value) { + await Future.delayed(const Duration(milliseconds: 500)); + } + // نتحقق مرة أخرى بعد انتهاء التجديد + final isValid = CRUD.isJwtValid(box.read(BoxName.jwt)?.toString() ?? ''); + return isValid; + } + + isRefreshing.value = true; + status.value = SessionStatus.refreshing; + + try { + // إظهار إشعار للمستخدم بأن الجلسة تنتهي ويتم تجديدها + if (!silent) { + _showSessionExpiredNotification(); + } + + // محاولة تجديد الجلسة مع إعادة المحاولة + for (int attempt = 1; attempt <= _maxRetries; attempt++) { + Log.print('🔄 Session refresh attempt $attempt/$_maxRetries'); + + await CRUD().getJWT(); + + // التحقق من نجاح التجديد + final newToken = box.read(BoxName.jwt)?.toString() ?? ''; + final isValid = CRUD.isJwtValid(newToken); + + if (isValid) { + status.value = SessionStatus.valid; + isRefreshing.value = false; + + // إشعار النجاح + if (!silent) { + _showSessionRefreshedNotification(); + } + + Log.print('✅ Session refreshed successfully'); + return true; + } + + // إذا فشلت المحاولة، انتظر قبل إعادة المحاولة + if (attempt < _maxRetries) { + await Future.delayed(Duration(seconds: attempt)); + } + } + + // فشلت كل محاولات التجديد + status.value = SessionStatus.expired; + isRefreshing.value = false; + + if (!silent) { + _showSessionExpiredFinalNotification(); + } + + Log.print('❌ Session refresh failed after $_maxRetries attempts'); + return false; + } catch (e) { + status.value = SessionStatus.expired; + isRefreshing.value = false; + Log.print('❌ Session refresh error: $e'); + + if (!silent) { + _showSessionExpiredFinalNotification(); + } + return false; + } + } + + /// إشعار للمستخدم: الجلسة على وشك الانتهاء ويتم تجديدها + void _showSessionExpiredNotification() { + if (!Get.isSnackbarOpen) { + Get.snackbar( + '⚠️ جاري تجديد الجلسة'.tr, + 'انتهت صلاحية الجلسة، يتم تجديدها تلقائياً...'.tr, + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.orange.withValues(alpha: 0.9), + colorText: Colors.white, + duration: const Duration(seconds: 3), + icon: const Icon( + Icons.refresh_rounded, + color: Colors.white, + size: 28, + ), + ); + } + } + + /// إشعار للمستخدم: تم تجديد الجلسة بنجاح + void _showSessionRefreshedNotification() { + Get.snackbar( + '✅ تم تجديد الجلسة'.tr, + 'تم تجديد الجلسة بنجاح، يمكنك متابعة العمل'.tr, + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.green.withValues(alpha: 0.9), + colorText: Colors.white, + duration: const Duration(seconds: 2), + ); + } + + /// إشعار للمستخدم: فشل تجديد الجلسة - يجب إعادة تسجيل الدخول + void _showSessionExpiredFinalNotification() { + Get.defaultDialog( + title: '🔒 انتهت الجلسة'.tr, + titleStyle: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), + middleText: + 'انتهت صلاحية الجلسة ولم نتمكن من تجديدها تلقائياً.\nيرجى إعادة تسجيل الدخول للمتابعة.' + .tr, + middleTextStyle: const TextStyle(fontSize: 16), + confirm: ElevatedButton( + onPressed: () { + Get.back(); + _forceLogout(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12), + ), + child: const Text('إعادة تسجيل الدخول'), + ), + barrierDismissible: false, + ); + } + + /// تسجيل الخروج القسري عند فشل تجديد الجلسة + void _forceLogout() { + try { + box.erase(); + } catch (_) {} + Get.offAllNamed('/login'); + } +} + +/// حالات الجلسة +enum SessionStatus { + /// الجلسة صالحة + valid, + + /// الجلسة منتهية الصلاحية + expired, + + /// جاري تجديد الجلسة + refreshing, + + /// حالة غير معروفة + unknown, +} diff --git a/siro_service/lib/main.dart b/siro_service/lib/main.dart index 3ae3416..8cb813e 100644 --- a/siro_service/lib/main.dart +++ b/siro_service/lib/main.dart @@ -1,9 +1,12 @@ +import 'dart:async'; + import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:siro_service/controller/firbase_messge.dart'; +import 'package:siro_service/controller/functions/session_manager.dart'; import 'package:siro_service/firebase_options.dart'; import 'controller/functions/encrypt_decrypt.dart'; @@ -14,6 +17,23 @@ import 'login_page.dart'; final box = GetStorage(); const storage = FlutterSecureStorage(); +/// فحص دوري لصلاحية الجلسة كل 30 ثانية +Timer? _sessionCheckTimer; + +void _startPeriodicSessionCheck() { + _sessionCheckTimer?.cancel(); + _sessionCheckTimer = Timer.periodic(const Duration(minutes: 30), (_) { + if (Get.isRegistered()) { + final sessionManager = Get.find(); + final status = sessionManager.checkSessionValidity(); + if (status == SessionStatus.expired) { + // محاولة تجديد الجلسة تلقائياً بصمت + sessionManager.refreshSession(silent: true); + } + } + }); +} + void main() async { WidgetsFlutterBinding.ensureInitialized(); await GetStorage.init(); @@ -27,6 +47,13 @@ void main() async { Firebase.app(); } Get.put(FirebaseMessagesController()).getToken(); + + // تهيئة SessionManager للتطبيق بالكامل + Get.put(SessionManager(), permanent: true); + + // بدء الفحص الدوري للجلسة + _startPeriodicSessionCheck(); + runApp(MyApp()); }