Update: 2026-08-06 18:32:03
This commit is contained in:
@@ -2,3 +2,7 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.useLibSizedPageAlignment=true
|
||||
# This builtInKotlin flag was added automatically by Flutter migrator
|
||||
android.builtInKotlin=false
|
||||
# This newDsl flag was added automatically by Flutter migrator
|
||||
android.newDsl=false
|
||||
|
||||
@@ -1,8 +1,41 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'api_config.dart';
|
||||
import 'hmac_interceptor.dart';
|
||||
import '../storage/secure_storage.dart';
|
||||
|
||||
/// Request/response fields that must never reach a log sink.
|
||||
const _sensitiveKeys = {
|
||||
'password',
|
||||
'password_confirmation',
|
||||
'old_password',
|
||||
'new_password',
|
||||
'otp',
|
||||
'access_token',
|
||||
'refresh_token',
|
||||
'device_secret',
|
||||
'push_token',
|
||||
};
|
||||
|
||||
/// Replaces sensitive values with a placeholder, recursing into nested maps.
|
||||
///
|
||||
/// Logging raw payloads printed live credentials into Xcode/logcat, where they
|
||||
/// persist well beyond the debug session.
|
||||
Object? _redact(Object? data) {
|
||||
if (data is Map) {
|
||||
return data.map((key, value) => MapEntry(
|
||||
key,
|
||||
_sensitiveKeys.contains(key.toString().toLowerCase())
|
||||
? '***'
|
||||
: _redact(value),
|
||||
));
|
||||
}
|
||||
if (data is List) {
|
||||
return data.map(_redact).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
class DioClient {
|
||||
static const String baseUrl = ApiConfig.baseUrl;
|
||||
late final Dio dio;
|
||||
@@ -23,29 +56,33 @@ class DioClient {
|
||||
// Add Interceptors
|
||||
dio.interceptors.add(HmacInterceptor(SecureStorage()));
|
||||
|
||||
// Custom Logging Interceptor as requested
|
||||
dio.interceptors.add(InterceptorsWrapper(
|
||||
onRequest: (options, handler) {
|
||||
print('--- API REQUEST ---');
|
||||
print('URL: ${options.method} ${options.uri}');
|
||||
print('Payload: ${options.data}');
|
||||
return handler.next(options);
|
||||
},
|
||||
onResponse: (response, handler) {
|
||||
print('--- API RESPONSE ---');
|
||||
print('URL: ${response.requestOptions.method} ${response.requestOptions.uri}');
|
||||
print('Status Code: ${response.statusCode}');
|
||||
print('Response: ${response.data}');
|
||||
return handler.next(response);
|
||||
},
|
||||
onError: (DioException e, handler) {
|
||||
print('--- API ERROR ---');
|
||||
print('URL: ${e.requestOptions.method} ${e.requestOptions.uri}');
|
||||
print('Status Code: ${e.response?.statusCode}');
|
||||
print('Response: ${e.response?.data ?? e.message}');
|
||||
return handler.next(e);
|
||||
},
|
||||
));
|
||||
// Logging interceptor. Debug builds only, and credentials are redacted:
|
||||
// these used to be bare print() calls, which also ran in release builds.
|
||||
if (kDebugMode) {
|
||||
dio.interceptors.add(InterceptorsWrapper(
|
||||
onRequest: (options, handler) {
|
||||
debugPrint('--- API REQUEST ---');
|
||||
debugPrint('URL: ${options.method} ${options.uri}');
|
||||
debugPrint('Payload: ${_redact(options.data)}');
|
||||
return handler.next(options);
|
||||
},
|
||||
onResponse: (response, handler) {
|
||||
debugPrint('--- API RESPONSE ---');
|
||||
debugPrint(
|
||||
'URL: ${response.requestOptions.method} ${response.requestOptions.uri}');
|
||||
debugPrint('Status Code: ${response.statusCode}');
|
||||
debugPrint('Response: ${_redact(response.data)}');
|
||||
return handler.next(response);
|
||||
},
|
||||
onError: (DioException e, handler) {
|
||||
debugPrint('--- API ERROR ---');
|
||||
debugPrint('URL: ${e.requestOptions.method} ${e.requestOptions.uri}');
|
||||
debugPrint('Status Code: ${e.response?.statusCode}');
|
||||
debugPrint('Response: ${_redact(e.response?.data) ?? e.message}');
|
||||
return handler.next(e);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Dio get client => dio;
|
||||
|
||||
@@ -20,6 +20,37 @@ class AuthController extends GetxController {
|
||||
var isLoading = false.obs;
|
||||
var phone = ''.obs;
|
||||
|
||||
/// Turns a DioException into a message that reflects what actually failed.
|
||||
///
|
||||
/// Without this, any transport failure (expired TLS cert, no network, DNS)
|
||||
/// surfaced as "wrong credentials", which sent users hunting for a password
|
||||
/// problem that did not exist.
|
||||
String _describeDioError(DioException e,
|
||||
{String fallback = 'بيانات الدخول غير صحيحة'}) {
|
||||
final data = e.response?.data;
|
||||
if (data is Map && data['message'] != null) {
|
||||
return data['message'].toString();
|
||||
}
|
||||
|
||||
switch (e.type) {
|
||||
case DioExceptionType.connectionTimeout:
|
||||
case DioExceptionType.sendTimeout:
|
||||
case DioExceptionType.receiveTimeout:
|
||||
return 'انتهت مهلة الاتصال بالخادم. تحقق من اتصالك بالإنترنت.';
|
||||
case DioExceptionType.badCertificate:
|
||||
return 'تعذّر التحقق من شهادة أمان الخادم. يرجى التواصل مع الدعم.';
|
||||
case DioExceptionType.connectionError:
|
||||
case DioExceptionType.unknown:
|
||||
final msg = e.error?.toString() ?? e.message ?? '';
|
||||
if (msg.contains('CERTIFICATE') || msg.contains('HandshakeException')) {
|
||||
return 'تعذّر التحقق من شهادة أمان الخادم. يرجى التواصل مع الدعم.';
|
||||
}
|
||||
return 'تعذّر الاتصال بالخادم. تحقق من اتصالك بالإنترنت.';
|
||||
default:
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> requestOtp(String phoneNumber) async {
|
||||
try {
|
||||
if (phoneNumber.trim().isEmpty) {
|
||||
@@ -120,9 +151,9 @@ class AuthController extends GetxController {
|
||||
}
|
||||
}
|
||||
} on DioException catch (e, stackTrace) {
|
||||
AppLogger.error('OTP Verify Failed', e.response?.data, stackTrace);
|
||||
AppLogger.error('OTP Verify Failed', e.response?.data ?? e.message, stackTrace);
|
||||
AppSnackbar.showError(
|
||||
'خطأ', e.response?.data['message'] ?? 'رمز التحقق غير صحيح');
|
||||
'خطأ', _describeDioError(e, fallback: 'رمز التحقق غير صحيح'));
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
@@ -199,12 +230,8 @@ class AuthController extends GetxController {
|
||||
}
|
||||
}
|
||||
} on DioException catch (e, stackTrace) {
|
||||
AppLogger.error('Email Login Failed', e.response?.data, stackTrace);
|
||||
String errorMessage = 'بيانات الدخول غير صحيحة';
|
||||
if (e.response?.data != null && e.response?.data is Map) {
|
||||
errorMessage = e.response?.data['message'] ?? errorMessage;
|
||||
}
|
||||
AppSnackbar.showError('خطأ', errorMessage);
|
||||
AppLogger.error('Email Login Failed', e.response?.data ?? e.message, stackTrace);
|
||||
AppSnackbar.showError('خطأ', _describeDioError(e));
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: musadaq_app
|
||||
description: Jordanian E-Invoicing Automation SaaS
|
||||
publish_to: 'none'
|
||||
version: 1.0.6+6
|
||||
version: 1.0.7+7
|
||||
|
||||
environment:
|
||||
sdk: '>=3.2.0 <4.0.0'
|
||||
|
||||
Reference in New Issue
Block a user