import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:siro_driver/controller/functions/encrypt_decrypt.dart'; import 'package:siro_driver/controller/functions/network/net_guard.dart'; import 'package:siro_driver/constant/box_name.dart'; import 'package:siro_driver/constant/links.dart'; import 'package:siro_driver/controller/auth/captin/login_captin_controller.dart'; import 'package:siro_driver/main.dart'; 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 '../../constant/api_key.dart'; import '../../views/widgets/error_snakbar.dart'; import 'gemeni.dart'; import 'upload_image.dart'; 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); static const Duration _errorLogDebounceDuration = Duration(minutes: 1); // ── فحص صلاحية 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 (_) { return false; } } static Future addError( String error, String details, String where) async { try { final currentErrorSignature = '$where-$error'; final now = DateTime.now(); if (currentErrorSignature == _lastErrorSignature && now.difference(_lastErrorTimestamp) < _errorLogDebounceDuration) { return; } _lastErrorSignature = currentErrorSignature; _lastErrorTimestamp = now; final userId = box.read(BoxName.driverID) ?? box.read(BoxName.passengerID); final userType = box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger'; final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver); CRUD().post( link: AppLink.addError, payload: { 'error': error.toString(), 'userId': userId.toString(), 'userType': userType, 'phone': phone.toString(), 'device': where, 'details': details, }, ); } catch (e) {} } // ───────────────────────────────────────────────────────────── // دالة مساعدة: يجيب البصمة المشفرة من GetStorage // نفس القيمة المرسلة عند login وعُملها hash في JWT // السيرفر يتحقق: sha256(X-Device-FP + FP_PEPPER) == JWT.fingerPrint // ───────────────────────────────────────────────────────────── String _getFpHeader() { return box.read(BoxName.deviceFingerprint)?.toString() ?? ''; } // ═══════════════════════════════════════════════════════════════ // _makeRequest — دالة مركزية لكل الطلبات // ─────────────────────────────────────────────────────────────── // Retry logic للشبكات الضعيفة (سوريا): // • 3 محاولات لأخطاء الشبكة (SocketException / TimeoutException) // • انتظار 1 ثانية بين المحاولات لأخطاء SocketException // • بدون انتظار لأخطاء Timeout (نعيد فوراً) // ═══════════════════════════════════════════════════════════════ Future _makeRequest({ required String link, Map? payload, required Map headers, }) async { // timeouts مرتفعة مناسبة للإنترنت الضعيف في سوريا const totalTimeout = Duration(seconds: 60); Future doPost() { final url = Uri.parse(link); return http .post(url, body: payload, headers: headers) .timeout(totalTimeout); } http.Response? response; int attempts = 0; final requestId = DateTime.now().millisecondsSinceEpoch.toString().substring(7); Log.print('🚀 [REQ-$requestId] $link'); if (payload != null) Log.print('📦 [PAYLOAD-$requestId] $payload'); while (attempts < 3) { try { attempts++; response = await doPost(); break; } on SocketException catch (_) { Log.print('⚠️ SocketException attempt $attempts — $link'); if (attempts >= 3) { _netGuard.notifyOnce((title, msg) => mySnackeBarError(msg)); return 'no_internet'; } await Future.delayed(Duration(seconds: attempts)); } on TimeoutException catch (_) { Log.print('⚠️ TimeoutException attempt $attempts — $link'); if (attempts >= 3) return 'failure'; await Future.delayed(Duration(milliseconds: 500 * attempts)); } catch (e) { if (e.toString().contains('errno = 9') && attempts < 3) { await Future.delayed(const Duration(milliseconds: 500)); continue; } addError( 'HTTP Exception: $e', 'Try: $attempts', 'CRUD._makeRequest $link'); return 'failure'; } } // لو كل المحاولات فشلت بدون response if (response == null) return 'failure'; final sc = response.statusCode; final body = response.body; Log.print('📥 [RES-$requestId] [$sc] $link'); Log.print('📄 [BODY-$requestId] $body'); // 2xx if (sc >= 200 && sc < 300) { try { return jsonDecode(body); } catch (e, st) { addError( 'JSON Decode Error', 'Body: $body\n$st', 'CRUD._makeRequest $link'); return 'failure'; } } // 401 → تجديد التوكن (مع حماية من الحلقة اللانهائية) if (sc == 401) { // تخطي تجديد التوكن لـ endpoints غير حرجة (مثل تسجيل الأخطاء) final isNonCritical = link.contains('errorApp.php'); if (!_isRefreshingJWT && !isNonCritical) { _isRefreshingJWT = true; try { await Get.put(LoginDriverController()).getJWT(); } finally { _isRefreshingJWT = false; } } return 'token_expired'; } // 5xx if (sc >= 500) { addError('Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link'); return 'failure'; } return 'failure'; } // ═══════════════════════════════════════════════════════════════ // post — طلب POST للسائق // التغيير: إضافة X-Device-FP header // ═══════════════════════════════════════════════════════════════ Future post({ required String link, Map? payload, }) async { String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; // فحص صلاحية التوكن قبل الإرسال — تجنب طلب مضمون الرفض if (!_isJwtValid(token) && !_isRefreshingJWT) { _isRefreshingJWT = true; try { await Get.put(LoginDriverController()).getJWT(); token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; } finally { _isRefreshingJWT = false; } } final headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer $token', 'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز }; return await _makeRequest(link: link, payload: payload, headers: headers); } // ═══════════════════════════════════════════════════════════════ // get — طلب GET للسائق (يستخدم POST method) // التغيير: إضافة X-Device-FP header + timeout مناسب لسوريا // ═══════════════════════════════════════════════════════════════ Future get({ required String link, Map? payload, }) async { try { // فحص صلاحية التوكن قبل الإرسال String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; if (!_isJwtValid(token) && !_isRefreshingJWT) { _isRefreshingJWT = true; try { await Get.put(LoginDriverController()).getJWT(); token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; } finally { _isRefreshingJWT = false; } } 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(), }, ).timeout(const Duration(seconds: 60)); Log.print('get [$link]: ${response.statusCode}'); Log.print('get body: ${response.body}'); if (response.statusCode == 200) { var jsonData = jsonDecode(response.body); if (jsonData['status'] == 'success') return response.body; return jsonData['status']; } else if (response.statusCode == 401) { if (!_isRefreshingJWT) { _isRefreshingJWT = true; try { await Get.put(LoginDriverController()).getJWT(); } finally { _isRefreshingJWT = false; } } return 'token_expired'; } else { addError('Non-200: ${response.statusCode}', 'crud().get - Other', url.toString()); return 'failure'; } } on TimeoutException { return 'failure'; } on SocketException { return 'no_internet'; } catch (e) { addError('GET Exception: $e', '', link); return 'failure'; } } // ═══════════════════════════════════════════════════════════════ // postWallet — طلب POST للمحفظة // التغيير: إضافة X-Device-FP header // 3 headers: JWT + HMAC + FP // ═══════════════════════════════════════════════════════════════ Future postWallet({ required String link, Map? payload, }) async { var jwt = await LoginDriverController().getJwtWallet(); final hmac = box.read(BoxName.hmac); final headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer $jwt', 'X-HMAC-Auth': hmac.toString(), 'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز }; return await _makeRequest(link: link, payload: payload, headers: headers); } // ═══════════════════════════════════════════════════════════════ // getWallet — طلب GET للمحفظة (يستخدم POST method) // التغيير: إضافة X-Device-FP header // ═══════════════════════════════════════════════════════════════ Future getWallet({ required String link, Map? payload, }) async { var s = await LoginDriverController().getJwtWallet(); final hmac = box.read(BoxName.hmac); var url = Uri.parse(link); var response = await _client.post( url, body: payload, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer $s', 'X-HMAC-Auth': hmac.toString(), 'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز }, ).timeout(const Duration(seconds: 60)); if (response.statusCode == 200) { 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(LoginDriverController()).getJwtWallet(); return 'token_expired'; } addError('Unauthorized: ${jsonData['error']}', 'crud().getWallet - 401', url.toString()); return 'failure'; } else { addError('Non-200: ${response.statusCode}', 'crud().getWallet - Other', url.toString()); return 'failure'; } } // ═══════════════════════════════════════════════════════════════ // postWalletMtn — طلب MTN للمحفظة // التغيير: إضافة X-Device-FP header // ═══════════════════════════════════════════════════════════════ Future postWalletMtn({ required String link, Map? payload, }) async { final s = await LoginDriverController().getJwtWallet(); final hmac = box.read(BoxName.hmac); final url = Uri.parse(link); try { final response = await _client.post( url, body: payload, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer $s', 'X-HMAC-Auth': hmac.toString(), 'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز }, ).timeout(const Duration(seconds: 60)); Map wrap(String status, {Object? message, int? code}) { return { 'status': status, 'message': message, '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(LoginDriverController()).getJWT(); return { 'status': 'failure', 'message': 'token_expired', 'code': 401 }; } return wrap('failure', message: jsonData); } catch (_) { return wrap('failure', message: response.body); } } else { try { return wrap('failure', message: jsonDecode(response.body)); } catch (_) { return wrap('failure', message: response.body); } } } catch (e) { return { 'status': 'failure', 'message': 'HTTP request error: $e', 'code': -1 }; } } // ======================================================================= // باقي الدوال الخارجية — لا تحتاج X-Device-FP (APIs خارجية) // ======================================================================= Future getAgoraToken({ required String channelName, required String uid, }) async { var uid = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver); var res = await _client.get( Uri.parse( 'https://orca-app-b2i85.ondigitalocean.app/token?channelName=$channelName'), headers: {'Authorization': 'Bearer ${AK.agoraAppCertificate}'}, ); if (res.statusCode == 200) { return jsonDecode(res.body)['token']; } } Future getLlama({ required String link, required String payload, required String prompt, }) async { var url = Uri.parse(link); // API key مشفر ومحمي في api_key.dart عبر X.r() ثلاثي المستوى var apiKey = AK.llamaKey; var headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer $apiKey', }; var data = json.encode({ 'model': 'Llama-3-70b-Inst-FW', 'messages': [ { 'role': 'user', 'content': 'Extract the desired information from the following passage as json decoded like $prompt just in this:\n\n$payload', } ], 'temperature': 0.9, }); var response = await _client.post(url, body: data, headers: headers); if (response.statusCode == 200) return response.body; return response.statusCode; } Future allMethodForAI(String prompt, linkPHP, imagePath) async { await ImageController().choosImage(linkPHP, imagePath); Future.delayed(const Duration(seconds: 2)); var extractedString = await arabicTextExtractByVisionAndAI(imagePath: imagePath); var json = jsonDecode(extractedString); var textValues = extractTextFromLines(json); await Get.put(AI()).anthropicAI(textValues, prompt, imagePath); } String extractTextFromLines(Map jsonData) { final readResult = jsonData['readResult']; final blocks = readResult['blocks']; final buffer = StringBuffer(); for (final block in blocks) { for (final line in block['lines']) { buffer.write(line['text']); buffer.write('\n'); } } return buffer.toString().trim(); } Future arabicTextExtractByVisionAndAI( {required String imagePath}) async { var headers = { 'Content-Type': 'application/json', 'Ocp-Apim-Subscription-Key': AK.ocpApimSubscriptionKey, }; String imagePathFull = '${AppLink.server}/card_image/$imagePath-${box.read(BoxName.driverID)}.jpg'; var request = http.Request( 'POST', Uri.parse( 'https://eastus.api.cognitive.microsoft.com/computervision/imageanalysis:analyze?features=caption,read&model-version=latest&language=en&api-version=2024-02-01'), ); request.body = json.encode({'url': imagePathFull}); request.headers.addAll(headers); http.StreamedResponse response = await request.send(); if (response.statusCode == 200) return await response.stream.bytesToString(); } Future getChatGPT( {required String link, required String payload}) async { var url = Uri.parse(link); var headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer ${Env.chatGPTkeySeferNew}', }; var data = json.encode({ 'model': 'gpt-3.5-turbo', 'messages': [ { 'role': 'user', 'content': 'Extract the desired information from the following passage as json decoded like vin,make,made,year,expiration_date,color,owner,registration_date just in this:\n\n$payload', } ], 'temperature': 0.9, }); var response = await _client.post(url, body: data, headers: headers); if (response.statusCode == 200) return response.body; return response.statusCode; } Future postPayMob( {required String link, Map? payload}) async { var url = Uri.parse(link); var response = await _client.post(url, body: payload, headers: {'Content-Type': 'application/json'}); var jsonData = jsonDecode(response.body); if (response.statusCode == 200) { if (jsonData['status'] == 'success') return response.body; return jsonData['status']; } return response.statusCode; } // ── sendEmail — إصلاح: استخدام r() بدل X.r() القديم ───────── Future sendEmail(String link, Map? payload) async { // r() هي نفس دالة فك التشفير الثلاثي المختصرة String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; if (!_isJwtValid(token)) { await LoginDriverController().getJWT(); token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; } final headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer $token', 'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز }; final request = http.Request('POST', Uri.parse(link)); request.bodyFields = payload ?? {}; request.headers.addAll(headers); final response = await request.send(); if (response.statusCode != 200) { final responseBody = await response.stream.bytesToString(); addError('sendEmail failed: ${response.statusCode}', responseBody, 'CRUD.sendEmail'); } } Future postFromDialogue( {required String link, Map? payload}) async { var url = Uri.parse(link); var response = await _client.post( url, body: payload, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}', }, ); if (response.body.isNotEmpty) { var jsonData = jsonDecode(response.body); if (response.statusCode == 200 && jsonData['status'] == 'success') { Get.back(); return response.body; } return jsonData['status']; } } Future sendVerificationRequest(String phoneNumber) async { final accountSid = AK.accountSIDTwillo; final authToken = AK.authTokenTwillo; final verifySid = AK.twilloRecoveryCode; await _client.post( Uri.parse( 'https://verify.twilio.com/v2/Services/$verifySid/Verifications'), headers: { 'Authorization': 'Basic ' + base64Encode(utf8.encode('$accountSid:$authToken')), 'Content-Type': 'application/x-www-form-urlencoded', }, body: {'To': phoneNumber, 'Channel': 'sms'}, ); } Future getGoogleApi( {required String link, Map? payload}) async { var url = Uri.parse(link); var response = await _client.post(url, body: payload); var jsonData = jsonDecode(response.body); if (jsonData['status'] == 'OK') return jsonData; return jsonData['status']; } Future update({ required String endpoint, required Map data, required String id, }) async { var url = Uri.parse('$endpoint/$id'); var response = await http.put( url, body: json.encode(data), headers: { 'Authorization': 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}' }, ); return json.decode(response.body); } Future delete({required String endpoint, required String id}) async { var url = Uri.parse('$endpoint/$id'); var response = await http.delete( url, headers: { 'Authorization': 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}' }, ); return json.decode(response.body); } Future getMapSaas({ required String link, }) async { var url = Uri.parse(link); try { var response = await _client.get( url, headers: { 'Content-Type': 'application/json', 'x-api-key': Env.mapSaasKey, }, ); Log.print('link -MapSaas: $link'); Log.print('response -MapSaas: ${response.body}'); if (response.statusCode == 200) { return jsonDecode(response.body); } Log.print('MapSaas Error: ${response.statusCode} - ${response.body}'); return null; } catch (e) { Log.print('MapSaas Exception: $e'); return null; } } Future postMapSaas({ required String link, required Map payload, }) async { var url = Uri.parse(link); try { var response = await _client.post( url, body: jsonEncode(payload), headers: { 'Content-Type': 'application/json', 'x-api-key': Env.mapSaasKey, }, ); Log.print('post -MapSaas link: $link'); Log.print('post -MapSaas payload: $payload'); Log.print('post -MapSaas response: ${response.body}'); if (response.statusCode == 200 || response.statusCode == 201) { return jsonDecode(response.body); } Log.print( 'MapSaas Post Error: ${response.statusCode} - ${response.body}'); return null; } catch (e) { Log.print('MapSaas Post Exception: $e'); return null; } } } class NoInternetException implements Exception { final String message; NoInternetException( [this.message = 'No internet connection. Please check your network and try again.']); @override String toString() => message; } class WeakNetworkException implements Exception { final String message; WeakNetworkException( [this.message = 'Your network connection is too slow. Please try again later.']); @override String toString() => message; } class ApiException implements Exception { final String message; final int? statusCode; ApiException(this.message, [this.statusCode]); @override String toString() => 'ApiException: $message (Status Code: ${statusCode ?? 'N/A'})'; }