288 lines
7.5 KiB
Dart
288 lines
7.5 KiB
Dart
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);
|
|
}
|
|
|
|
enum Environment {
|
|
development,
|
|
staging,
|
|
production,
|
|
}
|
|
|
|
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 const Environment currentEnvironment = Environment.production;
|
|
// Compatibility for the packaging service's legacy domain rewrite step.
|
|
static final Map<Environment, List<String>> _environmentHosts = {
|
|
Environment.production: [
|
|
_bootstrapH5LineUrl,
|
|
],
|
|
};
|
|
static final Random _wildcardRandom = Random.secure();
|
|
|
|
static List<H5Line> get h5Lines {
|
|
final configuredUrls = _dartDefinedH5LineUrls.trim().isEmpty
|
|
? _normalizedUniqueUrls(defaultH5LineUrlsForBaseUri(Uri.base))
|
|
: _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<String, String> get clientConfigQueryPayload => const {
|
|
'device': clientConfigDevice,
|
|
'serialNo': clientConfigSerialNo,
|
|
};
|
|
|
|
static List<Uri> get clientConfigQueryUris {
|
|
final dartDefinedUrl = _dartDefinedClientConfigQueryUrl.trim();
|
|
if (dartDefinedUrl.isNotEmpty) {
|
|
return [Uri.parse(dartDefinedUrl)];
|
|
}
|
|
|
|
final lines = h5Lines;
|
|
if (lines.isEmpty) {
|
|
return const [];
|
|
}
|
|
|
|
return clientConfigQueryUrisForHomeUri(lines.first.uri);
|
|
}
|
|
|
|
static List<String> defaultH5LineUrlsForBaseUri(Uri baseUri) {
|
|
final runtimeHomeUrl = _runtimeHomeUrlFromBaseUri(baseUri);
|
|
if (runtimeHomeUrl != null) {
|
|
return [runtimeHomeUrl];
|
|
}
|
|
|
|
final environmentHosts = _environmentHosts[currentEnvironment];
|
|
if (environmentHosts == null || environmentHosts.isEmpty) {
|
|
return const [_bootstrapH5LineUrl];
|
|
}
|
|
return environmentHosts;
|
|
}
|
|
|
|
static List<Uri> clientConfigQueryUrisForHomeUri(Uri homeUri) {
|
|
return [
|
|
_originUriWithPath(homeUri, '/client_config/query'),
|
|
_originUriWithPath(homeUri, '/api/user/client_config/query'),
|
|
];
|
|
}
|
|
|
|
static List<H5Line> 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<String> splitResourceUrlLines(String resourceUrl) {
|
|
if (resourceUrl.trim().isEmpty) {
|
|
return const [];
|
|
}
|
|
|
|
final lines = <String>[];
|
|
final seen = <String>{};
|
|
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<String> _normalizedUniqueUrls(Iterable<String> values) {
|
|
final urls = <String>[];
|
|
final seen = <String>{};
|
|
|
|
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? _runtimeHomeUrlFromBaseUri(Uri baseUri) {
|
|
if (_isLocalRuntimeUri(baseUri)) {
|
|
return null;
|
|
}
|
|
|
|
return _originUriWithPath(baseUri, '/').toString();
|
|
}
|
|
|
|
static Uri _originUriWithPath(Uri uri, String path) {
|
|
final normalizedPath = path.startsWith('/') ? path : '/$path';
|
|
return Uri.parse('${uri.scheme}://${uri.authority}$normalizedPath');
|
|
}
|
|
|
|
static bool _isLocalRuntimeUri(Uri uri) {
|
|
if (uri.scheme != 'http' && uri.scheme != 'https') {
|
|
return true;
|
|
}
|
|
|
|
final host = _normalizeHostForCompare(uri.host);
|
|
if (host.isEmpty) {
|
|
return true;
|
|
}
|
|
|
|
if (host == 'localhost' || host.endsWith('.localhost') || host == '::1') {
|
|
return true;
|
|
}
|
|
|
|
final ipv4Parts = _tryParseIpv4Address(host);
|
|
if (ipv4Parts == null) {
|
|
return false;
|
|
}
|
|
|
|
return ipv4Parts[0] == 127 || ipv4Parts.every((part) => part == 0);
|
|
}
|
|
|
|
static String _normalizeHostForCompare(String host) {
|
|
final normalized = host.trim().toLowerCase();
|
|
if (normalized.endsWith('.')) {
|
|
return normalized.substring(0, normalized.length - 1);
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
static List<int>? _tryParseIpv4Address(String host) {
|
|
final parts = host.split('.');
|
|
if (parts.length != 4) {
|
|
return null;
|
|
}
|
|
|
|
final octets = <int>[];
|
|
for (final part in parts) {
|
|
if (part.isEmpty || !RegExp(r'^\d+$').hasMatch(part)) {
|
|
return null;
|
|
}
|
|
final value = int.tryParse(part);
|
|
if (value == null || value < 0 || value > 255) {
|
|
return null;
|
|
}
|
|
octets.add(value);
|
|
}
|
|
return octets;
|
|
}
|
|
|
|
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<int>.generate(
|
|
16,
|
|
(_) => chars.codeUnitAt(_wildcardRandom.nextInt(chars.length)),
|
|
),
|
|
);
|
|
}
|
|
}
|