初始化提交
This commit is contained in:
Executable
+274
@@ -0,0 +1,274 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: core.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class ParseConfigRequest extends $pb.GeneratedMessage {
|
||||
factory ParseConfigRequest({
|
||||
$core.String? tempPath,
|
||||
$core.String? path,
|
||||
$core.bool? debug,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (tempPath != null) {
|
||||
$result.tempPath = tempPath;
|
||||
}
|
||||
if (path != null) {
|
||||
$result.path = path;
|
||||
}
|
||||
if (debug != null) {
|
||||
$result.debug = debug;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
ParseConfigRequest._() : super();
|
||||
factory ParseConfigRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory ParseConfigRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ParseConfigRequest', package: const $pb.PackageName(_omitMessageNames ? '' : 'ConfigOptions'), createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'tempPath', protoName: 'tempPath')
|
||||
..aOS(2, _omitFieldNames ? '' : 'path')
|
||||
..aOB(3, _omitFieldNames ? '' : 'debug')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
ParseConfigRequest clone() => ParseConfigRequest()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
ParseConfigRequest copyWith(void Function(ParseConfigRequest) updates) => super.copyWith((message) => updates(message as ParseConfigRequest)) as ParseConfigRequest;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ParseConfigRequest create() => ParseConfigRequest._();
|
||||
ParseConfigRequest createEmptyInstance() => create();
|
||||
static $pb.PbList<ParseConfigRequest> createRepeated() => $pb.PbList<ParseConfigRequest>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ParseConfigRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ParseConfigRequest>(create);
|
||||
static ParseConfigRequest? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get tempPath => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set tempPath($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasTempPath() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearTempPath() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get path => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set path($core.String v) { $_setString(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasPath() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearPath() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool get debug => $_getBF(2);
|
||||
@$pb.TagNumber(3)
|
||||
set debug($core.bool v) { $_setBool(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasDebug() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearDebug() => clearField(3);
|
||||
}
|
||||
|
||||
class ParseConfigResponse extends $pb.GeneratedMessage {
|
||||
factory ParseConfigResponse({
|
||||
$core.String? error,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (error != null) {
|
||||
$result.error = error;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
ParseConfigResponse._() : super();
|
||||
factory ParseConfigResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory ParseConfigResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ParseConfigResponse', package: const $pb.PackageName(_omitMessageNames ? '' : 'ConfigOptions'), createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'error')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
ParseConfigResponse clone() => ParseConfigResponse()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
ParseConfigResponse copyWith(void Function(ParseConfigResponse) updates) => super.copyWith((message) => updates(message as ParseConfigResponse)) as ParseConfigResponse;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ParseConfigResponse create() => ParseConfigResponse._();
|
||||
ParseConfigResponse createEmptyInstance() => create();
|
||||
static $pb.PbList<ParseConfigResponse> createRepeated() => $pb.PbList<ParseConfigResponse>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ParseConfigResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ParseConfigResponse>(create);
|
||||
static ParseConfigResponse? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get error => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set error($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasError() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearError() => clearField(1);
|
||||
}
|
||||
|
||||
class GenerateConfigRequest extends $pb.GeneratedMessage {
|
||||
factory GenerateConfigRequest({
|
||||
$core.String? path,
|
||||
$core.bool? debug,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (path != null) {
|
||||
$result.path = path;
|
||||
}
|
||||
if (debug != null) {
|
||||
$result.debug = debug;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
GenerateConfigRequest._() : super();
|
||||
factory GenerateConfigRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory GenerateConfigRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'GenerateConfigRequest', package: const $pb.PackageName(_omitMessageNames ? '' : 'ConfigOptions'), createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'path')
|
||||
..aOB(2, _omitFieldNames ? '' : 'debug')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
GenerateConfigRequest clone() => GenerateConfigRequest()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
GenerateConfigRequest copyWith(void Function(GenerateConfigRequest) updates) => super.copyWith((message) => updates(message as GenerateConfigRequest)) as GenerateConfigRequest;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static GenerateConfigRequest create() => GenerateConfigRequest._();
|
||||
GenerateConfigRequest createEmptyInstance() => create();
|
||||
static $pb.PbList<GenerateConfigRequest> createRepeated() => $pb.PbList<GenerateConfigRequest>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static GenerateConfigRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<GenerateConfigRequest>(create);
|
||||
static GenerateConfigRequest? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get path => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set path($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasPath() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearPath() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool get debug => $_getBF(1);
|
||||
@$pb.TagNumber(2)
|
||||
set debug($core.bool v) { $_setBool(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasDebug() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearDebug() => clearField(2);
|
||||
}
|
||||
|
||||
class GenerateConfigResponse extends $pb.GeneratedMessage {
|
||||
factory GenerateConfigResponse({
|
||||
$core.String? config,
|
||||
$core.String? error,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (config != null) {
|
||||
$result.config = config;
|
||||
}
|
||||
if (error != null) {
|
||||
$result.error = error;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
GenerateConfigResponse._() : super();
|
||||
factory GenerateConfigResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory GenerateConfigResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'GenerateConfigResponse', package: const $pb.PackageName(_omitMessageNames ? '' : 'ConfigOptions'), createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'config')
|
||||
..aOS(2, _omitFieldNames ? '' : 'error')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
GenerateConfigResponse clone() => GenerateConfigResponse()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
GenerateConfigResponse copyWith(void Function(GenerateConfigResponse) updates) => super.copyWith((message) => updates(message as GenerateConfigResponse)) as GenerateConfigResponse;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static GenerateConfigResponse create() => GenerateConfigResponse._();
|
||||
GenerateConfigResponse createEmptyInstance() => create();
|
||||
static $pb.PbList<GenerateConfigResponse> createRepeated() => $pb.PbList<GenerateConfigResponse>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static GenerateConfigResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<GenerateConfigResponse>(create);
|
||||
static GenerateConfigResponse? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get config => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set config($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasConfig() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearConfig() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get error => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set error($core.String v) { $_setString(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasError() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearError() => clearField(2);
|
||||
}
|
||||
|
||||
|
||||
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: core.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
Executable
+79
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: core.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:async' as $async;
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:grpc/service_api.dart' as $grpc;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'core.pb.dart' as $0;
|
||||
|
||||
export 'core.pb.dart';
|
||||
|
||||
@$pb.GrpcServiceName('ConfigOptions.CoreService')
|
||||
class CoreServiceClient extends $grpc.Client {
|
||||
static final _$parseConfig = $grpc.ClientMethod<$0.ParseConfigRequest, $0.ParseConfigResponse>(
|
||||
'/ConfigOptions.CoreService/ParseConfig',
|
||||
($0.ParseConfigRequest value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.ParseConfigResponse.fromBuffer(value));
|
||||
static final _$generateFullConfig = $grpc.ClientMethod<$0.GenerateConfigRequest, $0.GenerateConfigResponse>(
|
||||
'/ConfigOptions.CoreService/GenerateFullConfig',
|
||||
($0.GenerateConfigRequest value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.GenerateConfigResponse.fromBuffer(value));
|
||||
|
||||
CoreServiceClient($grpc.ClientChannel channel,
|
||||
{$grpc.CallOptions? options,
|
||||
$core.Iterable<$grpc.ClientInterceptor>? interceptors})
|
||||
: super(channel, options: options,
|
||||
interceptors: interceptors);
|
||||
|
||||
$grpc.ResponseFuture<$0.ParseConfigResponse> parseConfig($0.ParseConfigRequest request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$parseConfig, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.GenerateConfigResponse> generateFullConfig($0.GenerateConfigRequest request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$generateFullConfig, request, options: options);
|
||||
}
|
||||
}
|
||||
|
||||
@$pb.GrpcServiceName('ConfigOptions.CoreService')
|
||||
abstract class CoreServiceBase extends $grpc.Service {
|
||||
$core.String get $name => 'ConfigOptions.CoreService';
|
||||
|
||||
CoreServiceBase() {
|
||||
$addMethod($grpc.ServiceMethod<$0.ParseConfigRequest, $0.ParseConfigResponse>(
|
||||
'ParseConfig',
|
||||
parseConfig_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.ParseConfigRequest.fromBuffer(value),
|
||||
($0.ParseConfigResponse value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.GenerateConfigRequest, $0.GenerateConfigResponse>(
|
||||
'GenerateFullConfig',
|
||||
generateFullConfig_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.GenerateConfigRequest.fromBuffer(value),
|
||||
($0.GenerateConfigResponse value) => value.writeToBuffer()));
|
||||
}
|
||||
|
||||
$async.Future<$0.ParseConfigResponse> parseConfig_Pre($grpc.ServiceCall call, $async.Future<$0.ParseConfigRequest> request) async {
|
||||
return parseConfig(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.GenerateConfigResponse> generateFullConfig_Pre($grpc.ServiceCall call, $async.Future<$0.GenerateConfigRequest> request) async {
|
||||
return generateFullConfig(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.ParseConfigResponse> parseConfig($grpc.ServiceCall call, $0.ParseConfigRequest request);
|
||||
$async.Future<$0.GenerateConfigResponse> generateFullConfig($grpc.ServiceCall call, $0.GenerateConfigRequest request);
|
||||
}
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: core.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
|
||||
@$core.Deprecated('Use parseConfigRequestDescriptor instead')
|
||||
const ParseConfigRequest$json = {
|
||||
'1': 'ParseConfigRequest',
|
||||
'2': [
|
||||
{'1': 'tempPath', '3': 1, '4': 1, '5': 9, '10': 'tempPath'},
|
||||
{'1': 'path', '3': 2, '4': 1, '5': 9, '10': 'path'},
|
||||
{'1': 'debug', '3': 3, '4': 1, '5': 8, '10': 'debug'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ParseConfigRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List parseConfigRequestDescriptor = $convert.base64Decode(
|
||||
'ChJQYXJzZUNvbmZpZ1JlcXVlc3QSGgoIdGVtcFBhdGgYASABKAlSCHRlbXBQYXRoEhIKBHBhdG'
|
||||
'gYAiABKAlSBHBhdGgSFAoFZGVidWcYAyABKAhSBWRlYnVn');
|
||||
|
||||
@$core.Deprecated('Use parseConfigResponseDescriptor instead')
|
||||
const ParseConfigResponse$json = {
|
||||
'1': 'ParseConfigResponse',
|
||||
'2': [
|
||||
{'1': 'error', '3': 1, '4': 1, '5': 9, '9': 0, '10': 'error', '17': true},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_error'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ParseConfigResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List parseConfigResponseDescriptor = $convert.base64Decode(
|
||||
'ChNQYXJzZUNvbmZpZ1Jlc3BvbnNlEhkKBWVycm9yGAEgASgJSABSBWVycm9yiAEBQggKBl9lcn'
|
||||
'Jvcg==');
|
||||
|
||||
@$core.Deprecated('Use generateConfigRequestDescriptor instead')
|
||||
const GenerateConfigRequest$json = {
|
||||
'1': 'GenerateConfigRequest',
|
||||
'2': [
|
||||
{'1': 'path', '3': 1, '4': 1, '5': 9, '10': 'path'},
|
||||
{'1': 'debug', '3': 2, '4': 1, '5': 8, '10': 'debug'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `GenerateConfigRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List generateConfigRequestDescriptor = $convert.base64Decode(
|
||||
'ChVHZW5lcmF0ZUNvbmZpZ1JlcXVlc3QSEgoEcGF0aBgBIAEoCVIEcGF0aBIUCgVkZWJ1ZxgCIA'
|
||||
'EoCFIFZGVidWc=');
|
||||
|
||||
@$core.Deprecated('Use generateConfigResponseDescriptor instead')
|
||||
const GenerateConfigResponse$json = {
|
||||
'1': 'GenerateConfigResponse',
|
||||
'2': [
|
||||
{'1': 'config', '3': 1, '4': 1, '5': 9, '10': 'config'},
|
||||
{'1': 'error', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'error', '17': true},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_error'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `GenerateConfigResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List generateConfigResponseDescriptor = $convert.base64Decode(
|
||||
'ChZHZW5lcmF0ZUNvbmZpZ1Jlc3BvbnNlEhYKBmNvbmZpZxgBIAEoCVIGY29uZmlnEhkKBWVycm'
|
||||
'9yGAIgASgJSABSBWVycm9yiAEBQggKBl9lcnJvcg==');
|
||||
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
// import 'package:kaer_with_panels/core/localization/translations.dart';
|
||||
import 'package:kaer_with_panels/utils/platform_utils.dart';
|
||||
|
||||
@JsonEnum(valueField: 'key')
|
||||
enum ServiceMode {
|
||||
proxy("proxy"),
|
||||
systemProxy("system-proxy"),
|
||||
tun("vpn"),
|
||||
tunService("vpn-service");
|
||||
|
||||
const ServiceMode(this.key);
|
||||
|
||||
final String key;
|
||||
|
||||
static ServiceMode get defaultMode =>
|
||||
PlatformUtils.isDesktop ? systemProxy : tun;
|
||||
|
||||
/// supported service mode based on platform, use this instead of [values] in UI
|
||||
static List<ServiceMode> get choices {
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
return values;
|
||||
} else if (Platform.isMacOS) {
|
||||
return [proxy, systemProxy, tun];
|
||||
}
|
||||
// mobile
|
||||
return [proxy, tun];
|
||||
}
|
||||
|
||||
bool get isExperimental => switch (this) {
|
||||
tun => PlatformUtils.isDesktop,
|
||||
tunService => PlatformUtils.isDesktop,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// String present(TranslationsEn t) => switch (this) {
|
||||
// proxy => t.config.serviceModes.proxy,
|
||||
// systemProxy => t.config.serviceModes.systemProxy,
|
||||
// tun =>
|
||||
// "${t.config.serviceModes.tun}${isExperimental ? " (${t.settings.experimental})" : ""}",
|
||||
// tunService =>
|
||||
// "${t.config.serviceModes.tunService}${isExperimental ? " (${t.settings.experimental})" : ""}",
|
||||
// };
|
||||
|
||||
// String presentShort(TranslationsEn t) => switch (this) {
|
||||
// proxy => t.config.shortServiceModes.proxy,
|
||||
// systemProxy => t.config.shortServiceModes.systemProxy,
|
||||
// tun => t.config.shortServiceModes.tun,
|
||||
// tunService => t.config.shortServiceModes.tunService,
|
||||
// };
|
||||
}
|
||||
|
||||
@JsonEnum(valueField: 'key')
|
||||
enum IPv6Mode {
|
||||
disable("ipv4_only"),
|
||||
enable("prefer_ipv4"),
|
||||
prefer("prefer_ipv6"),
|
||||
only("ipv6_only");
|
||||
|
||||
const IPv6Mode(this.key);
|
||||
|
||||
final String key;
|
||||
|
||||
// String present(TranslationsEn t) => switch (this) {
|
||||
// disable => t.config.ipv6Modes.disable,
|
||||
// enable => t.config.ipv6Modes.enable,
|
||||
// prefer => t.config.ipv6Modes.prefer,
|
||||
// only => t.config.ipv6Modes.only,
|
||||
// };
|
||||
}
|
||||
|
||||
@JsonEnum(valueField: 'key')
|
||||
enum DomainStrategy {
|
||||
auto(""),
|
||||
preferIpv6("prefer_ipv6"),
|
||||
preferIpv4("prefer_ipv4"),
|
||||
ipv4Only("ipv4_only"),
|
||||
ipv6Only("ipv6_only");
|
||||
|
||||
const DomainStrategy(this.key);
|
||||
|
||||
final String key;
|
||||
|
||||
String get displayName => switch (this) {
|
||||
auto => "auto",
|
||||
_ => key,
|
||||
};
|
||||
}
|
||||
|
||||
enum TunImplementation {
|
||||
mixed,
|
||||
system,
|
||||
gvisor;
|
||||
}
|
||||
|
||||
enum MuxProtocol {
|
||||
h2mux,
|
||||
smux,
|
||||
yamux;
|
||||
}
|
||||
|
||||
@JsonEnum(valueField: 'key')
|
||||
enum WarpDetourMode {
|
||||
proxyOverWarp("proxy_over_warp"),
|
||||
warpOverProxy("warp_over_proxy");
|
||||
|
||||
const WarpDetourMode(this.key);
|
||||
|
||||
final String key;
|
||||
|
||||
// String present(TranslationsEn t) => switch (this) {
|
||||
// proxyOverWarp => t.config.warpDetourModes.proxyOverWarp,
|
||||
// warpOverProxy => t.config.warpDetourModes.warpOverProxy,
|
||||
// };
|
||||
}
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:kaer_with_panels/core/model/optional_range.dart';
|
||||
import 'package:kaer_with_panels/core/utils/json_converters.dart';
|
||||
import 'package:kaer_with_panels/features/log/model/log_level.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_config_enum.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_rule.dart';
|
||||
|
||||
part 'singbox_config_option.freezed.dart';
|
||||
part 'singbox_config_option.g.dart';
|
||||
|
||||
@freezed
|
||||
class SingboxConfigOption with _$SingboxConfigOption {
|
||||
const SingboxConfigOption._();
|
||||
|
||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
||||
const factory SingboxConfigOption({
|
||||
required String region,
|
||||
required bool blockAds,
|
||||
required bool useXrayCoreWhenPossible,
|
||||
required bool executeConfigAsIs,
|
||||
required LogLevel logLevel,
|
||||
required bool resolveDestination,
|
||||
required IPv6Mode ipv6Mode,
|
||||
required String remoteDnsAddress,
|
||||
required DomainStrategy remoteDnsDomainStrategy,
|
||||
required String directDnsAddress,
|
||||
required DomainStrategy directDnsDomainStrategy,
|
||||
required int mixedPort,
|
||||
required int tproxyPort,
|
||||
required int localDnsPort,
|
||||
required TunImplementation tunImplementation,
|
||||
required int mtu,
|
||||
required bool strictRoute,
|
||||
required String connectionTestUrl,
|
||||
@IntervalInSecondsConverter() required Duration urlTestInterval,
|
||||
required bool enableClashApi,
|
||||
required int clashApiPort,
|
||||
required bool enableTun,
|
||||
required bool enableTunService,
|
||||
required bool setSystemProxy,
|
||||
required bool bypassLan,
|
||||
required bool allowConnectionFromLan,
|
||||
required bool enableFakeDns,
|
||||
required bool enableDnsRouting,
|
||||
required bool independentDnsCache,
|
||||
// required String geoipPath,
|
||||
// required String geositePath,
|
||||
required List<SingboxRule> rules,
|
||||
required SingboxMuxOption mux,
|
||||
required SingboxTlsTricks tlsTricks,
|
||||
required SingboxWarpOption warp,
|
||||
required SingboxWarpOption warp2,
|
||||
}) = _SingboxConfigOption;
|
||||
|
||||
String format() {
|
||||
const encoder = JsonEncoder.withIndent(' ');
|
||||
return encoder.convert(toJson());
|
||||
}
|
||||
|
||||
factory SingboxConfigOption.fromJson(Map<String, dynamic> json) =>
|
||||
_$SingboxConfigOptionFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SingboxWarpOption with _$SingboxWarpOption {
|
||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
||||
const factory SingboxWarpOption({
|
||||
required bool enable,
|
||||
required WarpDetourMode mode,
|
||||
required String wireguardConfig,
|
||||
required String licenseKey,
|
||||
required String accountId,
|
||||
required String accessToken,
|
||||
required String cleanIp,
|
||||
required int cleanPort,
|
||||
@OptionalRangeJsonConverter() required OptionalRange noise,
|
||||
@OptionalRangeJsonConverter() required OptionalRange noiseSize,
|
||||
@OptionalRangeJsonConverter() required OptionalRange noiseDelay,
|
||||
@OptionalRangeJsonConverter() required String noiseMode,
|
||||
}) = _SingboxWarpOption;
|
||||
|
||||
factory SingboxWarpOption.fromJson(Map<String, dynamic> json) =>
|
||||
_$SingboxWarpOptionFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SingboxMuxOption with _$SingboxMuxOption {
|
||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
||||
const factory SingboxMuxOption({
|
||||
required bool enable,
|
||||
required bool padding,
|
||||
required int maxStreams,
|
||||
required MuxProtocol protocol,
|
||||
}) = _SingboxMuxOption;
|
||||
|
||||
factory SingboxMuxOption.fromJson(Map<String, dynamic> json) =>
|
||||
_$SingboxMuxOptionFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SingboxTlsTricks with _$SingboxTlsTricks {
|
||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
||||
const factory SingboxTlsTricks({
|
||||
required bool enableFragment,
|
||||
@OptionalRangeJsonConverter() required OptionalRange fragmentSize,
|
||||
@OptionalRangeJsonConverter() required OptionalRange fragmentSleep,
|
||||
required bool mixedSniCase,
|
||||
required bool enablePadding,
|
||||
@OptionalRangeJsonConverter() required OptionalRange paddingSize,
|
||||
}) = _SingboxTlsTricks;
|
||||
|
||||
factory SingboxTlsTricks.fromJson(Map<String, dynamic> json) =>
|
||||
_$SingboxTlsTricksFromJson(json);
|
||||
}
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_proxy_type.dart';
|
||||
|
||||
part 'singbox_outbound.freezed.dart';
|
||||
part 'singbox_outbound.g.dart';
|
||||
|
||||
@freezed
|
||||
class SingboxOutboundGroup with _$SingboxOutboundGroup {
|
||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
||||
const factory SingboxOutboundGroup({
|
||||
required String tag,
|
||||
@JsonKey(fromJson: _typeFromJson) required ProxyType type,
|
||||
required String selected,
|
||||
@Default([]) List<SingboxOutboundGroupItem> items,
|
||||
}) = _SingboxOutboundGroup;
|
||||
|
||||
factory SingboxOutboundGroup.fromJson(Map<String, dynamic> json) =>
|
||||
_$SingboxOutboundGroupFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SingboxOutboundGroupItem with _$SingboxOutboundGroupItem {
|
||||
const SingboxOutboundGroupItem._();
|
||||
|
||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
||||
const factory SingboxOutboundGroupItem({
|
||||
required String tag,
|
||||
@JsonKey(fromJson: _typeFromJson) required ProxyType type,
|
||||
required int urlTestDelay,
|
||||
}) = _SingboxOutboundGroupItem;
|
||||
|
||||
factory SingboxOutboundGroupItem.fromJson(Map<String, dynamic> json) =>
|
||||
_$SingboxOutboundGroupItemFromJson(json);
|
||||
}
|
||||
|
||||
final Map<String, ProxyType> _keyMap =
|
||||
Map.fromEntries(ProxyType.values.map((e) => MapEntry(e.key, e)));
|
||||
|
||||
ProxyType _typeFromJson(dynamic type) => ProxyType.fromJson(type);
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
enum ProxyType {
|
||||
direct("Direct"),
|
||||
block("Block"),
|
||||
dns("DNS"),
|
||||
socks("SOCKS"),
|
||||
http("HTTP"),
|
||||
shadowsocks("Shadowsocks"),
|
||||
vmess("VMess"),
|
||||
trojan("Trojan"),
|
||||
naive("Naive"),
|
||||
wireguard("WireGuard"),
|
||||
hysteria("Hysteria"),
|
||||
tor("Tor"),
|
||||
ssh("SSH"),
|
||||
shadowtls("ShadowTLS"),
|
||||
shadowsocksr("ShadowsocksR"),
|
||||
vless("VLESS"),
|
||||
tuic("TUIC"),
|
||||
hysteria2("Hysteria2"),
|
||||
|
||||
selector("Selector"),
|
||||
urltest("URLTest"),
|
||||
warp("Warp"),
|
||||
|
||||
xvless("xVLESS"),
|
||||
xvmess("xVMess"),
|
||||
xtrojan("xTrojan"),
|
||||
xfreedom("xFragment"),
|
||||
xshadowsocks("xShadowsocks"),
|
||||
xsocks("xSocks"),
|
||||
invalid("Invalid"),
|
||||
unknown("Unknown");
|
||||
|
||||
const ProxyType(this.label);
|
||||
|
||||
final String label;
|
||||
|
||||
String get key => name;
|
||||
|
||||
static List<ProxyType> groupValues = [selector, urltest];
|
||||
|
||||
bool get isGroup => ProxyType.groupValues.contains(this);
|
||||
static final Map<String, ProxyType> _keyMap = Map.fromEntries(ProxyType.values.map((e) => MapEntry(e.key, e)));
|
||||
static ProxyType fromJson(dynamic type) => _keyMap[(type as String?)?.toLowerCase()] ?? ProxyType.unknown;
|
||||
}
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'singbox_rule.freezed.dart';
|
||||
part 'singbox_rule.g.dart';
|
||||
|
||||
@freezed
|
||||
class SingboxRule with _$SingboxRule {
|
||||
const SingboxRule._();
|
||||
|
||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
||||
const factory SingboxRule({
|
||||
String? ruleSetUrl,
|
||||
String? domains,
|
||||
String? ip,
|
||||
String? port,
|
||||
String? protocol,
|
||||
@Default(RuleNetwork.tcpAndUdp) RuleNetwork network,
|
||||
@Default(RuleOutbound.proxy) RuleOutbound outbound,
|
||||
}) = _SingboxRule;
|
||||
|
||||
factory SingboxRule.fromJson(Map<String, dynamic> json) => _$SingboxRuleFromJson(json);
|
||||
}
|
||||
|
||||
enum RuleOutbound { proxy, bypass, block }
|
||||
|
||||
@JsonEnum(valueField: 'key')
|
||||
enum RuleNetwork {
|
||||
tcpAndUdp(""),
|
||||
tcp("tcp"),
|
||||
udp("udp");
|
||||
|
||||
const RuleNetwork(this.key);
|
||||
|
||||
final String? key;
|
||||
}
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'singbox_stats.freezed.dart';
|
||||
part 'singbox_stats.g.dart';
|
||||
|
||||
@freezed
|
||||
class SingboxStats with _$SingboxStats {
|
||||
const SingboxStats._();
|
||||
|
||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
||||
const factory SingboxStats({
|
||||
required int connectionsIn,
|
||||
required int connectionsOut,
|
||||
required int uplink,
|
||||
required int downlink,
|
||||
required int uplinkTotal,
|
||||
required int downlinkTotal,
|
||||
}) = _SingboxStats;
|
||||
|
||||
factory SingboxStats.fromJson(Map<String, dynamic> json) =>
|
||||
_$SingboxStatsFromJson(json);
|
||||
}
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'singbox_status.freezed.dart';
|
||||
|
||||
@freezed
|
||||
sealed class SingboxStatus with _$SingboxStatus {
|
||||
const SingboxStatus._();
|
||||
|
||||
const factory SingboxStatus.stopped({
|
||||
SingboxAlert? alert,
|
||||
String? message,
|
||||
}) = SingboxStopped;
|
||||
const factory SingboxStatus.starting() = SingboxStarting;
|
||||
const factory SingboxStatus.started() = SingboxStarted;
|
||||
const factory SingboxStatus.stopping() = SingboxStopping;
|
||||
|
||||
factory SingboxStatus.fromEvent(dynamic event) {
|
||||
switch (event) {
|
||||
case {
|
||||
"status": "Stopped",
|
||||
"alert": final String? alertStr,
|
||||
"message": final String? messageStr,
|
||||
}:
|
||||
final alert = SingboxAlert.values.firstOrNullWhere(
|
||||
(e) => alertStr?.toLowerCase() == e.name.toLowerCase(),
|
||||
);
|
||||
return SingboxStatus.stopped(alert: alert, message: messageStr);
|
||||
case {"status": "Stopped"}:
|
||||
return const SingboxStatus.stopped();
|
||||
case {"status": "Starting"}:
|
||||
return const SingboxStarting();
|
||||
case {"status": "Started"}:
|
||||
return const SingboxStarted();
|
||||
case {"status": "Stopping"}:
|
||||
return const SingboxStopping();
|
||||
default:
|
||||
throw Exception("unexpected status [$event]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum SingboxAlert {
|
||||
requestVPNPermission,
|
||||
requestNotificationPermission,
|
||||
emptyConfiguration,
|
||||
startCommandServer,
|
||||
createService,
|
||||
startService;
|
||||
}
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
import 'dart:convert';
|
||||
|
||||
typedef WarpResponse = ({
|
||||
String log,
|
||||
String accountId,
|
||||
String accessToken,
|
||||
String wireguardConfig,
|
||||
});
|
||||
|
||||
WarpResponse warpFromJson(dynamic json) {
|
||||
if (json
|
||||
case {
|
||||
"account-id": final String newAccountId,
|
||||
"access-token": final String newAccessToken,
|
||||
"log": final String log,
|
||||
"config": final Map<String, dynamic> wireguardConfig,
|
||||
}) {
|
||||
return (
|
||||
log: log,
|
||||
accountId: newAccountId,
|
||||
accessToken: newAccessToken,
|
||||
wireguardConfig: jsonEncode(wireguardConfig),
|
||||
);
|
||||
}
|
||||
throw Exception("invalid response");
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
import 'package:kaer_with_panels/singbox/generated/core.pbgrpc.dart';
|
||||
import 'package:kaer_with_panels/singbox/service/singbox_service.dart';
|
||||
|
||||
abstract class CoreSingboxService extends CoreServiceClient
|
||||
implements SingboxService {
|
||||
CoreSingboxService()
|
||||
: super(
|
||||
ClientChannel(
|
||||
'localhost',
|
||||
port: 7078,
|
||||
options: const ChannelOptions(
|
||||
credentials: ChannelCredentials.insecure(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> validateConfigByPath(
|
||||
String path,
|
||||
String tempPath,
|
||||
bool debug,
|
||||
) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
final response = await parseConfig(
|
||||
ParseConfigRequest(tempPath: tempPath, path: path, debug: false),
|
||||
);
|
||||
if (response.error != "") return left(response.error);
|
||||
return right(unit);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, String> generateFullConfigByPath(String path) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
final response = await generateFullConfig(
|
||||
GenerateConfigRequest(path: path, debug: false),
|
||||
);
|
||||
if (response.error != "") return left(response.error);
|
||||
return right(response.config);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Executable
+489
@@ -0,0 +1,489 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
// import 'package:combine/combine.dart'; // 暂时注释掉,使用 Isolate.run 替代
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:kaer_with_panels/core/model/directories.dart';
|
||||
import 'package:kaer_with_panels/gen/singbox_generated_bindings.dart';
|
||||
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_config_option.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_outbound.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_stats.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_status.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/warp_account.dart';
|
||||
import 'package:kaer_with_panels/singbox/service/singbox_service.dart';
|
||||
import 'package:kaer_with_panels/utils/utils.dart';
|
||||
import 'package:loggy/loggy.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:watcher/watcher.dart';
|
||||
|
||||
final _logger = Loggy('FFISingboxService');
|
||||
|
||||
class FFISingboxService with InfraLogger implements SingboxService {
|
||||
static final SingboxNativeLibrary _box = _gen();
|
||||
|
||||
late final ValueStream<SingboxStatus> _status;
|
||||
late final ReceivePort _statusReceiver;
|
||||
Stream<SingboxStats>? _serviceStatsStream;
|
||||
Stream<List<SingboxOutboundGroup>>? _outboundsStream;
|
||||
|
||||
static SingboxNativeLibrary _gen() {
|
||||
String fullPath = "";
|
||||
if (Platform.environment.containsKey('FLUTTER_TEST')) {
|
||||
fullPath = "libcore";
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
fullPath = p.join(fullPath, "libcore.dll");
|
||||
} else if (Platform.isMacOS) {
|
||||
fullPath = p.join(fullPath, "libcore.dylib");
|
||||
} else {
|
||||
fullPath = p.join(fullPath, "libcore.so");
|
||||
}
|
||||
_logger.debug('singbox native libs path: "$fullPath"');
|
||||
final lib = DynamicLibrary.open(fullPath);
|
||||
return SingboxNativeLibrary(lib);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
Future<void> init() async {
|
||||
loggy.debug("initializing");
|
||||
_statusReceiver = ReceivePort('service status receiver');
|
||||
final source = _statusReceiver
|
||||
.asBroadcastStream()
|
||||
.map((event) => jsonDecode(event as String))
|
||||
.map(SingboxStatus.fromEvent);
|
||||
_status = ValueConnectableStream.seeded(
|
||||
source,
|
||||
const SingboxStopped(),
|
||||
).autoConnect();
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> setup(
|
||||
Directories directories,
|
||||
bool debug,
|
||||
) {
|
||||
final port = _statusReceiver.sendPort.nativePort;
|
||||
return TaskEither(
|
||||
() => Isolate.run(
|
||||
() {
|
||||
_box.setupOnce(NativeApi.initializeApiDLData);
|
||||
final err = _box
|
||||
.setup(
|
||||
directories.baseDir.path.toNativeUtf8().cast(),
|
||||
directories.workingDir.path.toNativeUtf8().cast(),
|
||||
directories.tempDir.path.toNativeUtf8().cast(),
|
||||
port,
|
||||
debug ? 1 : 0,
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
return left(err);
|
||||
}
|
||||
return right(unit);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> validateConfigByPath(
|
||||
String path,
|
||||
String tempPath,
|
||||
bool debug,
|
||||
) {
|
||||
return TaskEither(
|
||||
() => Isolate.run(
|
||||
() {
|
||||
final err = _box
|
||||
.parse(
|
||||
path.toNativeUtf8().cast(),
|
||||
tempPath.toNativeUtf8().cast(),
|
||||
debug ? 1 : 0,
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
return left(err);
|
||||
}
|
||||
return right(unit);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> changeOptions(SingboxConfigOption options) {
|
||||
return TaskEither(
|
||||
() => Isolate.run(
|
||||
() {
|
||||
final json = jsonEncode(options.toJson());
|
||||
final err = _box
|
||||
.changeHiddifyOptions(json.toNativeUtf8().cast())
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
return left(err);
|
||||
}
|
||||
return right(unit);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, String> generateFullConfigByPath(
|
||||
String path,
|
||||
) {
|
||||
return TaskEither(
|
||||
() => Isolate.run(
|
||||
() {
|
||||
final response = _box
|
||||
.generateConfig(
|
||||
path.toNativeUtf8().cast(),
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (response.startsWith("error")) {
|
||||
return left(response.replaceFirst("error", ""));
|
||||
}
|
||||
return right(response);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> start(
|
||||
String configPath,
|
||||
String name,
|
||||
bool disableMemoryLimit,
|
||||
) {
|
||||
loggy.debug("starting, memory limit: [${!disableMemoryLimit}]");
|
||||
return TaskEither(
|
||||
() => Isolate.run(
|
||||
() {
|
||||
final err = _box
|
||||
.start(
|
||||
configPath.toNativeUtf8().cast(),
|
||||
disableMemoryLimit ? 1 : 0,
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
return left(err);
|
||||
}
|
||||
return right(unit);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> stop() {
|
||||
return TaskEither(
|
||||
() => Isolate.run(
|
||||
() {
|
||||
final err = _box.stop().cast<Utf8>().toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
return left(err);
|
||||
}
|
||||
return right(unit);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> restart(
|
||||
String configPath,
|
||||
String name,
|
||||
bool disableMemoryLimit,
|
||||
) {
|
||||
loggy.debug("restarting, memory limit: [${!disableMemoryLimit}]");
|
||||
return TaskEither(
|
||||
() => Isolate.run(
|
||||
() {
|
||||
final err = _box
|
||||
.restart(
|
||||
configPath.toNativeUtf8().cast(),
|
||||
disableMemoryLimit ? 1 : 0,
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
return left(err);
|
||||
}
|
||||
return right(unit);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> resetTunnel() {
|
||||
throw UnimplementedError(
|
||||
"reset tunnel function unavailable on platform",
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<SingboxStatus> watchStatus() => _status;
|
||||
|
||||
@override
|
||||
Stream<SingboxStats> watchStats() {
|
||||
if (_serviceStatsStream != null) return _serviceStatsStream!;
|
||||
final receiver = ReceivePort('stats');
|
||||
final statusStream = receiver.asBroadcastStream(
|
||||
onCancel: (_) {
|
||||
_logger.debug("stopping stats command client");
|
||||
final err = _box.stopCommandClient(1).cast<Utf8>().toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
_logger.error("error stopping stats client");
|
||||
}
|
||||
receiver.close();
|
||||
_serviceStatsStream = null;
|
||||
},
|
||||
).map(
|
||||
(event) {
|
||||
if (event case String _) {
|
||||
if (event.startsWith('error:')) {
|
||||
loggy.error("[service stats client] error received: $event");
|
||||
throw event.replaceFirst('error:', "");
|
||||
}
|
||||
return SingboxStats.fromJson(
|
||||
jsonDecode(event) as Map<String, dynamic>,
|
||||
);
|
||||
}
|
||||
loggy.error("[service status client] unexpected type, msg: $event");
|
||||
throw "invalid type";
|
||||
},
|
||||
);
|
||||
|
||||
final err = _box
|
||||
.startCommandClient(1, receiver.sendPort.nativePort)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
loggy.error("error starting status command: $err");
|
||||
throw err;
|
||||
}
|
||||
|
||||
return _serviceStatsStream = statusStream;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<SingboxOutboundGroup>> watchGroups() {
|
||||
final logger = newLoggy("watchGroups");
|
||||
if (_outboundsStream != null) return _outboundsStream!;
|
||||
final receiver = ReceivePort('groups');
|
||||
final outboundsStream = receiver.asBroadcastStream(
|
||||
onCancel: (_) {
|
||||
logger.debug("stopping");
|
||||
receiver.close();
|
||||
_outboundsStream = null;
|
||||
final err = _box.stopCommandClient(5).cast<Utf8>().toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
_logger.error("error stopping group client");
|
||||
}
|
||||
},
|
||||
).map(
|
||||
(event) {
|
||||
if (event case String _) {
|
||||
if (event.startsWith('error:')) {
|
||||
logger.error("error received: $event");
|
||||
throw event.replaceFirst('error:', "");
|
||||
}
|
||||
|
||||
return (jsonDecode(event) as List).map((e) {
|
||||
return SingboxOutboundGroup.fromJson(e as Map<String, dynamic>);
|
||||
}).toList();
|
||||
}
|
||||
logger.error("unexpected type, msg: $event");
|
||||
throw "invalid type";
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
final err = _box
|
||||
.startCommandClient(5, receiver.sendPort.nativePort)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
logger.error("error starting group command: $err");
|
||||
throw err;
|
||||
}
|
||||
} catch (e) {
|
||||
receiver.close();
|
||||
rethrow;
|
||||
}
|
||||
|
||||
return _outboundsStream = outboundsStream;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<SingboxOutboundGroup>> watchActiveGroups() {
|
||||
final logger = newLoggy("[ActiveGroupsClient]");
|
||||
final receiver = ReceivePort('active groups');
|
||||
final outboundsStream = receiver.asBroadcastStream(
|
||||
onCancel: (_) {
|
||||
logger.debug("stopping");
|
||||
receiver.close();
|
||||
final err = _box.stopCommandClient(13).cast<Utf8>().toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
logger.error("failed stopping: $err");
|
||||
}
|
||||
},
|
||||
).map(
|
||||
(event) {
|
||||
if (event case String _) {
|
||||
if (event.startsWith('error:')) {
|
||||
logger.error(event);
|
||||
throw event.replaceFirst('error:', "");
|
||||
}
|
||||
|
||||
return (jsonDecode(event) as List).map((e) {
|
||||
return SingboxOutboundGroup.fromJson(e as Map<String, dynamic>);
|
||||
}).toList();
|
||||
}
|
||||
logger.error("unexpected type, msg: $event");
|
||||
throw "invalid type";
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
final err = _box
|
||||
.startCommandClient(13, receiver.sendPort.nativePort)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
logger.error("error starting: $err");
|
||||
throw err;
|
||||
}
|
||||
} catch (e) {
|
||||
receiver.close();
|
||||
rethrow;
|
||||
}
|
||||
|
||||
return outboundsStream;
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> selectOutbound(String groupTag, String outboundTag) {
|
||||
return TaskEither(
|
||||
() => Isolate.run(
|
||||
() {
|
||||
final err = _box
|
||||
.selectOutbound(
|
||||
groupTag.toNativeUtf8().cast(),
|
||||
outboundTag.toNativeUtf8().cast(),
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
return left(err);
|
||||
}
|
||||
return right(unit);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> urlTest(String groupTag) {
|
||||
return TaskEither(
|
||||
() => Isolate.run(
|
||||
() {
|
||||
final err = _box
|
||||
.urlTest(groupTag.toNativeUtf8().cast())
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
return left(err);
|
||||
}
|
||||
return right(unit);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final _logBuffer = <String>[];
|
||||
int _logFilePosition = 0;
|
||||
|
||||
@override
|
||||
Stream<List<String>> watchLogs(String path) async* {
|
||||
yield await _readLogFile(File(path));
|
||||
yield* Watcher(path, pollingDelay: const Duration(seconds: 1))
|
||||
.events
|
||||
.asyncMap((event) async {
|
||||
if (event.type == ChangeType.MODIFY) {
|
||||
await _readLogFile(File(path));
|
||||
}
|
||||
return _logBuffer;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> clearLogs() {
|
||||
return TaskEither(
|
||||
() => Isolate.run(
|
||||
() {
|
||||
_logBuffer.clear();
|
||||
return right(unit);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<String>> _readLogFile(File file) async {
|
||||
if (_logFilePosition == 0 && file.lengthSync() == 0) return [];
|
||||
final content =
|
||||
await file.openRead(_logFilePosition).transform(utf8.decoder).join();
|
||||
_logFilePosition = file.lengthSync();
|
||||
final lines = const LineSplitter().convert(content);
|
||||
if (lines.length > 300) {
|
||||
lines.removeRange(0, lines.length - 300);
|
||||
}
|
||||
for (final line in lines) {
|
||||
_logBuffer.add(line);
|
||||
if (_logBuffer.length > 300) {
|
||||
_logBuffer.removeAt(0);
|
||||
}
|
||||
}
|
||||
return _logBuffer;
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, WarpResponse> generateWarpConfig({
|
||||
required String licenseKey,
|
||||
required String previousAccountId,
|
||||
required String previousAccessToken,
|
||||
}) {
|
||||
loggy.debug("generating warp config");
|
||||
return TaskEither(
|
||||
() => Isolate.run(
|
||||
() {
|
||||
final response = _box
|
||||
.generateWarpConfig(
|
||||
licenseKey.toNativeUtf8().cast(),
|
||||
previousAccountId.toNativeUtf8().cast(),
|
||||
previousAccessToken.toNativeUtf8().cast(),
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (response.startsWith("error:")) {
|
||||
return left(response.replaceFirst('error:', ""));
|
||||
}
|
||||
return right(warpFromJson(jsonDecode(response)));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:kaer_with_panels/core/model/directories.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_config_option.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_outbound.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_stats.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_status.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/warp_account.dart';
|
||||
import 'package:kaer_with_panels/singbox/service/singbox_service.dart';
|
||||
import 'package:kaer_with_panels/utils/custom_loggers.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
class PlatformSingboxService with InfraLogger implements SingboxService {
|
||||
static const channelPrefix = "com.baer.app";
|
||||
|
||||
static const methodChannel = MethodChannel("$channelPrefix/method");
|
||||
static const statusChannel =
|
||||
EventChannel("$channelPrefix/service.status", JSONMethodCodec());
|
||||
static const alertsChannel =
|
||||
EventChannel("$channelPrefix/service.alerts", JSONMethodCodec());
|
||||
static const statsChannel =
|
||||
EventChannel("$channelPrefix/stats", JSONMethodCodec());
|
||||
static const groupsChannel = EventChannel("$channelPrefix/groups");
|
||||
static const activeGroupsChannel =
|
||||
EventChannel("$channelPrefix/active-groups");
|
||||
static const logsChannel = EventChannel("$channelPrefix/service.logs");
|
||||
|
||||
late final ValueStream<SingboxStatus> _status;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
loggy.debug("initializing");
|
||||
final status =
|
||||
statusChannel.receiveBroadcastStream().map(SingboxStatus.fromEvent);
|
||||
final alerts =
|
||||
alertsChannel.receiveBroadcastStream().map(SingboxStatus.fromEvent);
|
||||
|
||||
_status = ValueConnectableStream(Rx.merge([status, alerts])).autoConnect();
|
||||
await _status.first;
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> setup(Directories directories, bool debug) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
if (!Platform.isIOS) {
|
||||
return right(unit);
|
||||
}
|
||||
|
||||
await methodChannel.invokeMethod("setup");
|
||||
return right(unit);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> validateConfigByPath(
|
||||
String path,
|
||||
String tempPath,
|
||||
bool debug,
|
||||
) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
final message = await methodChannel.invokeMethod<String>(
|
||||
"parse_config",
|
||||
{"path": path, "tempPath": tempPath, "debug": debug},
|
||||
);
|
||||
if (message == null || message.isEmpty) return right(unit);
|
||||
return left(message);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> changeOptions(SingboxConfigOption options) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
loggy.debug("changing options");
|
||||
await methodChannel.invokeMethod(
|
||||
"change_hiddify_options",
|
||||
jsonEncode(options.toJson()),
|
||||
);
|
||||
return right(unit);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, String> generateFullConfigByPath(String path) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
loggy.debug("generating full config by path");
|
||||
final configJson = await methodChannel.invokeMethod<String>(
|
||||
"generate_config",
|
||||
{"path": path},
|
||||
);
|
||||
if (configJson == null || configJson.isEmpty) {
|
||||
return left("null response");
|
||||
}
|
||||
return right(configJson);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> start(
|
||||
String path,
|
||||
String name,
|
||||
bool disableMemoryLimit,
|
||||
) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
loggy.debug("starting");
|
||||
await methodChannel.invokeMethod(
|
||||
"start",
|
||||
{"path": path, "name": name},
|
||||
);
|
||||
return right(unit);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> stop() {
|
||||
return TaskEither(
|
||||
() async {
|
||||
loggy.debug("stopping");
|
||||
await methodChannel.invokeMethod("stop");
|
||||
return right(unit);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> restart(
|
||||
String path,
|
||||
String name,
|
||||
bool disableMemoryLimit,
|
||||
) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
loggy.debug("restarting");
|
||||
await methodChannel.invokeMethod(
|
||||
"restart",
|
||||
{"path": path, "name": name},
|
||||
);
|
||||
return right(unit);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> resetTunnel() {
|
||||
return TaskEither(
|
||||
() async {
|
||||
// only available on iOS (and macOS later)
|
||||
if (!Platform.isIOS) {
|
||||
throw UnimplementedError(
|
||||
"reset tunnel function unavailable on platform",
|
||||
);
|
||||
}
|
||||
|
||||
loggy.debug("resetting tunnel");
|
||||
await methodChannel.invokeMethod("reset");
|
||||
return right(unit);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<SingboxOutboundGroup>> watchGroups() {
|
||||
loggy.debug("watching groups");
|
||||
return groupsChannel.receiveBroadcastStream().map(
|
||||
(event) {
|
||||
if (event case String _) {
|
||||
return (jsonDecode(event) as List).map((e) {
|
||||
return SingboxOutboundGroup.fromJson(e as Map<String, dynamic>);
|
||||
}).toList();
|
||||
}
|
||||
loggy.error("[group client] unexpected type, msg: $event");
|
||||
throw "invalid type";
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<SingboxOutboundGroup>> watchActiveGroups() {
|
||||
loggy.debug("watching active groups");
|
||||
return activeGroupsChannel.receiveBroadcastStream().map(
|
||||
(event) {
|
||||
if (event case String _) {
|
||||
return (jsonDecode(event) as List).map((e) {
|
||||
return SingboxOutboundGroup.fromJson(e as Map<String, dynamic>);
|
||||
}).toList();
|
||||
}
|
||||
loggy.error("[active group client] unexpected type, msg: $event");
|
||||
throw "invalid type";
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<SingboxStatus> watchStatus() => _status;
|
||||
|
||||
@override
|
||||
Stream<SingboxStats> watchStats() {
|
||||
loggy.debug("watching stats");
|
||||
return statsChannel.receiveBroadcastStream().map(
|
||||
(event) {
|
||||
if (event case Map<String, dynamic> _) {
|
||||
return SingboxStats.fromJson(event);
|
||||
}
|
||||
loggy.error(
|
||||
"[stats client] unexpected type(${event.runtimeType}), msg: $event",
|
||||
);
|
||||
throw "invalid type";
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> selectOutbound(String groupTag, String outboundTag) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
loggy.debug("selecting outbound");
|
||||
await methodChannel.invokeMethod(
|
||||
"select_outbound",
|
||||
{"groupTag": groupTag, "outboundTag": outboundTag},
|
||||
);
|
||||
return right(unit);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> urlTest(String groupTag) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
await methodChannel.invokeMethod(
|
||||
"url_test",
|
||||
{"groupTag": groupTag},
|
||||
);
|
||||
return right(unit);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<String>> watchLogs(String path) async* {
|
||||
yield* logsChannel
|
||||
.receiveBroadcastStream()
|
||||
.map((event) => (event as List).map((e) => e as String).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> clearLogs() {
|
||||
return TaskEither(
|
||||
() async {
|
||||
await methodChannel.invokeMethod("clear_logs");
|
||||
return right(unit);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, WarpResponse> generateWarpConfig({
|
||||
required String licenseKey,
|
||||
required String previousAccountId,
|
||||
required String previousAccessToken,
|
||||
}) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
loggy.debug("generating warp config");
|
||||
final warpConfig = await methodChannel.invokeMethod(
|
||||
"generate_warp_config",
|
||||
{
|
||||
"license-key": licenseKey,
|
||||
"previous-account-id": previousAccountId,
|
||||
"previous-access-token": previousAccessToken,
|
||||
},
|
||||
);
|
||||
return right(warpFromJson(jsonDecode(warpConfig as String)));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Executable
+96
@@ -0,0 +1,96 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:kaer_with_panels/core/model/directories.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_config_option.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_outbound.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_stats.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_status.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/warp_account.dart';
|
||||
import 'package:kaer_with_panels/singbox/service/ffi_singbox_service.dart';
|
||||
import 'package:kaer_with_panels/singbox/service/platform_singbox_service.dart';
|
||||
|
||||
abstract interface class SingboxService {
|
||||
factory SingboxService() {
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
return PlatformSingboxService();
|
||||
} else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
|
||||
return FFISingboxService();
|
||||
}
|
||||
throw Exception("unsupported platform");
|
||||
}
|
||||
|
||||
Future<void> init();
|
||||
|
||||
/// setup directories and other initial platform services
|
||||
TaskEither<String, Unit> setup(
|
||||
Directories directories,
|
||||
bool debug,
|
||||
);
|
||||
|
||||
/// validates config by path and save it
|
||||
///
|
||||
/// [path] is used to save validated config
|
||||
/// [tempPath] includes base config, possibly invalid
|
||||
/// [debug] indicates if debug mode (avoid in prod)
|
||||
TaskEither<String, Unit> validateConfigByPath(
|
||||
String path,
|
||||
String tempPath,
|
||||
bool debug,
|
||||
);
|
||||
|
||||
TaskEither<String, Unit> changeOptions(SingboxConfigOption options);
|
||||
|
||||
/// generates full sing-box configuration
|
||||
///
|
||||
/// [path] is the path to the base config file
|
||||
/// returns full patched json config file as string
|
||||
TaskEither<String, String> generateFullConfigByPath(String path);
|
||||
|
||||
/// start sing-box service
|
||||
///
|
||||
/// [path] is the path to the base config file (to be patched by previously set [SingboxConfigOption])
|
||||
/// [name] is the name of the active profile (not unique, used for presentation in platform specific ui)
|
||||
/// [disableMemoryLimit] is used to disable service memory limit (mostly used in mobile platforms i.e. iOS)
|
||||
TaskEither<String, Unit> start(
|
||||
String path,
|
||||
String name,
|
||||
bool disableMemoryLimit,
|
||||
);
|
||||
|
||||
TaskEither<String, Unit> stop();
|
||||
|
||||
/// similar to [start], but uses platform dependent behavior to restart the service
|
||||
|
||||
TaskEither<String, Unit> restart(
|
||||
String path,
|
||||
String name,
|
||||
bool disableMemoryLimit,
|
||||
);
|
||||
|
||||
TaskEither<String, Unit> resetTunnel();
|
||||
|
||||
Stream<List<SingboxOutboundGroup>> watchGroups();
|
||||
|
||||
Stream<List<SingboxOutboundGroup>> watchActiveGroups();
|
||||
|
||||
TaskEither<String, Unit> selectOutbound(String groupTag, String outboundTag);
|
||||
|
||||
TaskEither<String, Unit> urlTest(String groupTag);
|
||||
|
||||
/// watch status of sing-box service (started, starting, etc.)
|
||||
Stream<SingboxStatus> watchStatus();
|
||||
|
||||
/// watch stats of sing-box service (uplink, downlink, etc.)
|
||||
Stream<SingboxStats> watchStats();
|
||||
|
||||
Stream<List<String>> watchLogs(String path);
|
||||
|
||||
TaskEither<String, Unit> clearLogs();
|
||||
|
||||
TaskEither<String, WarpResponse> generateWarpConfig({
|
||||
required String licenseKey,
|
||||
required String previousAccountId,
|
||||
required String previousAccessToken,
|
||||
});
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import 'package:kaer_with_panels/singbox/service/singbox_service.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'singbox_service_provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
SingboxService singboxService(SingboxServiceRef ref) {
|
||||
return SingboxService();
|
||||
}
|
||||
Reference in New Issue
Block a user