// Autogenerated from Pigeon (v27.1.0), 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? 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 wrapResponse({ Object? result, PlatformException? error, bool empty = false, }) { if (empty) { return []; } if (error == null) { return [result]; } return [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 entryA in a.entries) { bool found = false; for (final MapEntry 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 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 _toList() { return [id, name, type, configJson, secretJson]; } Object encode() { return _toList(); } static SingboxProxyProfile decode(Object result) { result as List; 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([runtimeType, ..._toList()]); @override String toString() { return 'SingboxProxyProfile(id: $id, name: $name, type: $type, configJson: $configJson, secretJson: $secretJson)'; } } 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 _toList() { return [ preferredBasePort, blockUnmatchedTraffic, dnsConfig, bootstrapDohUrl, ]; } Object encode() { return _toList(); } static SingboxProxyRuntimeOptions decode(Object result) { result as List; 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([runtimeType, ..._toList()]); @override String toString() { return 'SingboxProxyRuntimeOptions(preferredBasePort: $preferredBasePort, blockUnmatchedTraffic: $blockUnmatchedTraffic, dnsConfig: $dnsConfig, bootstrapDohUrl: $bootstrapDohUrl)'; } } class SingboxProxyDnsServerConfig { SingboxProxyDnsServerConfig({ required this.tag, required this.address, this.detourTag, this.matchDomainSuffixes = const [], this.matchGeosites = const [], this.matchOutbounds = const [], this.matchInbounds = const [], }); /// 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 matchDomainSuffixes; /// Advanced sing-box geosite selectors (e.g. `geosite:cn`). List 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 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 matchInbounds; List _toList() { return [ tag, address, detourTag, matchDomainSuffixes, matchGeosites, matchOutbounds, matchInbounds, ]; } Object encode() { return _toList(); } static SingboxProxyDnsServerConfig decode(Object result) { result as List; return SingboxProxyDnsServerConfig( tag: result[0]! as String, address: result[1]! as String, detourTag: result[2] as String?, matchDomainSuffixes: (result[3]! as List).cast(), matchGeosites: (result[4]! as List).cast(), matchOutbounds: (result[5]! as List).cast(), matchInbounds: (result[6]! as List).cast(), ); } @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([runtimeType, ..._toList()]); @override String toString() { return 'SingboxProxyDnsServerConfig(tag: $tag, address: $address, detourTag: $detourTag, matchDomainSuffixes: $matchDomainSuffixes, matchGeosites: $matchGeosites, matchOutbounds: $matchOutbounds, matchInbounds: $matchInbounds)'; } } class SingboxProxyDnsConfig { SingboxProxyDnsConfig({ required this.servers, this.finalServerTag, required this.domainStrategy, }); List 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 _toList() { return [servers, finalServerTag, domainStrategy]; } Object encode() { return _toList(); } static SingboxProxyDnsConfig decode(Object result) { result as List; return SingboxProxyDnsConfig( servers: (result[0]! as List) .cast(), 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([runtimeType, ..._toList()]); @override String toString() { return 'SingboxProxyDnsConfig(servers: $servers, finalServerTag: $finalServerTag, domainStrategy: $domainStrategy)'; } } 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 _toList() { return [profileId, host, port, username, password]; } Object encode() { return _toList(); } static SingboxProxyRuntimeEndpoint decode(Object result) { result as List; 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([runtimeType, ..._toList()]); @override String toString() { return 'SingboxProxyRuntimeEndpoint(profileId: $profileId, host: $host, port: $port, username: $username, password: $password)'; } } class SingboxProxyRuntimeState { SingboxProxyRuntimeState({ required this.status, required this.endpoints, this.message, }); SingboxProxyRuntimeStatus status; List endpoints; String? message; List _toList() { return [status, endpoints, message]; } Object encode() { return _toList(); } static SingboxProxyRuntimeState decode(Object result) { result as List; return SingboxProxyRuntimeState( status: result[0]! as SingboxProxyRuntimeStatus, endpoints: (result[1]! as List) .cast(), 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([runtimeType, ..._toList()]); @override String toString() { return 'SingboxProxyRuntimeState(status: $status, endpoints: $endpoints, message: $message)'; } } class SingboxProxyConfigResult { SingboxProxyConfigResult({required this.configJson, required this.endpoints}); String configJson; List endpoints; List _toList() { return [configJson, endpoints]; } Object encode() { return _toList(); } static SingboxProxyConfigResult decode(Object result) { result as List; return SingboxProxyConfigResult( configJson: result[0]! as String, endpoints: (result[1]! as List) .cast(), ); } @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([runtimeType, ..._toList()]); @override String toString() { return 'SingboxProxyConfigResult(configJson: $configJson, endpoints: $endpoints)'; } } class SingboxProxyLogMessage { SingboxProxyLogMessage({ required this.level, required this.message, required this.timestamp, this.profileId, }); String level; String message; int timestamp; String? profileId; List _toList() { return [level, message, timestamp, profileId]; } Object encode() { return _toList(); } static SingboxProxyLogMessage decode(Object result) { result as List; 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([runtimeType, ..._toList()]); @override String toString() { return 'SingboxProxyLogMessage(level: $level, message: $message, timestamp: $timestamp, profileId: $profileId)'; } } 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 pigeonChannelCodec = _PigeonCodec(); final String pigeonVar_messageChannelSuffix; Future validateProfile(SingboxProxyProfile profile) async { final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.validateProfile$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [profile], ); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( pigeonVar_replyList, pigeonVar_channelName, isNullValid: true, ); return pigeonVar_replyValue as String?; } Future buildConfig( List profiles, SingboxProxyRuntimeOptions options, ) async { final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.buildConfig$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [profiles, options], ); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( pigeonVar_replyList, pigeonVar_channelName, isNullValid: false, ); return pigeonVar_replyValue! as SingboxProxyConfigResult; } Future start( List profiles, SingboxProxyRuntimeOptions options, ) async { final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.start$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [profiles, options], ); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( pigeonVar_replyList, pigeonVar_channelName, isNullValid: false, ); return pigeonVar_replyValue! as SingboxProxyRuntimeState; } Future stop(List profileIds) async { final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.stop$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [profileIds], ); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; _extractReplyValueOrThrow( pigeonVar_replyList, pigeonVar_channelName, isNullValid: true, ); } Future stopAll() async { final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.stopAll$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; _extractReplyValueOrThrow( pigeonVar_replyList, pigeonVar_channelName, isNullValid: true, ); } Future getState() async { final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_singbox_proxy.SingboxProxyApi.getState$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( pigeonVar_replyList, pigeonVar_channelName, isNullValid: false, ); return pigeonVar_replyValue! as SingboxProxyRuntimeState; } } abstract class SingboxProxyEventsApi { static const MessageCodec 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( '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 args = message! as List; 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( '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 args = message! as List; 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()), ); } }); } } } }