import 'dart:math'; class H5Line { const H5Line({ required this.label, required this.url, }); final String label; final String url; Uri get uri => Uri.parse(url); } class AppConfig { static const String appName = '本地打包'; static const String appLogo = ''; static const String clientConfigDevice = 'h5'; static const String clientConfigSerialNo = 'h5-domain'; static const String _dartDefinedH5LineUrls = String.fromEnvironment('H5_LINE_URLS'); static const String _dartDefinedClientConfigQueryUrl = String.fromEnvironment('CLIENT_CONFIG_QUERY_URL'); // Bootstrap only: used before /client_config/query returns dynamic H5 domains. static const String _bootstrapH5LineUrl = String.fromEnvironment( 'BOOTSTRAP_H5_LINE_URL', defaultValue: 'https://h5-test.imharry.work/'); static final Random _wildcardRandom = Random.secure(); static List get h5Lines { final configuredUrls = _dartDefinedH5LineUrls.trim().isEmpty ? _normalizedUniqueUrls(const [_bootstrapH5LineUrl]) : _normalizedUniqueUrls(_dartDefinedH5LineUrls.split(',')); final urls = configuredUrls.isEmpty ? _normalizedUniqueUrls(const [_bootstrapH5LineUrl]) : configuredUrls; return [ for (var index = 0; index < urls.length; index += 1) H5Line(label: '线路${index + 1}', url: urls[index]), ]; } static Map get clientConfigQueryPayload => const { 'device': clientConfigDevice, 'serialNo': clientConfigSerialNo, }; static List get clientConfigQueryUris { final dartDefinedUrl = _dartDefinedClientConfigQueryUrl.trim(); if (dartDefinedUrl.isNotEmpty) { return [Uri.parse(dartDefinedUrl)]; } final lines = h5Lines; if (lines.isEmpty) { return const []; } final homeUri = lines.first.uri; return [ homeUri.replace( path: '/client_config/query', queryParameters: const {}, fragment: '', ), homeUri.replace( path: '/api/user/client_config/query', queryParameters: const {}, fragment: '', ), ]; } static List h5LinesFromResourceUrl( String resourceUrl, { String Function()? wildcardFactory, }) { final urls = _normalizedUniqueUrls( splitResourceUrlLines(resourceUrl).map( (value) => _replaceWildcardHost( value, wildcardFactory: wildcardFactory, ), ), ); return [ for (var index = 0; index < urls.length; index += 1) H5Line(label: '线路${index + 1}', url: urls[index]), ]; } static List splitResourceUrlLines(String resourceUrl) { if (resourceUrl.trim().isEmpty) { return const []; } final lines = []; final seen = {}; for (final value in resourceUrl.split(RegExp(r'\\r\\n|\\n|\\r|\\t|[\r\n\t]+'))) { final line = value.trim(); if (line.isEmpty || seen.contains(line)) { continue; } lines.add(line); seen.add(line); } return List.unmodifiable(lines); } static bool isLoginPageUrl(String? url) { if (url == null || url.isEmpty) { return false; } final uri = Uri.tryParse(url); if (uri == null) { return false; } return _isLoginPath(uri.path) || _isLoginPath(uri.fragment); } static bool _isLoginPath(String path) { final normalized = path.trim(); if (normalized.isEmpty) { return false; } final pathOnly = normalized.split('?').first.split('#').first; final segments = pathOnly.split('/').where((segment) => segment.isNotEmpty); if (segments.isEmpty) { return false; } return segments.last == 'login'; } 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(); } static String _replaceWildcardHost( String value, { String Function()? wildcardFactory, }) { if (!value.contains('*.')) { return value; } return value.replaceAllMapped( '*.', (_) => '${(wildcardFactory ?? _randomWildcardLabel)()}.', ); } static String _randomWildcardLabel() { const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; return String.fromCharCodes( List.generate( 16, (_) => chars.codeUnitAt(_wildcardRandom.nextInt(chars.length)), ), ); } }