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}