diff --git a/lib/hiddifycore/core_start_failure.dart b/lib/hiddifycore/core_start_failure.dart new file mode 100644 index 0000000000..f11292610c --- /dev/null +++ b/lib/hiddifycore/core_start_failure.dart @@ -0,0 +1,17 @@ +import 'package:grpc/grpc.dart'; +import 'package:hiddify/features/connection/model/connection_failure.dart'; +import 'package:hiddify/singbox/model/core_status.dart'; + +({CoreStatus status, ConnectionFailure failure}) mapCoreStartGrpcError(GrpcError error) { + final grpcMessage = error.message?.trim(); + final detail = [ + 'failed to start background core', + error.codeName, + if (grpcMessage != null && grpcMessage.isNotEmpty) grpcMessage, + ].join(': '); + final status = CoreStatus.stopped(alert: CoreAlert.startFailed, message: detail); + final failure = error.code == StatusCode.unavailable + ? ConnectionFailure.backgroundCoreNotAvailable(detail) + : ConnectionFailure.unexpected(detail); + return (status: status, failure: failure); +} diff --git a/lib/hiddifycore/hiddify_core_service.dart b/lib/hiddifycore/hiddify_core_service.dart index fd4b31743c..068696e583 100644 --- a/lib/hiddifycore/hiddify_core_service.dart +++ b/lib/hiddifycore/hiddify_core_service.dart @@ -11,6 +11,7 @@ import 'package:hiddify/core/preferences/general_preferences.dart'; import 'package:hiddify/features/connection/model/connection_failure.dart'; import 'package:hiddify/features/settings/data/config_option_repository.dart'; import 'package:hiddify/hiddifycore/core_interface/core_interface.dart'; +import 'package:hiddify/hiddifycore/core_start_failure.dart'; import 'package:hiddify/hiddifycore/generated/v2/hcommon/common.pb.dart'; import 'package:hiddify/hiddifycore/generated/v2/hcore/hcore.pb.dart'; import 'package:hiddify/hiddifycore/generated/v2/hcore/hcore_service.pbgrpc.dart'; @@ -184,15 +185,10 @@ class HiddifyCoreService with InfraLogger { } } on GrpcError catch (e) { loggy.error("failed to start bg core: $e"); + final (:status, :failure) = mapCoreStartGrpcError(e); + statusController.add(currentState = status); ref.read(coreRestartSignalProvider.notifier).restart(); - if (e.code == StatusCode.unavailable) { - return left(const ConnectionFailure.unexpected("background core is not started yet!")); - } - // throw InvalidConfig(e.message); - // throw DioException.connectionError(requestOptions: RequestOptions(), reason: e.codeName, error: e); - - // throw DioException(requestOptions: RequestOptions(), error: e); - return left(const ConnectionFailure.unexpected("failed to start background core")); + return left(failure); } // if (res.messageType != MessageType.EMPTY) return left(res); diff --git a/test/hiddifycore/core_start_failure_test.dart b/test/hiddifycore/core_start_failure_test.dart new file mode 100644 index 0000000000..20f875e015 --- /dev/null +++ b/test/hiddifycore/core_start_failure_test.dart @@ -0,0 +1,35 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:grpc/grpc.dart'; +import 'package:hiddify/features/connection/model/connection_failure.dart'; +import 'package:hiddify/hiddifycore/core_start_failure.dart'; +import 'package:hiddify/singbox/model/core_status.dart'; + +void main() { + test('preserves gRPC details and publishes a stopped status', () { + final result = mapCoreStartGrpcError( + const GrpcError.unknown('start service: detour to an empty direct outbound makes no sense'), + ); + + const detail = + 'failed to start background core: UNKNOWN: ' + 'start service: detour to an empty direct outbound makes no sense'; + expect(result.status, const CoreStatus.stopped(alert: CoreAlert.startFailed, message: detail)); + expect(result.failure, const ConnectionFailure.unexpected(detail)); + }); + + test('classifies an unavailable Core separately', () { + final result = mapCoreStartGrpcError(const GrpcError.unavailable('connection refused')); + + const detail = 'failed to start background core: UNAVAILABLE: connection refused'; + expect(result.status, const CoreStatus.stopped(alert: CoreAlert.startFailed, message: detail)); + expect(result.failure, const ConnectionFailure.backgroundCoreNotAvailable(detail)); + }); + + test('does not add an empty message segment', () { + final result = mapCoreStartGrpcError(const GrpcError.unknown()); + + const detail = 'failed to start background core: UNKNOWN'; + expect(result.status, const CoreStatus.stopped(alert: CoreAlert.startFailed, message: detail)); + expect(result.failure, const ConnectionFailure.unexpected(detail)); + }); +}