Add proxy routing and sing-box support
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'src/singbox_proxy_api.g.dart';
|
||||
|
||||
export 'src/singbox_proxy_api.g.dart'
|
||||
show
|
||||
SingboxProxyConfigResult,
|
||||
SingboxProxyDnsConfig,
|
||||
SingboxProxyDnsServerConfig,
|
||||
SingboxProxyLogMessage,
|
||||
SingboxProxyProfile,
|
||||
SingboxProxyProfileType,
|
||||
SingboxProxyRuntimeEndpoint,
|
||||
SingboxProxyRuntimeOptions,
|
||||
SingboxProxyRuntimeState,
|
||||
SingboxProxyRuntimeStatus;
|
||||
|
||||
class FlutterSingboxProxy implements SingboxProxyEventsApi {
|
||||
FlutterSingboxProxy({SingboxProxyApi? api})
|
||||
: _api = api ?? SingboxProxyApi() {
|
||||
SingboxProxyEventsApi.setUp(this);
|
||||
}
|
||||
|
||||
final SingboxProxyApi _api;
|
||||
final _stateController =
|
||||
StreamController<SingboxProxyRuntimeState>.broadcast();
|
||||
final _logController = StreamController<SingboxProxyLogMessage>.broadcast();
|
||||
|
||||
Stream<SingboxProxyRuntimeState> get stateStream => _stateController.stream;
|
||||
Stream<SingboxProxyLogMessage> get logStream => _logController.stream;
|
||||
|
||||
Future<String?> validateProfile(SingboxProxyProfile profile) {
|
||||
return _api.validateProfile(profile);
|
||||
}
|
||||
|
||||
Future<SingboxProxyConfigResult> buildConfig(
|
||||
List<SingboxProxyProfile> profiles, {
|
||||
SingboxProxyRuntimeOptions? options,
|
||||
}) {
|
||||
return _api.buildConfig(profiles, options ?? SingboxProxyRuntimeOptions());
|
||||
}
|
||||
|
||||
Future<SingboxProxyRuntimeState> start(
|
||||
List<SingboxProxyProfile> profiles, {
|
||||
SingboxProxyRuntimeOptions? options,
|
||||
}) {
|
||||
return _api.start(profiles, options ?? SingboxProxyRuntimeOptions());
|
||||
}
|
||||
|
||||
Future<void> stop(List<String> profileIds) => _api.stop(profileIds);
|
||||
|
||||
Future<void> stopAll() => _api.stopAll();
|
||||
|
||||
Future<SingboxProxyRuntimeState> getState() => _api.getState();
|
||||
|
||||
@override
|
||||
void onStateChanged(SingboxProxyRuntimeState state) {
|
||||
_stateController.add(state);
|
||||
}
|
||||
|
||||
@override
|
||||
void onLogMessage(SingboxProxyLogMessage message) {
|
||||
_logController.add(message);
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
SingboxProxyEventsApi.setUp(null);
|
||||
await _stateController.close();
|
||||
await _logController.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,912 @@
|
||||
// Autogenerated from Pigeon (v26.3.2), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
// ignore_for_file: unused_import, unused_shown_name
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data' show Float64List, Int32List, Int64List;
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
||||
|
||||
Object? _extractReplyValueOrThrow(
|
||||
List<Object?>? replyList,
|
||||
String channelName, {
|
||||
required bool isNullValid,
|
||||
}) {
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel: "$channelName".',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) {
|
||||
throw PlatformException(
|
||||
code: 'null-error',
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
}
|
||||
return replyList.firstOrNull;
|
||||
}
|
||||
|
||||
List<Object?> wrapResponse({
|
||||
Object? result,
|
||||
PlatformException? error,
|
||||
bool empty = false,
|
||||
}) {
|
||||
if (empty) {
|
||||
return <Object?>[];
|
||||
}
|
||||
if (error == null) {
|
||||
return <Object?>[result];
|
||||
}
|
||||
return <Object?>[error.code, error.message, error.details];
|
||||
}
|
||||
|
||||
bool _deepEquals(Object? a, Object? b) {
|
||||
if (identical(a, b)) {
|
||||
return true;
|
||||
}
|
||||
if (a is double && b is double) {
|
||||
if (a.isNaN && b.isNaN) {
|
||||
return true;
|
||||
}
|
||||
return a == b;
|
||||
}
|
||||
if (a is List && b is List) {
|
||||
return a.length == b.length &&
|
||||
a.indexed.every(
|
||||
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
||||
);
|
||||
}
|
||||
if (a is Map && b is Map) {
|
||||
if (a.length != b.length) {
|
||||
return false;
|
||||
}
|
||||
for (final MapEntry<Object?, Object?> entryA in a.entries) {
|
||||
bool found = false;
|
||||
for (final MapEntry<Object?, Object?> entryB in b.entries) {
|
||||
if (_deepEquals(entryA.key, entryB.key)) {
|
||||
if (_deepEquals(entryA.value, entryB.value)) {
|
||||
found = true;
|
||||
break;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return a == b;
|
||||
}
|
||||
|
||||
int _deepHash(Object? value) {
|
||||
if (value is List) {
|
||||
return Object.hashAll(value.map(_deepHash));
|
||||
}
|
||||
if (value is Map) {
|
||||
int result = 0;
|
||||
for (final MapEntry<Object?, Object?> entry in value.entries) {
|
||||
result += (_deepHash(entry.key) * 31) ^ _deepHash(entry.value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (value is double && value.isNaN) {
|
||||
// Normalize NaN to a consistent hash.
|
||||
return 0x7FF8000000000000.hashCode;
|
||||
}
|
||||
if (value is double && value == 0.0) {
|
||||
// Normalize -0.0 to 0.0 so they have the same hash code.
|
||||
return 0.0.hashCode;
|
||||
}
|
||||
return value.hashCode;
|
||||
}
|
||||
|
||||
enum SingboxProxyProfileType {
|
||||
socks,
|
||||
http,
|
||||
shadowsocks,
|
||||
vmess,
|
||||
vless,
|
||||
trojan,
|
||||
naive,
|
||||
hysteria,
|
||||
hysteria2,
|
||||
tuic,
|
||||
ssh,
|
||||
wireguard,
|
||||
shadowTls,
|
||||
anyTls,
|
||||
customOutbound,
|
||||
}
|
||||
|
||||
enum SingboxProxyRuntimeStatus { stopped, starting, running, stopping, error }
|
||||
|
||||
class SingboxProxyProfile {
|
||||
SingboxProxyProfile({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.type,
|
||||
required this.configJson,
|
||||
this.secretJson,
|
||||
});
|
||||
|
||||
String id;
|
||||
|
||||
String name;
|
||||
|
||||
SingboxProxyProfileType type;
|
||||
|
||||
/// Public profile configuration as JSON. The schema is intentionally owned by
|
||||
/// the profile type so the Pigeon API stays stable while sing-box evolves.
|
||||
String configJson;
|
||||
|
||||
/// Resolved secret values as JSON. Flutter stores secrets independently and
|
||||
/// only passes them to native code when building or starting a runtime config.
|
||||
String? secretJson;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[id, name, type, configJson, secretJson];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static SingboxProxyProfile decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return SingboxProxyProfile(
|
||||
id: result[0]! as String,
|
||||
name: result[1]! as String,
|
||||
type: result[2]! as SingboxProxyProfileType,
|
||||
configJson: result[3]! as String,
|
||||
secretJson: result[4] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
bool operator ==(Object other) {
|
||||
if (other is! SingboxProxyProfile || other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(id, other.id) &&
|
||||
_deepEquals(name, other.name) &&
|
||||
_deepEquals(type, other.type) &&
|
||||
_deepEquals(configJson, other.configJson) &&
|
||||
_deepEquals(secretJson, other.secretJson);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
|
||||
}
|
||||
|
||||
class SingboxProxyRuntimeOptions {
|
||||
SingboxProxyRuntimeOptions({
|
||||
this.preferredBasePort,
|
||||
this.blockUnmatchedTraffic = true,
|
||||
this.dnsConfig,
|
||||
this.bootstrapDohUrl,
|
||||
});
|
||||
|
||||
/// Optional preferred start port for generated local SOCKS inbounds.
|
||||
int? preferredBasePort;
|
||||
|
||||
/// If true, traffic entering sing-box without a matching inbound rule is
|
||||
/// rejected instead of falling through to direct.
|
||||
bool blockUnmatchedTraffic;
|
||||
|
||||
/// Optional DNS block emitted into sing-box config. When null, sing-box
|
||||
/// uses its built-in default (system resolver), which can leak DNS outside
|
||||
/// the proxy — callers should always provide an explicit configuration.
|
||||
SingboxProxyDnsConfig? dnsConfig;
|
||||
|
||||
/// DoH endpoint used by the native LocalDNSTransport bridge to bootstrap
|
||||
/// hostname-only DNS server addresses (and any other hostname appearing in
|
||||
/// the sing-box config). When null, the bridge refuses to resolve and
|
||||
/// sing-box's stock `/etc/resolv.conf`/127.0.0.1:53 path runs — which is
|
||||
/// broken on Android. Callers should always pass the browser DoH URL.
|
||||
String? bootstrapDohUrl;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[
|
||||
preferredBasePort,
|
||||
blockUnmatchedTraffic,
|
||||
dnsConfig,
|
||||
bootstrapDohUrl,
|
||||
];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static SingboxProxyRuntimeOptions decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return SingboxProxyRuntimeOptions(
|
||||
preferredBasePort: result[0] as int?,
|
||||
blockUnmatchedTraffic: result[1]! as bool,
|
||||
dnsConfig: result[2] as SingboxProxyDnsConfig?,
|
||||
bootstrapDohUrl: result[3] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
bool operator ==(Object other) {
|
||||
if (other is! SingboxProxyRuntimeOptions ||
|
||||
other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(preferredBasePort, other.preferredBasePort) &&
|
||||
_deepEquals(blockUnmatchedTraffic, other.blockUnmatchedTraffic) &&
|
||||
_deepEquals(dnsConfig, other.dnsConfig) &&
|
||||
_deepEquals(bootstrapDohUrl, other.bootstrapDohUrl);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
|
||||
}
|
||||
|
||||
class SingboxProxyDnsServerConfig {
|
||||
SingboxProxyDnsServerConfig({
|
||||
required this.tag,
|
||||
required this.address,
|
||||
this.detourTag,
|
||||
this.matchDomainSuffixes = const <String>[],
|
||||
this.matchGeosites = const <String>[],
|
||||
this.matchOutbounds = const <String>[],
|
||||
this.matchInbounds = const <String>[],
|
||||
});
|
||||
|
||||
/// sing-box server tag, used to reference the server from `dns.rules`.
|
||||
String tag;
|
||||
|
||||
/// Server address. `https://...`, `tls://...`, `quic://...`, or plain IP.
|
||||
/// Hostnames are resolved on demand via the platform LocalDNSTransport
|
||||
/// bridge (sing-box `type: "local"` with our DoH-backed implementation).
|
||||
String address;
|
||||
|
||||
/// Outbound tag to dial the resolver through, or `direct` for direct, or
|
||||
/// null when sing-box should pick automatically.
|
||||
String? detourTag;
|
||||
|
||||
/// If non-empty, attaches a `dns.rules` entry routing matching domains to
|
||||
/// this server.
|
||||
List<String> matchDomainSuffixes;
|
||||
|
||||
/// Advanced sing-box geosite selectors (e.g. `geosite:cn`).
|
||||
List<String> matchGeosites;
|
||||
|
||||
/// If non-empty, attaches a `dns.rules` entry routing queries that *the
|
||||
/// listed outbounds* originate to this server. Note: sing-box treats an
|
||||
/// outbound's own bootstrap lookups (e.g. WireGuard peer hostname
|
||||
/// resolution) as queries from that outbound, so using this for per-profile
|
||||
/// scoping creates a chicken-and-egg loop at startup. Prefer
|
||||
/// [matchInbounds] for "queries from tabs routed through this profile".
|
||||
List<String> matchOutbounds;
|
||||
|
||||
/// If non-empty, attaches a `dns.rules` entry matching the listed inbound
|
||||
/// tags. Queries entering via that inbound (e.g. a tab whose container is
|
||||
/// bound to this profile's local SOCKS inbound) resolve through this
|
||||
/// server. Endpoint-bootstrap lookups don't come from any inbound, so this
|
||||
/// scope safely excludes them.
|
||||
List<String> matchInbounds;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[
|
||||
tag,
|
||||
address,
|
||||
detourTag,
|
||||
matchDomainSuffixes,
|
||||
matchGeosites,
|
||||
matchOutbounds,
|
||||
matchInbounds,
|
||||
];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static SingboxProxyDnsServerConfig decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return SingboxProxyDnsServerConfig(
|
||||
tag: result[0]! as String,
|
||||
address: result[1]! as String,
|
||||
detourTag: result[2] as String?,
|
||||
matchDomainSuffixes: (result[3]! as List<Object?>).cast<String>(),
|
||||
matchGeosites: (result[4]! as List<Object?>).cast<String>(),
|
||||
matchOutbounds: (result[5]! as List<Object?>).cast<String>(),
|
||||
matchInbounds: (result[6]! as List<Object?>).cast<String>(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
bool operator ==(Object other) {
|
||||
if (other is! SingboxProxyDnsServerConfig ||
|
||||
other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(tag, other.tag) &&
|
||||
_deepEquals(address, other.address) &&
|
||||
_deepEquals(detourTag, other.detourTag) &&
|
||||
_deepEquals(matchDomainSuffixes, other.matchDomainSuffixes) &&
|
||||
_deepEquals(matchGeosites, other.matchGeosites) &&
|
||||
_deepEquals(matchOutbounds, other.matchOutbounds) &&
|
||||
_deepEquals(matchInbounds, other.matchInbounds);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
|
||||
}
|
||||
|
||||
class SingboxProxyDnsConfig {
|
||||
SingboxProxyDnsConfig({
|
||||
required this.servers,
|
||||
this.finalServerTag,
|
||||
required this.domainStrategy,
|
||||
});
|
||||
|
||||
List<SingboxProxyDnsServerConfig> servers;
|
||||
|
||||
/// Server tag used as `dns.final`. When null, sing-box uses the first
|
||||
/// server in the list as the fallback.
|
||||
String? finalServerTag;
|
||||
|
||||
/// sing-box `dns.strategy` string. e.g. `prefer_ipv4`, `ipv4_only`.
|
||||
String domainStrategy;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[servers, finalServerTag, domainStrategy];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static SingboxProxyDnsConfig decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return SingboxProxyDnsConfig(
|
||||
servers: (result[0]! as List<Object?>)
|
||||
.cast<SingboxProxyDnsServerConfig>(),
|
||||
finalServerTag: result[1] as String?,
|
||||
domainStrategy: result[2]! as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
bool operator ==(Object other) {
|
||||
if (other is! SingboxProxyDnsConfig || other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(servers, other.servers) &&
|
||||
_deepEquals(finalServerTag, other.finalServerTag) &&
|
||||
_deepEquals(domainStrategy, other.domainStrategy);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
|
||||
}
|
||||
|
||||
class SingboxProxyRuntimeEndpoint {
|
||||
SingboxProxyRuntimeEndpoint({
|
||||
required this.profileId,
|
||||
required this.host,
|
||||
required this.port,
|
||||
required this.username,
|
||||
required this.password,
|
||||
});
|
||||
|
||||
String profileId;
|
||||
|
||||
String host;
|
||||
|
||||
int port;
|
||||
|
||||
String username;
|
||||
|
||||
String password;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[profileId, host, port, username, password];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static SingboxProxyRuntimeEndpoint decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return SingboxProxyRuntimeEndpoint(
|
||||
profileId: result[0]! as String,
|
||||
host: result[1]! as String,
|
||||
port: result[2]! as int,
|
||||
username: result[3]! as String,
|
||||
password: result[4]! as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
bool operator ==(Object other) {
|
||||
if (other is! SingboxProxyRuntimeEndpoint ||
|
||||
other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(profileId, other.profileId) &&
|
||||
_deepEquals(host, other.host) &&
|
||||
_deepEquals(port, other.port) &&
|
||||
_deepEquals(username, other.username) &&
|
||||
_deepEquals(password, other.password);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
|
||||
}
|
||||
|
||||
class SingboxProxyRuntimeState {
|
||||
SingboxProxyRuntimeState({
|
||||
required this.status,
|
||||
required this.endpoints,
|
||||
this.message,
|
||||
});
|
||||
|
||||
SingboxProxyRuntimeStatus status;
|
||||
|
||||
List<SingboxProxyRuntimeEndpoint> endpoints;
|
||||
|
||||
String? message;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[status, endpoints, message];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static SingboxProxyRuntimeState decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return SingboxProxyRuntimeState(
|
||||
status: result[0]! as SingboxProxyRuntimeStatus,
|
||||
endpoints: (result[1]! as List<Object?>)
|
||||
.cast<SingboxProxyRuntimeEndpoint>(),
|
||||
message: result[2] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
bool operator ==(Object other) {
|
||||
if (other is! SingboxProxyRuntimeState ||
|
||||
other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(status, other.status) &&
|
||||
_deepEquals(endpoints, other.endpoints) &&
|
||||
_deepEquals(message, other.message);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
|
||||
}
|
||||
|
||||
class SingboxProxyConfigResult {
|
||||
SingboxProxyConfigResult({required this.configJson, required this.endpoints});
|
||||
|
||||
String configJson;
|
||||
|
||||
List<SingboxProxyRuntimeEndpoint> endpoints;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[configJson, endpoints];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static SingboxProxyConfigResult decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return SingboxProxyConfigResult(
|
||||
configJson: result[0]! as String,
|
||||
endpoints: (result[1]! as List<Object?>)
|
||||
.cast<SingboxProxyRuntimeEndpoint>(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
bool operator ==(Object other) {
|
||||
if (other is! SingboxProxyConfigResult ||
|
||||
other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(configJson, other.configJson) &&
|
||||
_deepEquals(endpoints, other.endpoints);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
|
||||
}
|
||||
|
||||
class SingboxProxyLogMessage {
|
||||
SingboxProxyLogMessage({
|
||||
required this.level,
|
||||
required this.message,
|
||||
required this.timestamp,
|
||||
this.profileId,
|
||||
});
|
||||
|
||||
String level;
|
||||
|
||||
String message;
|
||||
|
||||
int timestamp;
|
||||
|
||||
String? profileId;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[level, message, timestamp, profileId];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static SingboxProxyLogMessage decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return SingboxProxyLogMessage(
|
||||
level: result[0]! as String,
|
||||
message: result[1]! as String,
|
||||
timestamp: result[2]! as int,
|
||||
profileId: result[3] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
bool operator ==(Object other) {
|
||||
if (other is! SingboxProxyLogMessage || other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(level, other.level) &&
|
||||
_deepEquals(message, other.message) &&
|
||||
_deepEquals(timestamp, other.timestamp) &&
|
||||
_deepEquals(profileId, other.profileId);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
|
||||
}
|
||||
|
||||
class _PigeonCodec extends StandardMessageCodec {
|
||||
const _PigeonCodec();
|
||||
@override
|
||||
void writeValue(WriteBuffer buffer, Object? value) {
|
||||
if (value is int) {
|
||||
buffer.putUint8(4);
|
||||
buffer.putInt64(value);
|
||||
} else if (value is SingboxProxyProfileType) {
|
||||
buffer.putUint8(129);
|
||||
writeValue(buffer, value.index);
|
||||
} else if (value is SingboxProxyRuntimeStatus) {
|
||||
buffer.putUint8(130);
|
||||
writeValue(buffer, value.index);
|
||||
} else if (value is SingboxProxyProfile) {
|
||||
buffer.putUint8(131);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is SingboxProxyRuntimeOptions) {
|
||||
buffer.putUint8(132);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is SingboxProxyDnsServerConfig) {
|
||||
buffer.putUint8(133);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is SingboxProxyDnsConfig) {
|
||||
buffer.putUint8(134);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is SingboxProxyRuntimeEndpoint) {
|
||||
buffer.putUint8(135);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is SingboxProxyRuntimeState) {
|
||||
buffer.putUint8(136);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is SingboxProxyConfigResult) {
|
||||
buffer.putUint8(137);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is SingboxProxyLogMessage) {
|
||||
buffer.putUint8(138);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 129:
|
||||
final value = readValue(buffer) as int?;
|
||||
return value == null ? null : SingboxProxyProfileType.values[value];
|
||||
case 130:
|
||||
final value = readValue(buffer) as int?;
|
||||
return value == null ? null : SingboxProxyRuntimeStatus.values[value];
|
||||
case 131:
|
||||
return SingboxProxyProfile.decode(readValue(buffer)!);
|
||||
case 132:
|
||||
return SingboxProxyRuntimeOptions.decode(readValue(buffer)!);
|
||||
case 133:
|
||||
return SingboxProxyDnsServerConfig.decode(readValue(buffer)!);
|
||||
case 134:
|
||||
return SingboxProxyDnsConfig.decode(readValue(buffer)!);
|
||||
case 135:
|
||||
return SingboxProxyRuntimeEndpoint.decode(readValue(buffer)!);
|
||||
case 136:
|
||||
return SingboxProxyRuntimeState.decode(readValue(buffer)!);
|
||||
case 137:
|
||||
return SingboxProxyConfigResult.decode(readValue(buffer)!);
|
||||
case 138:
|
||||
return SingboxProxyLogMessage.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SingboxProxyApi {
|
||||
/// Constructor for [SingboxProxyApi]. The [binaryMessenger] named argument is
|
||||
/// available for dependency injection. If it is left null, the default
|
||||
/// BinaryMessenger will be used which routes to the host platform.
|
||||
SingboxProxyApi({
|
||||
BinaryMessenger? binaryMessenger,
|
||||
String messageChannelSuffix = '',
|
||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||
? '.$messageChannelSuffix'
|
||||
: '';
|
||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||
|
||||
final String pigeonVar_messageChannelSuffix;
|
||||
|
||||
Future<String?> validateProfile(SingboxProxyProfile profile) async {
|
||||
final pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.validateProfile$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
||||
<Object?>[profile],
|
||||
);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
|
||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||
pigeonVar_replyList,
|
||||
pigeonVar_channelName,
|
||||
isNullValid: true,
|
||||
);
|
||||
return pigeonVar_replyValue as String?;
|
||||
}
|
||||
|
||||
Future<SingboxProxyConfigResult> buildConfig(
|
||||
List<SingboxProxyProfile> profiles,
|
||||
SingboxProxyRuntimeOptions options,
|
||||
) async {
|
||||
final pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.buildConfig$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
||||
<Object?>[profiles, options],
|
||||
);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
|
||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||
pigeonVar_replyList,
|
||||
pigeonVar_channelName,
|
||||
isNullValid: false,
|
||||
);
|
||||
return pigeonVar_replyValue! as SingboxProxyConfigResult;
|
||||
}
|
||||
|
||||
Future<SingboxProxyRuntimeState> start(
|
||||
List<SingboxProxyProfile> profiles,
|
||||
SingboxProxyRuntimeOptions options,
|
||||
) async {
|
||||
final pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.start$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
||||
<Object?>[profiles, options],
|
||||
);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
|
||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||
pigeonVar_replyList,
|
||||
pigeonVar_channelName,
|
||||
isNullValid: false,
|
||||
);
|
||||
return pigeonVar_replyValue! as SingboxProxyRuntimeState;
|
||||
}
|
||||
|
||||
Future<void> stop(List<String> profileIds) async {
|
||||
final pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.stop$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
||||
<Object?>[profileIds],
|
||||
);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
|
||||
_extractReplyValueOrThrow(
|
||||
pigeonVar_replyList,
|
||||
pigeonVar_channelName,
|
||||
isNullValid: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> stopAll() async {
|
||||
final pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.stopAll$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
|
||||
_extractReplyValueOrThrow(
|
||||
pigeonVar_replyList,
|
||||
pigeonVar_channelName,
|
||||
isNullValid: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<SingboxProxyRuntimeState> getState() async {
|
||||
final pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.getState$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
|
||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||
pigeonVar_replyList,
|
||||
pigeonVar_channelName,
|
||||
isNullValid: false,
|
||||
);
|
||||
return pigeonVar_replyValue! as SingboxProxyRuntimeState;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SingboxProxyEventsApi {
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||
|
||||
void onStateChanged(SingboxProxyRuntimeState state);
|
||||
|
||||
void onLogMessage(SingboxProxyLogMessage message);
|
||||
|
||||
static void setUp(
|
||||
SingboxProxyEventsApi? api, {
|
||||
BinaryMessenger? binaryMessenger,
|
||||
String messageChannelSuffix = '',
|
||||
}) {
|
||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||
? '.$messageChannelSuffix'
|
||||
: '';
|
||||
{
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyEventsApi.onStateChanged$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger,
|
||||
);
|
||||
if (api == null) {
|
||||
pigeonVar_channel.setMessageHandler(null);
|
||||
} else {
|
||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||
final List<Object?> args = message! as List<Object?>;
|
||||
final SingboxProxyRuntimeState arg_state =
|
||||
args[0]! as SingboxProxyRuntimeState;
|
||||
try {
|
||||
api.onStateChanged(arg_state);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(
|
||||
error: PlatformException(code: 'error', message: e.toString()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
{
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyEventsApi.onLogMessage$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger,
|
||||
);
|
||||
if (api == null) {
|
||||
pigeonVar_channel.setMessageHandler(null);
|
||||
} else {
|
||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||
final List<Object?> args = message! as List<Object?>;
|
||||
final SingboxProxyLogMessage arg_message =
|
||||
args[0]! as SingboxProxyLogMessage;
|
||||
try {
|
||||
api.onLogMessage(arg_message);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(
|
||||
error: PlatformException(code: 'error', message: e.toString()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user