import 'package:openim_common/openim_common.dart' as openim_common; enum Environment { production, } class H5Line { const H5Line({ required this.label, required this.url, }); final String label; final String url; Uri get uri => Uri.parse(url); String get displayAddress { final parsed = uri; final host = parsed.hasPort ? '${parsed.host}:${parsed.port}' : parsed.host; final path = parsed.path.isEmpty || parsed.path == '/' ? '' : parsed.path; return '$host$path'; } } class AppConfig { static const Environment currentEnvironment = Environment.production; static const String appName = '本地打包'; static const String appLogo = ''; static const String canonicalWebHost = 'h5-test.imharry.work'; static const String _dartDefinedH5LineUrls = String.fromEnvironment('H5_LINE_URLS'); static const Set legacyWebHosts = { 'h5-test.imharry.work', }; static final Map> _environmentHosts = { Environment.production: [ canonicalWebHost, ], }; static List get environmentHosts { final generatedHosts = _normalizedUniqueUrls( openim_common.Config.environmentHosts, ); if (generatedHosts.isNotEmpty) { return generatedHosts; } return _normalizedUniqueUrls(_environmentHosts[currentEnvironment] ?? []); } static List get h5Lines { final configuredUrls = _dartDefinedH5LineUrls.trim().isEmpty ? environmentHosts : _normalizedUniqueUrls(_dartDefinedH5LineUrls.split(',')); final urls = configuredUrls.isEmpty ? _normalizedUniqueUrls(const [canonicalWebHost]) : configuredUrls; return [ for (var index = 0; index < urls.length; index += 1) H5Line(label: '线路${index + 1}', url: urls[index]), ]; } static String homeUrl({int lineIndex = 0, String? appName, String? appLogo}) { final lines = h5Lines; final safeIndex = _safeLineIndex(lineIndex, lines.length); return lines[safeIndex].url; } static String withFreshShellParams(String url) { return _removeShellParams(Uri.parse(url)).toString(); } static bool shouldRewriteMainFrameUrl(String url) { final uri = Uri.tryParse(url); if (uri == null) { return false; } final host = uri.host.toLowerCase(); return (legacyWebHosts.contains(host) && !_isConfiguredH5Host(host)) || _hasShellParams(uri); } static String canonicalizeMainFrameUrl(String url) { final uri = _removeShellParams(Uri.parse(url)); final host = uri.host.toLowerCase(); if (!legacyWebHosts.contains(host) || _isConfiguredH5Host(host)) { return uri.toString(); } return uri .replace( scheme: 'https', host: canonicalWebHost, ) .toString(); } static int? lineIndexForUrl(String url) { final uri = Uri.tryParse(url); if (uri == null) { return null; } final lines = h5Lines; for (var index = 0; index < lines.length; index += 1) { if (_isSameLine(lines[index].uri, uri)) { return index; } } return null; } static Uri _removeShellParams(Uri uri) { final queryParameters = Map.from(uri.queryParameters); queryParameters.remove('flutter_shell'); queryParameters.remove('shell_cache_bust'); queryParameters.remove('shell_app_name'); queryParameters.remove('shell_app_logo'); final fragmentParameters = Uri.splitQueryString(uri.fragment); fragmentParameters.remove('shell_app_name'); fragmentParameters.remove('shell_app_logo'); return uri.replace( queryParameters: queryParameters, fragment: fragmentParameters.isEmpty ? '' : Uri(queryParameters: fragmentParameters).query, ); } static bool _hasShellParams(Uri uri) { final fragmentParameters = Uri.splitQueryString(uri.fragment); return uri.queryParameters.containsKey('flutter_shell') || uri.queryParameters.containsKey('shell_cache_bust') || uri.queryParameters.containsKey('shell_app_name') || uri.queryParameters.containsKey('shell_app_logo') || fragmentParameters.containsKey('shell_app_name') || fragmentParameters.containsKey('shell_app_logo'); } static int _safeLineIndex(int index, int length) { if (length <= 0 || index < 0 || index >= length) { return 0; } return index; } static bool _isConfiguredH5Host(String host) { return h5Lines.any((line) => line.uri.host.toLowerCase() == host); } static bool _isSameLine(Uri lineUri, Uri uri) { if (lineUri.host.toLowerCase() != uri.host.toLowerCase()) { return false; } if (lineUri.hasPort && lineUri.port != uri.port) { return false; } if (lineUri.scheme.isNotEmpty && lineUri.scheme != uri.scheme) { return false; } final linePath = _pathWithTrailingSlash(lineUri.path); final uriPath = _pathWithTrailingSlash(uri.path); return linePath == '/' || uriPath.startsWith(linePath); } static String _pathWithTrailingSlash(String path) { if (path.isEmpty) { return '/'; } return path.endsWith('/') ? path : '$path/'; } static List _normalizedUniqueUrls(Iterable values) { final urls = []; final seen = {}; for (final value in values) { final normalized = _tryNormalizeHomeUrl(value); if (normalized == null || seen.contains(normalized)) { continue; } urls.add(normalized); seen.add(normalized); } return List.unmodifiable(urls); } static String? _tryNormalizeHomeUrl(String value) { try { return _normalizeHomeUrl(value); } catch (_) { return null; } } static String _normalizeHomeUrl(String host) { final value = host.trim(); if (value.isEmpty) { throw const FormatException('Empty H5 line URL'); } final normalized = value.startsWith('http://') || value.startsWith('https://') ? value : 'https://$value'; final uri = Uri.parse(normalized); final path = uri.path.isEmpty ? '/' : uri.path; return uri.replace(path: path).toString(); } }