Main coves client
1import 'package:flutter/foundation.dart';
2import 'package:flutter/material.dart';
3import 'package:url_launcher/url_launcher.dart';
4
5/// Utility class for safely launching external URLs
6///
7/// Provides security validation and error handling for opening URLs
8/// in external browsers or applications.
9class UrlLauncher {
10 UrlLauncher._(); // Private constructor to prevent instantiation
11
12 /// Allowed URL schemes for security
13 static const _allowedSchemes = ['http', 'https'];
14
15 /// Launches an external URL with security validation
16 ///
17 /// Returns true if the URL was successfully launched, false otherwise.
18 ///
19 /// Security:
20 /// - Only allows http and https schemes
21 /// - Blocks potentially malicious schemes (javascript:, file:, etc.)
22 /// - Opens in external browser for user control
23 ///
24 /// If [context] is provided and mounted, shows a user-friendly error message
25 /// when the URL cannot be opened.
26 static Future<bool> launchExternalUrl(
27 String url, {
28 BuildContext? context,
29 }) async {
30 try {
31 final uri = Uri.parse(url);
32
33 // Validate URL scheme for security
34 if (!_allowedSchemes.contains(uri.scheme.toLowerCase())) {
35 if (kDebugMode) {
36 debugPrint('Blocked non-http(s) URL scheme: ${uri.scheme}');
37 }
38 _showErrorIfPossible(context, 'Invalid link format');
39 return false;
40 }
41
42 // Check if URL can be launched
43 if (await canLaunchUrl(uri)) {
44 return await launchUrl(uri, mode: LaunchMode.externalApplication);
45 }
46
47 if (kDebugMode) {
48 debugPrint('Could not launch URL: $url');
49 }
50 // ignore: use_build_context_synchronously
51 _showErrorIfPossible(context, 'Could not open link');
52 return false;
53 } on FormatException catch (e) {
54 if (kDebugMode) {
55 debugPrint('Invalid URL format: $url - $e');
56 }
57 // ignore: use_build_context_synchronously
58 _showErrorIfPossible(context, 'Invalid link format');
59 return false;
60 } on Exception catch (e) {
61 if (kDebugMode) {
62 debugPrint('Error launching URL: $url - $e');
63 }
64 // ignore: use_build_context_synchronously
65 _showErrorIfPossible(context, 'Could not open link');
66 return false;
67 }
68 }
69
70 /// Shows an error snackbar if context is available and mounted
71 static void _showErrorIfPossible(BuildContext? context, String message) {
72 if (context != null && context.mounted) {
73 ScaffoldMessenger.of(
74 context,
75 ).showSnackBar(SnackBar(content: Text(message)));
76 }
77 }
78}