feat: 初始化并发问题处理
Some checks failed
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has been cancelled
Build Windows / build (push) Has been cancelled

This commit is contained in:
speakeloudest 2025-11-13 05:15:18 -08:00
parent 748ec6bee9
commit a47174df56
5 changed files with 400 additions and 118 deletions

View File

@ -28,13 +28,10 @@ class KROutboundItem {
/// URL /// URL
String url = ""; String url = "";
@override
String toString() {
return 'KROutboundItem(tag: $tag, country: $country, delay: ${urlTestDelay.value}ms)';
}
/// ///
/// KrNodeListItem KROutboundItem /// KrItem KROutboundItem
KROutboundItem(KrNodeListItem nodeListItem) { KROutboundItem(KrNodeListItem nodeListItem) {
id = nodeListItem.id.toString(); id = nodeListItem.id.toString();
protocol = nodeListItem.protocol; protocol = nodeListItem.protocol;
@ -84,13 +81,13 @@ class KROutboundItem {
case "vless": case "vless":
final securityConfig = final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {}; json["security_config"] as Map<String, dynamic>? ?? {};
// server_name // server_name
String serverName = securityConfig["sni"] ?? ""; String serverName = securityConfig["sni"] ?? "";
if (serverName.isEmpty) { if (serverName.isEmpty) {
serverName = nodeListItem.serverAddr; serverName = nodeListItem.serverAddr;
} }
config = { config = {
"type": "vless", "type": "vless",
"tag": nodeListItem.name, "tag": nodeListItem.name,
@ -115,13 +112,13 @@ class KROutboundItem {
case "vmess": case "vmess":
final securityConfig = final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {}; json["security_config"] as Map<String, dynamic>? ?? {};
// server_name // server_name
String serverName = securityConfig["sni"] ?? ""; String serverName = securityConfig["sni"] ?? "";
if (serverName.isEmpty) { if (serverName.isEmpty) {
serverName = nodeListItem.serverAddr; serverName = nodeListItem.serverAddr;
} }
config = { config = {
"type": "vmess", "type": "vmess",
"tag": nodeListItem.name, "tag": nodeListItem.name,
@ -152,7 +149,7 @@ class KROutboundItem {
break; break;
case "hysteria": case "hysteria":
case "hysteria2": case "hysteria2":
// "hysteria" Hysteria2 // "hysteria" Hysteria2
final securityConfig = final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {}; json["security_config"] as Map<String, dynamic>? ?? {};
config = { config = {
@ -177,14 +174,14 @@ class KROutboundItem {
case "trojan": case "trojan":
final securityConfig = final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {}; json["security_config"] as Map<String, dynamic>? ?? {};
// server_name // server_name
String serverName = securityConfig["sni"] ?? ""; String serverName = securityConfig["sni"] ?? "";
if (serverName.isEmpty) { if (serverName.isEmpty) {
// SNI使 // SNI使
serverName = nodeListItem.serverAddr; serverName = nodeListItem.serverAddr;
} }
config = { config = {
"type": "trojan", "type": "trojan",
"tag": nodeListItem.name, "tag": nodeListItem.name,
@ -274,6 +271,7 @@ class KROutboundItem {
// 🔧 config transport // 🔧 config transport
Map<String, dynamic>? transportConfig; Map<String, dynamic>? transportConfig;
Map<String, dynamic>? securityConfig; Map<String, dynamic>? securityConfig;
int actualPort = nodeListItem.port; // 🔧 使
// 🔧 protocols // 🔧 protocols
if (nodeListItem.protocols.isNotEmpty) { if (nodeListItem.protocols.isNotEmpty) {
@ -307,6 +305,28 @@ class KROutboundItem {
} }
if (matchedProtocol != null) { if (matchedProtocol != null) {
// 🔧 0使 protocols
// 53441 protocols 287
if (actualPort == 0 && matchedProtocol['port'] != null) {
//
int protocolPort = 0;
final portValue = matchedProtocol['port'];
if (portValue is int) {
protocolPort = portValue;
} else if (portValue is String) {
protocolPort = int.tryParse(portValue) ?? 0;
}
if (protocolPort > 0) {
actualPort = protocolPort;
if (kDebugMode) {
print(' ✅ 从 protocols 使用端口: $protocolPort (顶层端口为0)');
}
}
} else if (kDebugMode && matchedProtocol['port'] != null) {
print(' ✅ 保留顶层端口: $actualPort (protocols中的端口: ${matchedProtocol['port']})');
}
// transport // transport
if (matchedProtocol['network'] != null || matchedProtocol['transport'] != null) { if (matchedProtocol['network'] != null || matchedProtocol['transport'] != null) {
final network = matchedProtocol['network'] ?? matchedProtocol['transport']; final network = matchedProtocol['network'] ?? matchedProtocol['transport'];
@ -402,7 +422,7 @@ class KROutboundItem {
switch (nodeListItem.protocol) { switch (nodeListItem.protocol) {
case "shadowsocks": case "shadowsocks":
// 使 protocols cipher method // 使 protocols cipher method
String finalMethod = nodeListItem.method.isNotEmpty String finalMethod = nodeListItem.method.isNotEmpty
? nodeListItem.method ? nodeListItem.method
: "2022-blake3-aes-256-gcm"; : "2022-blake3-aes-256-gcm";
@ -411,22 +431,21 @@ class KROutboundItem {
"type": "shadowsocks", "type": "shadowsocks",
"tag": nodeListItem.name, "tag": nodeListItem.name,
"server": nodeListItem.serverAddr, "server": nodeListItem.serverAddr,
"server_port": nodeListItem.port, "server_port": actualPort,
"method": finalMethod, "method": finalMethod,
"password": nodeListItem.uuid "password": nodeListItem.uuid
}; };
if (kDebugMode) { if (kDebugMode) {
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}'); print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}');
}
if (kDebugMode) {
print('📄 使用加密方法: $finalMethod'); print('📄 使用加密方法: $finalMethod');
} print('📄 完整配置 JSON:');
if (kDebugMode) { print(jsonEncode(config));
print('📄 完整配置: $config'); print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
} }
break; break;
case "vless": case "vless":
// IP地址 // IP地址
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$') final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr); .hasMatch(nodeListItem.serverAddr);
@ -436,17 +455,25 @@ class KROutboundItem {
serverName = securityConfig['sni'].toString(); serverName = securityConfig['sni'].toString();
} }
// 🔧 security_config TLS // 🔧 TLS
final bool vlessTlsEnabled = securityConfig?['tls_enabled'] ?? false; // 1. 使 securityConfig['tls_enabled']
// 2.
bool vlessTlsEnabled = securityConfig?['tls_enabled'] ?? true; // TLS
// TLS端口80, 8080 TLS
if (nodeListItem.port == 80 || nodeListItem.port == 8080) {
vlessTlsEnabled = false;
}
if (kDebugMode) { if (kDebugMode) {
print('🔐 VLESS TLS 状态: enabled=$vlessTlsEnabled'); print('🔐 VLESS TLS 状态: enabled=$vlessTlsEnabled (port=${nodeListItem.port}, isDomain=$isDomain)');
} }
config = { config = {
"type": "vless", "type": "vless",
"tag": nodeListItem.name, "tag": nodeListItem.name,
"server": nodeListItem.serverAddr, "server": nodeListItem.serverAddr,
"server_port": nodeListItem.port, "server_port": actualPort,
"uuid": nodeListItem.uuid, "uuid": nodeListItem.uuid,
if (transportConfig != null) "transport": transportConfig, if (transportConfig != null) "transport": transportConfig,
if (vlessTlsEnabled) "tls": { if (vlessTlsEnabled) "tls": {
@ -460,14 +487,27 @@ class KROutboundItem {
} }
}; };
if (kDebugMode) { if (kDebugMode) {
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}'); print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}');
} print('📋 原始节点信息:');
if (kDebugMode) { print(' - serverAddr: ${nodeListItem.serverAddr}');
print('📄 完整配置: $config'); print(' - port: ${nodeListItem.port}');
print(' - uuid: ${nodeListItem.uuid}');
print(' - protocols: ${nodeListItem.protocols}');
print('🔐 安全配置:');
print(' - TLS 启用: $vlessTlsEnabled');
print(' - 是域名: $isDomain');
print(' - server_name: $serverName');
print(' - securityConfig: $securityConfig');
print('📡 传输配置:');
print(' - transportConfig: $transportConfig');
print('📄 最终生成的完整配置 JSON:');
print(jsonEncode(config));
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
} }
break; break;
case "vmess": case "vmess":
// IP地址 // IP地址
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$') final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr); .hasMatch(nodeListItem.serverAddr);
@ -477,17 +517,25 @@ class KROutboundItem {
serverName = securityConfig['sni'].toString(); serverName = securityConfig['sni'].toString();
} }
// 🔧 security_config TLS // 🔧 TLS
final bool tlsEnabled = securityConfig?['tls_enabled'] ?? false; // 1. 使 securityConfig['tls_enabled']
// 2.
bool tlsEnabled = securityConfig?['tls_enabled'] ?? true; // TLS
// TLS端口80, 8080 TLS
if (nodeListItem.port == 80 || nodeListItem.port == 8080) {
tlsEnabled = false;
}
if (kDebugMode) { if (kDebugMode) {
print('🔐 TLS 状态: enabled=$tlsEnabled'); print('🔐 VMess TLS 状态: enabled=$tlsEnabled (port=${nodeListItem.port}, isDomain=$isDomain)');
} }
config = { config = {
"type": "vmess", "type": "vmess",
"tag": nodeListItem.name, "tag": nodeListItem.name,
"server": nodeListItem.serverAddr, "server": nodeListItem.serverAddr,
"server_port": nodeListItem.port, "server_port": actualPort,
"uuid": nodeListItem.uuid, "uuid": nodeListItem.uuid,
"alter_id": 0, "alter_id": 0,
"security": "auto", "security": "auto",
@ -503,14 +551,27 @@ class KROutboundItem {
} }
}; };
if (kDebugMode) { if (kDebugMode) {
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
print('✅ VMess 节点配置构建成功: ${nodeListItem.name}'); print('✅ VMess 节点配置构建成功: ${nodeListItem.name}');
} print('📋 原始节点信息:');
if (kDebugMode) { print(' - serverAddr: ${nodeListItem.serverAddr}');
print('📄 完整配置: $config'); print(' - port: ${nodeListItem.port}');
print(' - uuid: ${nodeListItem.uuid}');
print(' - protocols: ${nodeListItem.protocols}');
print('🔐 安全配置:');
print(' - TLS 启用: $tlsEnabled');
print(' - 是域名: $isDomain');
print(' - server_name: $serverName');
print(' - securityConfig: $securityConfig');
print('📡 传输配置:');
print(' - transportConfig: $transportConfig');
print('📄 最终生成的完整配置 JSON:');
print(jsonEncode(config));
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
} }
break; break;
case "trojan": case "trojan":
// IP地址 // IP地址
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$') final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr); .hasMatch(nodeListItem.serverAddr);
@ -524,7 +585,7 @@ class KROutboundItem {
"type": "trojan", "type": "trojan",
"tag": nodeListItem.name, "tag": nodeListItem.name,
"server": nodeListItem.serverAddr, "server": nodeListItem.serverAddr,
"server_port": nodeListItem.port, "server_port": actualPort,
"password": nodeListItem.uuid, "password": nodeListItem.uuid,
if (transportConfig != null) "transport": transportConfig, if (transportConfig != null) "transport": transportConfig,
"tls": { "tls": {
@ -546,7 +607,7 @@ class KROutboundItem {
break; break;
case "hysteria": case "hysteria":
case "hysteria2": case "hysteria2":
// "hysteria" Hysteria2 // "hysteria" Hysteria2
if (kDebugMode) { if (kDebugMode) {
print('🔍 构建 Hysteria2 节点: ${nodeListItem.name}'); print('🔍 构建 Hysteria2 节点: ${nodeListItem.name}');
} }

View File

@ -2,7 +2,7 @@ import 'dart:convert';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
class KRNodeList { class KRNodeList {
final List<KrNodeListItem> list; final List<KrNodeListItem> list;
final String subscribeId; final String subscribeId;
final String startTime; final String startTime;
final String expireTime; final String expireTime;
@ -35,8 +35,8 @@ class KRNodeList {
// id // id
try { try {
subscribeData = listData.firstWhere( subscribeData = listData.firstWhere(
(item) => (item as Map<String, dynamic>)['id']?.toString() == requestSubscribeId, (item) => (item as Map<String, dynamic>)['id']?.toString() == requestSubscribeId,
orElse: () => listData[0] as Map<String, dynamic> orElse: () => listData[0] as Map<String, dynamic>
) as Map<String, dynamic>; ) as Map<String, dynamic>;
KRLogUtil.kr_i('✅ 找到匹配的订阅项: id=$requestSubscribeId', tag: 'NodeList'); KRLogUtil.kr_i('✅ 找到匹配的订阅项: id=$requestSubscribeId', tag: 'NodeList');
} catch (e) { } catch (e) {
@ -70,7 +70,7 @@ class KRNodeList {
class KrNodeListItem { class KrNodeListItem {
final int id; final int id;
String name; String name;
final String uuid; final String uuid;
final String protocol; final String protocol;
final String relayMode; final String relayMode;
@ -136,25 +136,68 @@ class KrNodeListItem {
String method = json['method']?.toString() ?? ''; // Shadowsocks等 String method = json['method']?.toString() ?? ''; // Shadowsocks等
final protocols = json['protocols']?.toString() ?? ''; // JSON final protocols = json['protocols']?.toString() ?? ''; // JSON
// 🔧 protocols port cipher // 🔧 JSON
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'NodeList');
KRLogUtil.kr_i('📥 收到节点 API 原始数据:', tag: 'NodeList');
KRLogUtil.kr_i('节点名称: ${json['name']}', tag: 'NodeList');
KRLogUtil.kr_i('协议类型: ${json['protocol']}', tag: 'NodeList');
KRLogUtil.kr_i('完整 JSON:', tag: 'NodeList');
KRLogUtil.kr_i(jsonEncode(json), tag: 'NodeList');
// 🔧 protocols cipher port
if (protocols.isNotEmpty) { if (protocols.isNotEmpty) {
try { try {
final protocolsList = jsonDecode(protocols) as List; final protocolsList = jsonDecode(protocols) as List;
final currentProtocol = json['protocol']?.toString().toLowerCase() ?? '';
KRLogUtil.kr_i('📋 protocols 字段内容 (${protocolsList.length} 个协议):', tag: 'NodeList');
for (var i = 0; i < protocolsList.length; i++) {
KRLogUtil.kr_i(' 协议 $i: ${jsonEncode(protocolsList[i])}', tag: 'NodeList');
}
if (protocolsList.isNotEmpty) { if (protocolsList.isNotEmpty) {
final firstProtocol = protocolsList[0] as Map<String, dynamic>; // 🔧 使
// 使 protocols Map<String, dynamic>? matchedProtocolConfig;
if (firstProtocol['port'] != null) {
port = _parseIntSafely(firstProtocol['port']); //
for (var protocolConfig in protocolsList) {
final configMap = protocolConfig as Map<String, dynamic>;
// 🔧 API 使 'type' 'protocol'
final protocolType = (configMap['type'] ?? configMap['protocol'])?.toString().toLowerCase() ?? '';
// shadowsocks/ss, vmess, vless, trojan
if (protocolType == currentProtocol ||
(currentProtocol == 'shadowsocks' && protocolType == 'ss') ||
(currentProtocol == 'ss' && protocolType == 'shadowsocks')) {
matchedProtocolConfig = configMap;
KRLogUtil.kr_i('🎯 找到匹配的协议配置: $protocolType', tag: 'NodeList');
break;
}
} }
if (firstProtocol['cipher'] != null && firstProtocol['cipher'].toString().isNotEmpty) {
method = firstProtocol['cipher'].toString(); // 使API
final targetProtocol = matchedProtocolConfig ?? (protocolsList[0] as Map<String, dynamic>);
// 🔧 port 使 protocols
// 53441 protocols 287
if (port == 0 && targetProtocol['port'] != null) {
port = _parseIntSafely(targetProtocol['port']);
KRLogUtil.kr_i('✅ 从 protocols 解析端口: $port', tag: 'NodeList');
} else {
KRLogUtil.kr_i('✅ 保留顶层端口: $port (protocols中的端口: ${targetProtocol['port']})', tag: 'NodeList');
}
// cipher
if (targetProtocol['cipher'] != null && targetProtocol['cipher'].toString().isNotEmpty) {
method = targetProtocol['cipher'].toString();
KRLogUtil.kr_i('✅ 从 protocols 解析 cipher: $method', tag: 'NodeList');
} }
KRLogUtil.kr_i('从 protocols 解析: port=$port, cipher=$method', tag: 'NodeList');
} }
} catch (e) { } catch (e) {
KRLogUtil.kr_w('解析 protocols 字段失败: $e', tag: 'NodeList'); KRLogUtil.kr_w('⚠️ 解析 protocols 字段失败: $e', tag: 'NodeList');
} }
} }
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'NodeList');
return KrNodeListItem( return KrNodeListItem(
id: _parseIntSafely(json['id']), id: _parseIntSafely(json['id']),

View File

@ -80,8 +80,8 @@ class HINodeListView extends GetView<HINodeListController> {
// //
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
// child: _buildSubscribeList(context) child: _buildSubscribeList(context)
child: _kr_buildRegionList(context) // child: _kr_buildRegionList(context)
); );
} }
@ -380,7 +380,7 @@ class HINodeListView extends GetView<HINodeListController> {
children: [ children: [
Flexible( Flexible(
child: Text( child: Text(
controller.homeController.kr_getCountryFullName(item.country), '${controller.homeController.kr_getCountryFullName(item.country)}-${item.tag}',
style: KrAppTextStyle(fontSize: 14, color: Colors.white), style: KrAppTextStyle(fontSize: 14, color: Colors.white),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 1, maxLines: 1,

View File

@ -849,6 +849,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
if (kDebugMode) { if (kDebugMode) {
} }
await KRSingBoxImp.instance.kr_start(); await KRSingBoxImp.instance.kr_start();
KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController'); KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController');
if (kDebugMode) { if (kDebugMode) {
} }
@ -2185,6 +2186,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
kr_isConnected.value = true; kr_isConnected.value = true;
kr_startConnectionTimer(); kr_startConnectionTimer();
kr_updateConnectionInfo(); kr_updateConnectionInfo();
KRSingBoxImp.instance.kr_debugTunConnectivity();
// 🔧 // 🔧
if (!_kr_tryUpdateDelayFromActiveGroups()) { if (!_kr_tryUpdateDelayFromActiveGroups()) {

View File

@ -107,6 +107,9 @@ class KRSingBoxImp {
/// ///
bool _kr_isInitialized = false; bool _kr_isInitialized = false;
/// Futuresingle-flight
Future<void>? _kr_initFuture;
/// ///
bool get kr_isProxyReady => kr_status.value is SingboxStarted; bool get kr_isProxyReady => kr_status.value is SingboxStarted;
@ -139,12 +142,23 @@ class KRSingBoxImp {
/// ///
Future<void> init() async { Future<void> init() async {
// //
if (_kr_isInitialized) { if (_kr_isInitialized) {
KRLogUtil.kr_i('SingBox 已经初始化,跳过重复初始化', tag: 'SingBox'); KRLogUtil.kr_i('SingBox 已经初始化,跳过重复初始化', tag: 'SingBox');
return; return;
} }
// Future
if (_kr_initFuture != null) {
KRLogUtil.kr_i('SingBox 初始化进行中等待完成single-flight', tag: 'SingBox');
await _kr_initFuture;
return;
}
// single-flight Future
final completer = Completer<void>();
_kr_initFuture = completer.future;
try { try {
KRLogUtil.kr_i('开始初始化 SingBox'); KRLogUtil.kr_i('开始初始化 SingBox');
// //
@ -167,16 +181,16 @@ class KRSingBoxImp {
KRLogUtil.kr_i('iOS 路径获取完成: $paths'); KRLogUtil.kr_i('iOS 路径获取完成: $paths');
kr_configDics = ( kr_configDics = (
baseDir: Directory(paths?["base"]! as String), baseDir: Directory(paths?["base"]! as String),
workingDir: Directory(paths?["working"]! as String), workingDir: Directory(paths?["working"]! as String),
tempDir: Directory(paths?["temp"]! as String), tempDir: Directory(paths?["temp"]! as String),
); );
} else { } else {
final baseDir = await getApplicationSupportDirectory(); final baseDir = await getApplicationSupportDirectory();
final workingDir = final workingDir =
Platform.isAndroid ? await getExternalStorageDirectory() : baseDir; Platform.isAndroid ? await getExternalStorageDirectory() : baseDir;
final tempDir = await getTemporaryDirectory(); final tempDir = await getTemporaryDirectory();
// Windows 使 // Windows 使
Directory normalizePath(Directory dir) { Directory normalizePath(Directory dir) {
if (Platform.isWindows) { if (Platform.isWindows) {
@ -188,11 +202,11 @@ class KRSingBoxImp {
} }
return dir; return dir;
} }
kr_configDics = ( kr_configDics = (
baseDir: normalizePath(baseDir), baseDir: normalizePath(baseDir),
workingDir: normalizePath(workingDir!), workingDir: normalizePath(workingDir!),
tempDir: normalizePath(tempDir), tempDir: normalizePath(tempDir),
); );
KRLogUtil.kr_i('其他平台路径初始化完成'); KRLogUtil.kr_i('其他平台路径初始化完成');
} }
@ -201,7 +215,7 @@ class KRSingBoxImp {
KRLogUtil.kr_i('baseDir: ${kr_configDics.baseDir.path}', tag: 'SingBox'); KRLogUtil.kr_i('baseDir: ${kr_configDics.baseDir.path}', tag: 'SingBox');
KRLogUtil.kr_i('workingDir: ${kr_configDics.workingDir.path}', tag: 'SingBox'); KRLogUtil.kr_i('workingDir: ${kr_configDics.workingDir.path}', tag: 'SingBox');
KRLogUtil.kr_i('tempDir: ${kr_configDics.tempDir.path}', tag: 'SingBox'); KRLogUtil.kr_i('tempDir: ${kr_configDics.tempDir.path}', tag: 'SingBox');
// //
if (!kr_configDics.baseDir.existsSync()) { if (!kr_configDics.baseDir.existsSync()) {
await kr_configDics.baseDir.create(recursive: true); await kr_configDics.baseDir.create(recursive: true);
@ -215,11 +229,11 @@ class KRSingBoxImp {
await kr_configDics.tempDir.create(recursive: true); await kr_configDics.tempDir.create(recursive: true);
KRLogUtil.kr_i('已创建 tempDir', tag: 'SingBox'); KRLogUtil.kr_i('已创建 tempDir', tag: 'SingBox');
} }
// libcore data workingDir // libcore data workingDir
// libcore Setup os.Chdir(workingPath) data workingDir // libcore Setup os.Chdir(workingPath) data workingDir
final dataDir = Directory(p.join(kr_configDics.workingDir.path, 'data')); final dataDir = Directory(p.join(kr_configDics.workingDir.path, 'data'));
// data Windows // data Windows
int retryCount = 0; int retryCount = 0;
const maxRetries = 5; const maxRetries = 5;
@ -228,7 +242,7 @@ class KRSingBoxImp {
await dataDir.create(recursive: true); await dataDir.create(recursive: true);
// Windows // Windows
await Future.delayed(const Duration(milliseconds: 100)); await Future.delayed(const Duration(milliseconds: 100));
// //
if (dataDir.existsSync()) { if (dataDir.existsSync()) {
KRLogUtil.kr_i('✅ 已创建 data 目录: ${dataDir.path}', tag: 'SingBox'); KRLogUtil.kr_i('✅ 已创建 data 目录: ${dataDir.path}', tag: 'SingBox');
@ -248,13 +262,13 @@ class KRSingBoxImp {
await Future.delayed(Duration(milliseconds: delayMs)); await Future.delayed(Duration(milliseconds: delayMs));
} }
} }
if (!dataDir.existsSync()) { if (!dataDir.existsSync()) {
final error = 'data 目录不存在: ${dataDir.path}'; final error = 'data 目录不存在: ${dataDir.path}';
KRLogUtil.kr_e('$error', tag: 'SingBox'); KRLogUtil.kr_e('$error', tag: 'SingBox');
throw Exception(error); throw Exception(error);
} }
// //
try { try {
final testFile = File(p.join(dataDir.path, '.test_write')); final testFile = File(p.join(dataDir.path, '.test_write'));
@ -265,13 +279,13 @@ class KRSingBoxImp {
KRLogUtil.kr_e('⚠️ data 目录写入权限验证失败: $e', tag: 'SingBox'); KRLogUtil.kr_e('⚠️ data 目录写入权限验证失败: $e', tag: 'SingBox');
// libcore // libcore
} }
// Windows // Windows
if (Platform.isWindows) { if (Platform.isWindows) {
await Future.delayed(const Duration(milliseconds: 300)); await Future.delayed(const Duration(milliseconds: 300));
KRLogUtil.kr_i('⏳ Windows 文件系统同步等待完成', tag: 'SingBox'); KRLogUtil.kr_i('⏳ Windows 文件系统同步等待完成', tag: 'SingBox');
} }
// setup() workingDir data 访 // setup() workingDir data 访
// libcore Setup() os.Chdir(workingPath)使 "./data" // libcore Setup() os.Chdir(workingPath)使 "./data"
// os.Chdir() 访 // os.Chdir() 访
@ -280,7 +294,7 @@ class KRSingBoxImp {
KRLogUtil.kr_e(error, tag: 'SingBox'); KRLogUtil.kr_e(error, tag: 'SingBox');
throw Exception(error); throw Exception(error);
} }
// workingDir // workingDir
try { try {
final testWorkingFile = File(p.join(kr_configDics.workingDir.path, '.test_working_dir')); final testWorkingFile = File(p.join(kr_configDics.workingDir.path, '.test_working_dir'));
@ -292,7 +306,7 @@ class KRSingBoxImp {
KRLogUtil.kr_e(error, tag: 'SingBox'); KRLogUtil.kr_e(error, tag: 'SingBox');
throw Exception(error); throw Exception(error);
} }
final finalDataDir = Directory(p.join(kr_configDics.workingDir.path, 'data')); final finalDataDir = Directory(p.join(kr_configDics.workingDir.path, 'data'));
if (!finalDataDir.existsSync()) { if (!finalDataDir.existsSync()) {
KRLogUtil.kr_e('❌ 最终验证失败data 目录不存在', tag: 'SingBox'); KRLogUtil.kr_e('❌ 最终验证失败data 目录不存在', tag: 'SingBox');
@ -308,7 +322,7 @@ class KRSingBoxImp {
} }
throw Exception('data 目录在 setup() 前验证失败: ${finalDataDir.path}'); throw Exception('data 目录在 setup() 前验证失败: ${finalDataDir.path}');
} }
// //
try { try {
final verifyFile = File(p.join(finalDataDir.path, '.verify_setup')); final verifyFile = File(p.join(finalDataDir.path, '.verify_setup'));
@ -364,7 +378,7 @@ class KRSingBoxImp {
KRLogUtil.kr_i(' - tempDir: ${kr_configDics.tempDir.path}', tag: 'SingBox'); KRLogUtil.kr_i(' - tempDir: ${kr_configDics.tempDir.path}', tag: 'SingBox');
KRLogUtil.kr_i(' - data 目录: ${p.join(kr_configDics.workingDir.path, "data")}', tag: 'SingBox'); KRLogUtil.kr_i(' - data 目录: ${p.join(kr_configDics.workingDir.path, "data")}', tag: 'SingBox');
KRLogUtil.kr_i(' - data 目录存在: ${finalDataDir.existsSync()}', tag: 'SingBox'); KRLogUtil.kr_i(' - data 目录存在: ${finalDataDir.existsSync()}', tag: 'SingBox');
// Windows data // Windows data
if (Platform.isWindows && finalDataDir.existsSync()) { if (Platform.isWindows && finalDataDir.existsSync()) {
try { try {
@ -378,10 +392,10 @@ class KRSingBoxImp {
KRLogUtil.kr_e(' - 无法列出 data 目录内容: $e', tag: 'SingBox'); KRLogUtil.kr_e(' - 无法列出 data 目录内容: $e', tag: 'SingBox');
} }
} }
KRLogUtil.kr_i(' - libcore 将通过 os.Chdir() 切换到: ${kr_configDics.workingDir.path}', tag: 'SingBox'); KRLogUtil.kr_i(' - libcore 将通过 os.Chdir() 切换到: ${kr_configDics.workingDir.path}', tag: 'SingBox');
KRLogUtil.kr_i(' - 然后使用相对路径 "./data" 访问数据库', tag: 'SingBox'); KRLogUtil.kr_i(' - 然后使用相对路径 "./data" 访问数据库', tag: 'SingBox');
// Windows // Windows
if (Platform.isWindows) { if (Platform.isWindows) {
final workingPath = kr_configDics.workingDir.path; final workingPath = kr_configDics.workingDir.path;
@ -400,11 +414,11 @@ class KRSingBoxImp {
KRLogUtil.kr_i('📡 开始调用 setup() 注册 FFI 端口', tag: 'SingBox'); KRLogUtil.kr_i('📡 开始调用 setup() 注册 FFI 端口', tag: 'SingBox');
final setupResult = await kr_singBox.setup(kr_configDics, false).run(); final setupResult = await kr_singBox.setup(kr_configDics, false).run();
setupResult.match( setupResult.match(
(error) { (error) {
KRLogUtil.kr_e('❌ setup() 失败: $error', tag: 'SingBox'); KRLogUtil.kr_e('❌ setup() 失败: $error', tag: 'SingBox');
throw Exception('FFI setup 失败: $error'); throw Exception('FFI setup 失败: $error');
}, },
(_) { (_) {
KRLogUtil.kr_i('✅ setup() 成功FFI 端口已注册', tag: 'SingBox'); KRLogUtil.kr_i('✅ setup() 成功FFI 端口已注册', tag: 'SingBox');
}, },
); );
@ -416,6 +430,10 @@ class KRSingBoxImp {
// UI libcore // UI libcore
_kr_subscribeToStatus(); _kr_subscribeToStatus();
KRLogUtil.kr_i('✅ 状态订阅已设置', tag: 'SingBox'); KRLogUtil.kr_i('✅ 状态订阅已设置', tag: 'SingBox');
// single-flight
completer.complete();
_kr_initFuture = null;
} catch (e, stackTrace) { } catch (e, stackTrace) {
KRLogUtil.kr_e('❌ SingBox 初始化失败: $e'); KRLogUtil.kr_e('❌ SingBox 初始化失败: $e');
KRLogUtil.kr_e('📚 错误堆栈: $stackTrace'); KRLogUtil.kr_e('📚 错误堆栈: $stackTrace');
@ -451,6 +469,11 @@ class KRSingBoxImp {
// //
_kr_isInitialized = false; _kr_isInitialized = false;
// single-flight
if (!completer.isCompleted) {
completer.completeError(e, stackTrace);
}
_kr_initFuture = null;
rethrow; rethrow;
} }
} }
@ -464,13 +487,13 @@ class KRSingBoxImp {
"region": "other", // hiddify-app: 使 "other" "region": "other", // hiddify-app: 使 "other"
"block-ads": false, // hiddify-app: 广 "block-ads": false, // hiddify-app: 广
"use-xray-core-when-possible": false, "use-xray-core-when-possible": false,
"execute-config-as-is": false, "execute-config-as-is": true, // 🔧 使 libcore
"log-level": "info", // 使 info warn "log-level": "info", // 使 info warn
"resolve-destination": false, "resolve-destination": false,
"ipv6-mode": "ipv4_only", // hiddify-app: 使 IPv4 (: ipv4_only, prefer_ipv4, prefer_ipv6, ipv6_only) "ipv6-mode": "ipv4_only", // hiddify-app: 使 IPv4 (: ipv4_only, prefer_ipv4, prefer_ipv6, ipv6_only)
"remote-dns-address": "udp://1.1.1.1", // hiddify-app: 使 Cloudflare DNS "remote-dns-address": "https://dns.google/dns-query", // 使 Google DoH DNS
"remote-dns-domain-strategy": "prefer_ipv4", "remote-dns-domain-strategy": "prefer_ipv4",
"direct-dns-address": "udp://1.1.1.1", // hiddify-app: 使 1.1.1.1 "direct-dns-address": "local" ,
"direct-dns-domain-strategy": "prefer_ipv4", "direct-dns-domain-strategy": "prefer_ipv4",
"mixed-port": kr_port, "mixed-port": kr_port,
"tproxy-port": kr_port, "tproxy-port": kr_port,
@ -561,7 +584,7 @@ class KRSingBoxImp {
_kr_subscriptions.add( _kr_subscriptions.add(
kr_singBox.watchStatus().listen( kr_singBox.watchStatus().listen(
(status) { (status) {
if (kDebugMode) { if (kDebugMode) {
print('🔵 收到 Native 状态更新: ${status.runtimeType}'); print('🔵 收到 Native 状态更新: ${status.runtimeType}');
} }
@ -599,7 +622,7 @@ class KRSingBoxImp {
// try-catch // try-catch
final stream = kr_singBox.watchStats(); final stream = kr_singBox.watchStats();
final subscription = stream.listen( final subscription = stream.listen(
(stats) { (stats) {
kr_stats.value = stats; kr_stats.value = stats;
}, },
onError: (error) { onError: (error) {
@ -624,11 +647,11 @@ class KRSingBoxImp {
_kr_subscriptions.add( _kr_subscriptions.add(
kr_singBox.watchActiveGroups().listen( kr_singBox.watchActiveGroups().listen(
(groups) { (groups) {
print('[watchActiveGroups] 📡 收到活动组更新,数量: ${groups.length}'); print('[watchActiveGroups] 📡 收到活动组更新,数量: ${groups.length}');
KRLogUtil.kr_i('📡 收到活动组更新,数量: ${groups.length}', tag: 'SingBox'); KRLogUtil.kr_i('📡 收到活动组更新,数量: ${groups.length}', tag: 'SingBox');
kr_activeGroups.value = groups; kr_activeGroups.value = groups;
// //
for (int i = 0; i < groups.length; i++) { for (int i = 0; i < groups.length; i++) {
final group = groups[i]; final group = groups[i];
@ -638,7 +661,7 @@ class KRSingBoxImp {
KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox'); KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox');
} }
} }
KRLogUtil.kr_i('✅ 活动组处理完成', tag: 'SingBox'); KRLogUtil.kr_i('✅ 活动组处理完成', tag: 'SingBox');
}, },
onError: (error) { onError: (error) {
@ -651,7 +674,7 @@ class KRSingBoxImp {
_kr_subscriptions.add( _kr_subscriptions.add(
kr_singBox.watchGroups().listen( kr_singBox.watchGroups().listen(
(groups) { (groups) {
print('[watchGroups] 📡 收到所有组更新,数量: ${groups.length}'); print('[watchGroups] 📡 收到所有组更新,数量: ${groups.length}');
kr_allGroups.value = groups; kr_allGroups.value = groups;
// //
@ -679,7 +702,7 @@ class KRSingBoxImp {
// "select" // "select"
final selectGroup = kr_activeGroups.firstWhere( final selectGroup = kr_activeGroups.firstWhere(
(group) => group.tag == 'select', (group) => group.tag == 'select',
orElse: () => throw Exception('未找到 "select" 选择器组'), orElse: () => throw Exception('未找到 "select" 选择器组'),
); );
@ -719,11 +742,11 @@ class KRSingBoxImp {
/// ///
/// command.sock /// command.sock
Future<void> _kr_selectOutboundWithRetry( Future<void> _kr_selectOutboundWithRetry(
String groupTag, String groupTag,
String outboundTag, { String outboundTag, {
int maxAttempts = 3, int maxAttempts = 3,
int initialDelay = 100, int initialDelay = 100,
}) async { }) async {
int attempt = 0; int attempt = 0;
int delay = initialDelay; int delay = initialDelay;
@ -1031,17 +1054,86 @@ class KRSingBoxImp {
KRLogUtil.kr_i('✅ 过滤后节点数量: ${kr_outbounds.length}/${outbounds.length}', tag: 'SingBox'); KRLogUtil.kr_i('✅ 过滤后节点数量: ${kr_outbounds.length}/${outbounds.length}', tag: 'SingBox');
// outboundsMobile.buildConfig() // 🔧 SingBox
final map = { // {"outbounds": [...]}, libcore
"outbounds": kr_outbounds //
final Map<String, dynamic> fullConfig = {
"log": {
"level": "debug",
"timestamp": true
},
"dns": {
"servers": [
{
"tag": "dns-remote",
"address": "https://dns.google/dns-query",
"detour": "direct",
"address_resolver": "dns-direct"
},
{
"tag": "dns-direct",
"address": "local",
"detour": "direct"
}
],
"rules": [],
"final": "dns-remote",
"strategy": "prefer_ipv4"
},
"inbounds": [
{
"type": "tun",
"tag": "tun-in",
"interface_name": "utun",
"inet4_address": "172.19.0.1/30",
"auto_route": true,
"strict_route": true,
"sniff": true,
"sniff_override_destination": false
}
],
"outbounds": [
// 🔧 selector
{
"type": "selector",
"tag": "proxy",
"outbounds": kr_outbounds.map((o) => o['tag'] as String).toList(),
"default": kr_outbounds.isNotEmpty ? kr_outbounds[0]['tag'] : "direct",
},
...kr_outbounds,
{
"type": "direct",
"tag": "direct"
},
{
"type": "block",
"tag": "block"
},
{
"type": "dns",
"tag": "dns-out"
}
],
"route": {
"rules": [
{ "type": "dns", "outbound": "dns-out" }, // sing-box dns-out
{ "type": "field", "port": 53, "network": "udp", "outbound": "direct" }, // UDP 53
{ "type": "field", "port": 53, "network": "tcp", "outbound": "direct" }, // TCP 53
{ "type": "field", "port": 853, "network": "tcp", "outbound": "direct" }, // TCP 853Private DNS
{ "type": "field", "domain": ["dns.google", "cloudflare-dns.com", "one.one.one.one"], "outbound": "direct" } // DoH
],
"final": "proxy", // 🔧 使 selector
"auto_detect_interface": true
}
}; };
final file = _file(kr_configName); final file = _file(kr_configName);
final temp = _tempFile(kr_configName); final temp = _tempFile(kr_configName);
final mapStr = jsonEncode(map); final mapStr = jsonEncode(fullConfig);
KRLogUtil.kr_i('📄 配置文件内容长度: ${mapStr.length}', tag: 'SingBox'); KRLogUtil.kr_i('📄 完整配置文件长度: ${mapStr.length}', tag: 'SingBox');
KRLogUtil.kr_i('📄 完整配置文件内容: $mapStr', tag: 'SingBox'); KRLogUtil.kr_i('📄 Outbounds 数量: ${kr_outbounds.length}', tag: 'SingBox');
KRLogUtil.kr_i('📄 配置前800字符:\n${mapStr.substring(0, mapStr.length > 800 ? 800 : mapStr.length)}', tag: 'SingBox');
await file.writeAsString(mapStr); await file.writeAsString(mapStr);
await temp.writeAsString(mapStr); await temp.writeAsString(mapStr);
@ -1089,11 +1181,11 @@ class KRSingBoxImp {
final oOption = SingboxConfigOption.fromJson(_getConfigOption()); final oOption = SingboxConfigOption.fromJson(_getConfigOption());
final changeResult = await kr_singBox.changeOptions(oOption).run(); final changeResult = await kr_singBox.changeOptions(oOption).run();
changeResult.match( changeResult.match(
(error) { (error) {
KRLogUtil.kr_e('❌ changeOptions() 失败: $error', tag: 'SingBox'); KRLogUtil.kr_e('❌ changeOptions() 失败: $error', tag: 'SingBox');
throw Exception('初始化 HiddifyOptions 失败: $error'); throw Exception('初始化 HiddifyOptions 失败: $error');
}, },
(_) { (_) {
KRLogUtil.kr_i('✅ HiddifyOptions 初始化成功', tag: 'SingBox'); KRLogUtil.kr_i('✅ HiddifyOptions 初始化成功', tag: 'SingBox');
}, },
); );
@ -1112,7 +1204,7 @@ class KRSingBoxImp {
_kr_subscribeToStatus(); _kr_subscribeToStatus();
await kr_singBox.start(_cutPath, kr_configName, false).map( await kr_singBox.start(_cutPath, kr_configName, false).map(
(r) { (r) {
KRLogUtil.kr_i('✅ SingBox 启动成功', tag: 'SingBox'); KRLogUtil.kr_i('✅ SingBox 启动成功', tag: 'SingBox');
}, },
).mapLeft((err) { ).mapLeft((err) {
@ -1316,9 +1408,9 @@ class KRSingBoxImp {
mode = KRCountryUtil.kr_getCurrentCountryCode(); mode = KRCountryUtil.kr_getCurrentCountryCode();
KRLogUtil.kr_i('🎯 切换到规则代理模式: $mode', tag: 'SingBox'); KRLogUtil.kr_i('🎯 切换到规则代理模式: $mode', tag: 'SingBox');
break; break;
// case KRConnectionType.direct: // case KRConnectionType.direct:
// mode = "direct"; // mode = "direct";
// break; // break;
} }
oOption["region"] = mode; oOption["region"] = mode;
KRLogUtil.kr_i('📝 更新 region 配置: $mode', tag: 'SingBox'); KRLogUtil.kr_i('📝 更新 region 配置: $mode', tag: 'SingBox');
@ -1454,8 +1546,8 @@ class KRSingBoxImp {
// 使 then/catchError UI // 使 then/catchError UI
kr_singBox.selectOutbound("select", tag).run().then((result) { kr_singBox.selectOutbound("select", tag).run().then((result) {
result.match( result.match(
(error) => KRLogUtil.kr_w('🔁 定时器重选节点失败: $error', tag: 'SingBox'), (error) => KRLogUtil.kr_w('🔁 定时器重选节点失败: $error', tag: 'SingBox'),
(_) => KRLogUtil.kr_d('🔁 定时器重选节点成功', tag: 'SingBox'), (_) => KRLogUtil.kr_d('🔁 定时器重选节点成功', tag: 'SingBox'),
); );
}).catchError((error) { }).catchError((error) {
KRLogUtil.kr_w('🔁 定时器重选节点异常: $error', tag: 'SingBox'); KRLogUtil.kr_w('🔁 定时器重选节点异常: $error', tag: 'SingBox');
@ -1473,13 +1565,45 @@ class KRSingBoxImp {
if (country == 'auto') { if (country == 'auto') {
KRLogUtil.kr_i('🌀 国家为 auto执行自动节点选择逻辑', tag: 'SingBox'); KRLogUtil.kr_i('🌀 国家为 auto执行自动节点选择逻辑', tag: 'SingBox');
await kr_selectOutbound('auto'); try {
// auto/
await KRSecureStorage().kr_saveData(key: 'SELECTED_COUNTRY_TAG', value: 'auto');
await KRSecureStorage().kr_saveData(key: _keySelectedNode, value: 'auto');
await kr_selectOutbound('auto');
_nodeSelectionTimer?.cancel();
_nodeSelectionTimer = null;
KRLogUtil.kr_i('✅ 已切换为 auto 节点选择', tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_e('❌ auto 国家选择失败: $e', tag: 'SingBox');
rethrow;
}
return; return;
} }
try {
await KRSecureStorage().kr_saveData(key: 'SELECTED_COUNTRY_TAG', value: country);
final oOption = _getConfigOption();
oOption["region"] = country;
KRLogUtil.kr_i('📝 更新 region 配置为国家: $country', tag: 'SingBox');
final op = SingboxConfigOption.fromJson(oOption);
await kr_singBox.changeOptions(op)
..map((r) {
KRLogUtil.kr_i('✅ 国家 region 更新成功: $country', tag: 'SingBox');
}).mapLeft((err) {
KRLogUtil.kr_e('❌ 更新国家 region 失败: $err', tag: 'SingBox');
throw err;
}).run();
// auto 便
await KRSecureStorage().kr_saveData(key: _keySelectedNode, value: 'auto');
KRLogUtil.kr_i(' 已应用国家到配置,等待控制器执行重启', tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_e('❌ 国家选择流程失败: $e', tag: 'SingBox');
rethrow;
}
if (kr_activeGroups.isEmpty) { if (kr_activeGroups.isEmpty) {
KRLogUtil.kr_w('⚠️ kr_activeGroups 当前为空,无法进行国家选择', tag: 'SingBox'); KRLogUtil.kr_w('⚠️ kr_activeGroups 当前为空,国家选择后将由重启同步分组', tag: 'SingBox');
return;
} }
} }
@ -1500,7 +1624,7 @@ class KRSingBoxImp {
Future<void> kr_urlTest(String groupTag) async { Future<void> kr_urlTest(String groupTag) async {
KRLogUtil.kr_i('🧪 开始 URL 测试: $groupTag', tag: 'SingBox'); KRLogUtil.kr_i('🧪 开始 URL 测试: $groupTag', tag: 'SingBox');
KRLogUtil.kr_i('📊 当前活动组数量: ${kr_activeGroups.length}', tag: 'SingBox'); KRLogUtil.kr_i('📊 当前活动组数量: ${kr_activeGroups.length}', tag: 'SingBox');
// //
for (int i = 0; i < kr_activeGroups.length; i++) { for (int i = 0; i < kr_activeGroups.length; i++) {
final group = kr_activeGroups[i]; final group = kr_activeGroups[i];
@ -1510,15 +1634,15 @@ class KRSingBoxImp {
KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox'); KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox');
} }
} }
try { try {
KRLogUtil.kr_i('🚀 调用 SingBox URL 测试 API...', tag: 'SingBox'); KRLogUtil.kr_i('🚀 调用 SingBox URL 测试 API...', tag: 'SingBox');
final result = await kr_singBox.urlTest(groupTag).run(); final result = await kr_singBox.urlTest(groupTag).run();
KRLogUtil.kr_i('✅ URL 测试完成: $groupTag, 结果: $result', tag: 'SingBox'); KRLogUtil.kr_i('✅ URL 测试完成: $groupTag, 结果: $result', tag: 'SingBox');
// SingBox // SingBox
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 2));
// //
KRLogUtil.kr_i('🔄 测试后活动组状态检查:', tag: 'SingBox'); KRLogUtil.kr_i('🔄 测试后活动组状态检查:', tag: 'SingBox');
for (int i = 0; i < kr_activeGroups.length; i++) { for (int i = 0; i < kr_activeGroups.length; i++) {
@ -1534,4 +1658,56 @@ class KRSingBoxImp {
KRLogUtil.kr_e('📚 错误详情: ${e.toString()}', tag: 'SingBox'); KRLogUtil.kr_e('📚 错误详情: ${e.toString()}', tag: 'SingBox');
} }
} }
}
Future<void> kr_debugTunConnectivity() async {
KRLogUtil.kr_i('🧪 [TUN Debug] 开始调试', tag: 'SingBoxTun');
final statusType = kr_status.value.runtimeType.toString();
KRLogUtil.kr_i('🔎 当前状态: $statusType', tag: 'SingBoxTun');
final selectedCountry = await KRSecureStorage().kr_readData(key: 'SELECTED_COUNTRY_TAG');
final selectedNode = await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
KRLogUtil.kr_i('🎯 选择: country=$selectedCountry, node=$selectedNode', tag: 'SingBox');
final activeTags = kr_activeGroups.map((g) => g.tag).join(', ');
KRLogUtil.kr_i('🧩 分组: all=${kr_allGroups.length}, active=${kr_activeGroups.length} [$activeTags]', tag: 'SingBoxTun');
// 1) TCP 1.1.1.1:443 IP HTTP
try {
final sock = await Socket.connect('1.1.1.1', 443, timeout: const Duration(seconds: 3));
sock.destroy();
KRLogUtil.kr_i('🌐 TCP 443连接成功 (1.1.1.1:443)', tag: 'SingBoxTun');
} catch (e) {
KRLogUtil.kr_e('🌐 TCP 443连接失败: $e', tag: 'SingBoxTun');
}
// 2) HTTPS 使 Google 204
try {
final client = HttpClient()..connectionTimeout = const Duration(seconds: 3);
final req = await client.getUrl(Uri.parse('https://connectivitycheck.gstatic.com/generate_204'));
req.headers.add('User-Agent', 'kr-debug');
final resp = await req.close();
KRLogUtil.kr_i('🔐 HTTPS 204 状态: ${resp.statusCode}', tag: 'SingBoxTun');
} catch (e) {
KRLogUtil.kr_e('🔐 HTTPS 204 错误: $e', tag: 'SingBoxTun');
}
// 3) DNS解析
try {
final addrs = await InternetAddress.lookup('google.com');
KRLogUtil.kr_i('🧭 DNS解析成功: ${addrs.map((a) => a.address).join(", ")}', tag: 'SingBoxTun');
} catch (e) {
KRLogUtil.kr_e('🧭 DNS解析错误: $e', tag: 'SingBoxTun');
}
// 4) TCP 53DNS TCP53可达性
try {
final sock = await Socket.connect('8.8.8.8', 53, timeout: const Duration(seconds: 3));
sock.destroy();
KRLogUtil.kr_i('🛰️ TCP 53连接成功 (8.8.8.8:53)', tag: 'SingBoxTun');
} catch (e) {
KRLogUtil.kr_w('🛰️ TCP 53连接失败: $e', tag: 'SingBoxTun');
}
KRLogUtil.kr_i('✅ [TUN Debug] 结束调试', tag: 'SingBoxTun');
}
}