Skip to content

Commit f8296b2

Browse files
jigar-fatavism
andauthored
Sanitize user facing error (#8721)
* sanitize user errors * Delete app_setting_auth_session_test.dart * code review commetns * Update choose_payment_method.dart --------- Co-authored-by: atavism <atavism@users.noreply.github.com>
1 parent 287176b commit f8296b2

20 files changed

Lines changed: 108 additions & 76 deletions

assets/locales/en.po

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,3 +1523,15 @@ msgstr "Got it"
15231523

15241524
msgid "vpn_conflict_connect_anyway"
15251525
msgstr "Connect Anyway"
1526+
1527+
msgid "err_check_connection"
1528+
msgstr "Unable to connect. Check your internet connection."
1529+
1530+
msgid "err_service_unavailable"
1531+
msgstr "Service temporarily unavailable. Trying again..."
1532+
1533+
msgid "err_connection_failed"
1534+
msgstr "Connection failed. Please try again."
1535+
1536+
msgid "err_ruleset_failed"
1537+
msgstr "Unable to load routing configuration. Retrying..."

lib/core/extensions/error.dart

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,18 +78,69 @@ extension ErrorExetension on Object {
7878
if (description.contains('Cannot use your own code for promotion')) {
7979
return "referral_code_own_invalid".i18n;
8080
}
81-
return description;
81+
82+
final categoryKey = _classifyVpnError(description);
83+
if (categoryKey != null) return categoryKey.i18n;
84+
85+
return "an_error_occurred".i18n;
8286
}
8387

8488
if (this is StateError) {
85-
return (this as StateError).message;
89+
final categoryKey = _classifyVpnError((this as StateError).message);
90+
if (categoryKey != null) return categoryKey.i18n;
91+
return "an_error_occurred".i18n;
8692
}
8793
if (this is Exception) {
88-
return (this as Exception).toString();
94+
final categoryKey = _classifyVpnError((this as Exception).toString());
95+
if (categoryKey != null) return categoryKey.i18n;
96+
return "an_error_occurred".i18n;
8997
}
9098

91-
return "error_occurred".i18n;
99+
return "an_error_occurred".i18n;
100+
}
101+
}
102+
103+
/// Classifies VPN-related errors into user-friendly
104+
/// categories based on regex patterns.
105+
final List<(RegExp, String)> _vpnErrorPatterns = [
106+
(
107+
RegExp(
108+
r'no such host|dns|network is unreachable|i/o timeout|no route to host|connection refused',
109+
caseSensitive: false,
110+
),
111+
'err_check_connection',
112+
),
113+
(
114+
RegExp(r'\b503\b|service unavailable', caseSensitive: false),
115+
'err_service_unavailable',
116+
),
117+
(
118+
RegExp(r'ruleset|geosite|geoip|smart routing', caseSensitive: false),
119+
'err_ruleset_failed',
120+
),
121+
(
122+
RegExp(
123+
r'tunnel|tun device|setup failed|failed to start vpn|libbox',
124+
caseSensitive: false,
125+
),
126+
'err_connection_failed',
127+
),
128+
];
129+
130+
String? _classifyVpnError(String description) {
131+
if (description.isEmpty) return null;
132+
for (final (pattern, key) in _vpnErrorPatterns) {
133+
if (pattern.hasMatch(description)) return key;
92134
}
135+
return null;
136+
}
137+
138+
/// Returns a localized user-facing message for a raw error string. Use this
139+
/// at boundaries where errors arrive as plain strings (e.g. FFI results)
140+
/// rather than as `Exception` instances, instead of wrapping them in
141+
/// `Exception(...)` just to route through `localizedDescription`.
142+
String localizeRawError(String rawError) {
143+
return (_classifyVpnError(rawError) ?? 'an_error_occurred').i18n;
93144
}
94145

95146
/// Strips the radiance IPC prefix from error messages.

lib/core/services/app_purchase.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,15 +309,15 @@ class AppPurchase {
309309
final fetchResult = await lanternService.fetchUserData();
310310
final fetchedUser = fetchResult.fold((failure) {
311311
appLogger.warning(
312-
'[AppPurchase] Failed to fetch latest user data for purchase check: ${failure.localizedErrorMessage}',
312+
'[AppPurchase] Failed to fetch latest user data for purchase check: ${failure.error}',
313313
);
314314
return null;
315315
}, (user) => user);
316316

317317
final user = fetchedUser ??
318318
(await lanternService.getUserData()).fold((failure) {
319319
appLogger.warning(
320-
'[AppPurchase] Failed to load cached user data for purchase check: ${failure.localizedErrorMessage}',
320+
'[AppPurchase] Failed to load cached user data for purchase check: ${failure.error}',
321321
);
322322
return null;
323323
}, (user) => user);

lib/features/account/account.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ class Account extends HookConsumerWidget {
437437
result.fold(
438438
(l) {
439439
context.hideLoadingDialog();
440-
appLogger.error('Logout error: ${l.localizedErrorMessage}');
440+
appLogger.error('Logout error: ${l.error}');
441441
context.showSnackBar(l.localizedErrorMessage);
442442
},
443443
(user) {

lib/features/account/delete_account.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ class _DeleteAccountState extends ConsumerState<DeleteAccount> {
171171
result.fold(
172172
(failure) {
173173
appLogger
174-
.error('Account deletion failed: ${failure.localizedErrorMessage}');
174+
.error('Account deletion failed: ${failure.error}');
175175
context.hideLoadingDialog();
176176
context.showSnackBarError(failure.localizedErrorMessage);
177177
},

lib/features/auth/choose_payment_method.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ class ChoosePaymentMethod extends HookConsumerWidget {
215215
},
216216
onError: (error) {
217217
finishPaymentRedirect(paymentRedirectInFlight);
218+
218219
///error while subscribing
219220
appLogger.error('Error subscribing to plan: $error');
220221
if (error is StripeException) {
@@ -322,9 +323,7 @@ class ChoosePaymentMethod extends HookConsumerWidget {
322323
await result.fold<Future<void>>(
323324
(failure) async {
324325
context.hideLoadingDialog();
325-
appLogger.error(
326-
'Error redirecting to payment: ${failure.localizedErrorMessage}',
327-
);
326+
appLogger.error('Error redirecting to payment: ${failure.error}');
328327
context.showSnackBar(failure.localizedErrorMessage);
329328
},
330329
(url) async {

lib/features/auth/create_password.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class CreatePassword extends HookConsumerWidget {
104104
(failure) {
105105
context.hideLoadingDialog();
106106
appLogger.error(
107-
'Failed to create password: ${failure.localizedErrorMessage}',
107+
'Failed to create password: ${failure.error}',
108108
);
109109
context.showSnackBarError(failure.localizedErrorMessage);
110110
},

lib/features/home/provider/feature_flag_notifier.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class FeatureFlagNotifier extends _$FeatureFlagNotifier {
2020
result.fold(
2121
(failure) {
2222
appLogger.error(
23-
'Error fetching feature flags: ${failure.localizedErrorMessage}');
23+
'Error fetching feature flags: ${failure.error}');
2424
},
2525
(flags) {
2626
try {

lib/features/home/provider/home_notifier.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class HomeNotifier extends _$HomeNotifier {
2020
return result.fold(
2121
(failure) {
2222
appLogger.error(
23-
'Error getting user data: ${failure.localizedErrorMessage}',
23+
'Error getting user data: ${failure.error}',
2424
);
2525
throw Exception('Failed to get user data');
2626
},
@@ -38,7 +38,7 @@ class HomeNotifier extends _$HomeNotifier {
3838
result.fold(
3939
(failure) {
4040
appLogger.error(
41-
'Error fetching user data: ${failure.localizedErrorMessage}',
41+
'Error fetching user data: ${failure.error}',
4242
);
4343
},
4444
(userData) {
@@ -56,7 +56,7 @@ class HomeNotifier extends _$HomeNotifier {
5656
result.fold(
5757
(failure) {
5858
appLogger.error(
59-
'Error refreshing user data: ${failure.localizedErrorMessage}',
59+
'Error refreshing user data: ${failure.error}',
6060
);
6161
state = AsyncValue.error(failure, StackTrace.current);
6262
},

lib/features/language/language.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class LanguageListView extends HookConsumerWidget {
104104
either.fold(
105105
(failure) {
106106
appLogger
107-
.error('Error updating locale: ${failure.localizedErrorMessage}');
107+
.error('Error updating locale: ${failure.error}');
108108
},
109109
(r) {
110110
appLogger.debug('Locale updated to: $newLocale');

0 commit comments

Comments
 (0)