From 24f5931e25b86c93b1825cfbecfbbd8b2ccc88ca Mon Sep 17 00:00:00 2001 From: AmisKwok Date: Mon, 21 Sep 2026 04:30:09 +0800 Subject: [PATCH 1/2] fix: correct upload timing, web listener leak, DNS DoH contract and add IPv6 DNS - Stop the upload timer at request-send so the server response no longer inflates upload throughput (speed_test_service). - Detach browser online/offline listeners on the last connectivity subscription cancel to avoid leaking global listeners (connectivity_service_web). - Return the documented 'unsupported' result for DoH-less web DNS servers instead of probing an arbitrary https URL (dns_service_web). - Stop the web ping timer at response headers so large bodies do not inflate latency (ping_service_web). - Resolve IPv6 (AAAA) from the raw-UDP resolver and bind a matching address family (dns_service_io, dns_packet). - Guard example setState after await and disable the Run button while busy. - Add a speed-test smoke test to prevent regressions. --- CHANGELOG.md | 29 +++++ README.md | 4 +- README_zh.md | 4 +- example/lib/main.dart | 105 +++++++++++------- example/pubspec.lock | 2 +- ios/zero_network_kit.podspec | 2 +- lib/src/dns/dns_packet.dart | 15 ++- .../services/connectivity_service_web.dart | 31 +++++- lib/src/services/dns_service_io.dart | 90 +++++++++------ lib/src/services/dns_service_web.dart | 6 +- lib/src/services/ping_service_web.dart | 11 +- lib/src/services/speed_test_service.dart | 5 +- macos/zero_network_kit.podspec | 2 +- pubspec.yaml | 2 +- test/speed_test_service_test.dart | 51 +++++++++ 15 files changed, 258 insertions(+), 101 deletions(-) create mode 100644 test/speed_test_service_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index e42f031..262370b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## 1.0.5 + +### Fixed / 修复 + +- **Upload speed measurement** — the upload duration now stops as soon as the + request body is fully sent, so the server's response download no longer + inflates the measured upload throughput (and the reported `SpeedTestProgress` + value stays consistent with the final result). + - **上传速率测量**——上传耗时改为在请求体完全发出时即停止,服务器回包下载时间不再计入,上传速率与进度回调数值保持一致。 +- **Web connectivity listener leak** — `WebConnectivityAdapter.onConnectivityChanged` + now attaches the `online` / `offline` browser listeners on the first + subscription and detaches them on the last cancellation, so repeated + subscriptions no longer accumulate global event listeners. + - **Web 连通性监听泄漏**——`WebConnectivityAdapter.onConnectivityChanged` 改为首次订阅时挂载、末次取消时移除浏览器的 `online` / `offline` 监听,反复订阅不再累积全局监听。 +- **Web DNS DoH contract** — an explicit DNS server with no known DoH endpoint now + returns the documented "unsupported" result instead of probing an arbitrary + `https:///dns-query` URL. + - **Web 端 DNS DoH 契约**——对没有已知 DoH 端点的显式 DNS 服务器,现在按文档返回"不支持"结果,而非去请求一个未必提供 DoH 的 `https:///dns-query` 地址。 +- **Web ping RTT** — the TCP-probe ping on the web stops the timer at the response + headers, so a large response body no longer inflates the reported latency. + - **Web 端 Ping RTT**——Web 端的 TCP 探测 ping 在收到响应头即停表,响应体大小不再抬高测得的延迟。 + +### Added / 新增 + +- **IPv6 DNS resolution** — the raw-UDP resolver now sends both A and AAAA + queries and binds a wildcard socket that matches the target server's address + family, so IPv6 answers are resolved too (previously only IPv4). + - **IPv6 DNS 解析**——原始 UDP 解析器现在同时发送 A 与 AAAA 查询,并按目标服务器地址族绑定通配套接字,从而也能解析出 IPv6 地址(此前仅限 IPv4)。 + ## 1.0.4 ### Fixed / 修复 diff --git a/README.md b/README.md index e58847f..cffdd47 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ micro-benchmarks — for Android, iOS, macOS, Windows, Linux and Web (partial). [![Dart](https://img.shields.io/badge/Dart-✓-0175C2?logo=dart)](https://dart.dev) [![Style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) -> **🔔 Upgrade recommended:** `1.0.4` removes the remaining `dart:io` imports from the Web build — the platform variants are now selected by gating the `dart:io` implementations behind `dart.library.io`, so the Web build resolves to the browser-based implementations and compiles with `flutter build web --wasm`. Upgrade to `^1.0.4`. +> **🔔 Upgrade recommended:** `1.0.5` fixes the upload-speed measurement (no longer inflated by the server response), stops a Web connectivity listener leak, aligns the Web DNS DoH contract with its docs, and adds IPv6 (AAAA) to the raw-UDP resolver. Upgrade to `^1.0.5`. 🌐 **[Official Website](https://www.zerolabsco.com/)**  ·  📦 **[View on pub.dev](https://pub.dev/packages/zero_network_kit)**  ·  🔗 **[View on GitHub](https://github.com/zero-labsco/zero_network_kit)** @@ -74,7 +74,7 @@ Design goals: ```yaml dependencies: - zero_network_kit: ^1.0.4 + zero_network_kit: ^1.0.5 ``` ### Android permissions diff --git a/README_zh.md b/README_zh.md index 7ccdca6..7a56354 100644 --- a/README_zh.md +++ b/README_zh.md @@ -18,7 +18,7 @@ [![Dart](https://img.shields.io/badge/Dart-✓-0175C2?logo=dart)](https://dart.dev) [![Style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart) -> **🔔 推荐升级:** `1.0.4` 移除了 Web 构建中残留的 `dart:io` 引用——各平台实现改为按 `dart.library.io` 条件引入原生版本,Web 侧因此只解析到基于浏览器的实现,可用 `flutter build web --wasm` 编译。建议升级到 `^1.0.4`。 +> **🔔 推荐升级:** `1.0.5` 修正了上传速率测量(不再被服务器回包时间抬高)、修复了 Web 连通性监听泄漏、使 Web 端 DNS 的 DoH 契约与文档一致,并为原始 UDP 解析器加入 IPv6(AAAA)支持。建议升级到 `^1.0.5`。 🌐 **[官方网站](https://www.zerolabsco.com/)**  ·  📦 **[在 pub.dev 查看](https://pub.dev/packages/zero_network_kit)**  ·  🔗 **[查看 GitHub 仓库](https://github.com/zero-labsco/zero_network_kit)** @@ -72,7 +72,7 @@ ```yaml dependencies: - zero_network_kit: ^1.0.4 + zero_network_kit: ^1.0.5 ``` ### Android 权限 diff --git a/example/lib/main.dart b/example/lib/main.dart index 7a7564a..e67c303 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -105,10 +105,13 @@ class _DiagnosticHomePageState extends State { title: 'Connection', description: 'Transport type, IP, gateway and VPN (SSID / signal on mobile)', - onPressed: () => _run('connection', () async { - final info = await NetworkDiagnostic.checkConnection(); - setState(() => _connection = info); - }), + onPressed: _busyLabel != null + ? null + : () => _run('connection', () async { + final info = await NetworkDiagnostic.checkConnection(); + if (!mounted) return; + setState(() => _connection = info); + }), body: _connection == null ? null : _KeyValueList( @@ -134,10 +137,13 @@ class _DiagnosticHomePageState extends State { _ActionCard( title: 'Ping', description: 'TCP handshake round trip towards 1.1.1.1', - onPressed: () => _run('ping', () async { - final result = await NetworkDiagnostic.ping(count: 5); - setState(() => _ping = result); - }), + onPressed: _busyLabel != null + ? null + : () => _run('ping', () async { + final result = await NetworkDiagnostic.ping(count: 5); + if (!mounted) return; + setState(() => _ping = result); + }), body: _ping == null ? null : _KeyValueList( @@ -156,12 +162,15 @@ class _DiagnosticHomePageState extends State { _ActionCard( title: 'DNS', description: 'Raw UDP queries against public resolvers', - onPressed: () => _run('dns', () async { - final results = await NetworkDiagnostic.resolve( - includeSystemResolver: true, - ); - setState(() => _dns = results); - }), + onPressed: _busyLabel != null + ? null + : () => _run('dns', () async { + final results = await NetworkDiagnostic.resolve( + includeSystemResolver: true, + ); + if (!mounted) return; + setState(() => _dns = results); + }), body: _dns.isEmpty ? null : _KeyValueList( @@ -176,10 +185,13 @@ class _DiagnosticHomePageState extends State { _ActionCard( title: 'Speed test', description: 'Download/upload throughput and latency', - onPressed: () => _run('speed', () async { - final result = await NetworkDiagnostic.runSpeedTest(); - setState(() => _speed = result); - }), + onPressed: _busyLabel != null + ? null + : () => _run('speed', () async { + final result = await NetworkDiagnostic.runSpeedTest(); + if (!mounted) return; + setState(() => _speed = result); + }), body: _speed == null ? null : _KeyValueList( @@ -199,10 +211,13 @@ class _DiagnosticHomePageState extends State { _ActionCard( title: 'Quality score', description: 'Weighted score across latency, DNS and bandwidth', - onPressed: () => _run('quality', () async { - final score = await NetworkDiagnostic.evaluateQuality(); - setState(() => _quality = score); - }), + onPressed: _busyLabel != null + ? null + : () => _run('quality', () async { + final score = await NetworkDiagnostic.evaluateQuality(); + if (!mounted) return; + setState(() => _quality = score); + }), body: _quality == null ? null : _KeyValueList( @@ -218,29 +233,35 @@ class _DiagnosticHomePageState extends State { _ActionCard( title: 'Full diagnostic', description: 'One shot report covering every probe above', - onPressed: () => _run('report', () async { - final report = await NetworkDiagnostic.diagnose( - includeSpeedTest: false, - includePorts: true, - ); - setState(() { - _connection = report.connection; - _ping = report.ping; - _dns = report.dnsResults; - _quality = report.quality; - }); - }), + onPressed: _busyLabel != null + ? null + : () => _run('report', () async { + final report = await NetworkDiagnostic.diagnose( + includeSpeedTest: false, + includePorts: true, + ); + if (!mounted) return; + setState(() { + _connection = report.connection; + _ping = report.ping; + _dns = report.dnsResults; + _quality = report.quality; + }); + }), ), _ActionCard( title: 'Benchmark the API', description: 'Measure how fast the diagnostic API itself runs', - onPressed: () => _run('benchmark', () async { - final suite = await NetworkBenchmark.runAll( - iterations: 5, - warmupIterations: 1, - ); - setState(() => _benchmark = suite); - }), + onPressed: _busyLabel != null + ? null + : () => _run('benchmark', () async { + final suite = await NetworkBenchmark.runAll( + iterations: 5, + warmupIterations: 1, + ); + if (!mounted) return; + setState(() => _benchmark = suite); + }), body: _benchmark == null ? null : _KeyValueList( @@ -268,7 +289,7 @@ class _ActionCard extends StatelessWidget { final String title; final String description; - final Future Function() onPressed; + final Future Function()? onPressed; final Widget? body; @override diff --git a/example/pubspec.lock b/example/pubspec.lock index d60b3c8..8c6c352 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -378,7 +378,7 @@ packages: path: ".." relative: true source: path - version: "1.0.4" + version: "1.0.5" sdks: dart: ">=3.11.5 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/ios/zero_network_kit.podspec b/ios/zero_network_kit.podspec index 23864dc..62bf806 100644 --- a/ios/zero_network_kit.podspec +++ b/ios/zero_network_kit.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'zero_network_kit' - s.version = '1.0.4' + s.version = '1.0.5' s.summary = 'Flutter network diagnostics: ping, DNS, speed test, port scan and quality score.' s.description = <<-DESC A Flutter plugin for network diagnostics: connectivity check, ping, DNS diff --git a/lib/src/dns/dns_packet.dart b/lib/src/dns/dns_packet.dart index d3dfff4..d55acc0 100644 --- a/lib/src/dns/dns_packet.dart +++ b/lib/src/dns/dns_packet.dart @@ -89,8 +89,17 @@ class DnsResponse { class DnsPacket { const DnsPacket._(); - /// 构造一个 A 记录查询报文 / Builds a standard A-record query message. - static Uint8List encodeQuery(String domain, {required int id}) { + /// 构造一个 DNS 查询报文 / Builds a standard DNS query message. + /// + /// [type] 为查询的资源记录类型,默认 [DnsRecordType.a](IPv4);传入 + /// [DnsRecordType.aaa] 即可发出 AAAA(IPv6)查询 / + /// [type] is the requested record type; it defaults to [DnsRecordType.a] + /// (IPv4) and accepts [DnsRecordType.aaaa] for an IPv6 query. + static Uint8List encodeQuery( + String domain, { + required int id, + int type = DnsRecordType.a, + }) { final labels = domain .split('.') .where((label) => label.isNotEmpty) @@ -122,7 +131,7 @@ class DnsPacket { builder.addByte(0); // Root label. final question = ByteData(4); - question.setUint16(0, DnsRecordType.a); + question.setUint16(0, type); question.setUint16(2, 1); // IN class. builder.add(question.buffer.asUint8List()); diff --git a/lib/src/services/connectivity_service_web.dart b/lib/src/services/connectivity_service_web.dart index 9b2c480..d9bb0df 100644 --- a/lib/src/services/connectivity_service_web.dart +++ b/lib/src/services/connectivity_service_web.dart @@ -45,12 +45,31 @@ class WebConnectivityAdapter implements ConnectivityAdapter { @override Stream> get onConnectivityChanged { final controller = StreamController>.broadcast(); - EventStreamProvider( - 'online', - ).forTarget(window).listen((_) => controller.add(const ['wifi'])); - EventStreamProvider( - 'offline', - ).forTarget(window).listen((_) => controller.add(const ['none'])); + StreamSubscription? onlineSub; + StreamSubscription? offlineSub; + + void attach() { + onlineSub ??= EventStreamProvider( + 'online', + ).forTarget(window).listen((_) => controller.add(const ['wifi'])); + offlineSub ??= EventStreamProvider( + 'offline', + ).forTarget(window).listen((_) => controller.add(const ['none'])); + } + + void detach() { + onlineSub?.cancel(); + offlineSub?.cancel(); + onlineSub = null; + offlineSub = null; + } + + // 首次订阅时挂载浏览器事件监听,最后一次取消时移除,避免反复订阅导致全局 + // 监听累积(泄漏)/ Attach the browser listeners on the first subscription and + // detach them on the last cancellation so repeated subscriptions don't leak + // global event listeners. + controller.onListen = attach; + controller.onCancel = detach; return controller.stream; } } diff --git a/lib/src/services/dns_service_io.dart b/lib/src/services/dns_service_io.dart index e87d418..1250dfb 100644 --- a/lib/src/services/dns_service_io.dart +++ b/lib/src/services/dns_service_io.dart @@ -124,11 +124,36 @@ class DnsService { try { final target = await _resolveServer(server).timeout(timeout); - socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0); + // 依据目标地址族绑定对应通配套接字,IPv6 服务器也能正常收发 / + // Bind a wildcard socket that matches the target's address family so an + // IPv6 server is reachable too. + final bindAddress = target.type == InternetAddressType.IPv6 + ? InternetAddress.anyIPv6 + : InternetAddress.anyIPv4; + socket = await RawDatagramSocket.bind(bindAddress, 0); final udp = socket; - final id = DnsPacket.randomId(); - final payload = DnsPacket.encodeQuery(domain, id: id); - final completer = Completer(); + + final aId = DnsPacket.randomId(); + final aaaaId = (aId ^ 0x8000) & 0xFFFF; + final aPayload = DnsPacket.encodeQuery( + domain, + id: aId, + type: DnsRecordType.a, + ); + final aaaaPayload = DnsPacket.encodeQuery( + domain, + id: aaaaId, + type: DnsRecordType.aaaa, + ); + + final addresses = []; + final completer = Completer(); + var aDone = false; + var aaaaDone = false; + + void maybeComplete() { + if (aDone && aaaaDone && !completer.isCompleted) completer.complete(); + } subscription = udp.listen( (event) { @@ -137,10 +162,21 @@ class DnsService { if (datagram == null) return; try { final response = DnsPacket.parse(datagram.data); - if (response.id != id) return; - if (!completer.isCompleted) completer.complete(response); - } catch (error) { - if (!completer.isCompleted) completer.completeError(error); + if (response.id == aId) { + if (response.responseCode == 0) { + addresses.addAll(response.addresses); + } + aDone = true; + maybeComplete(); + } else if (response.id == aaaaId) { + if (response.responseCode == 0) { + addresses.addAll(response.addresses); + } + aaaaDone = true; + maybeComplete(); + } + } catch (_) { + // 忽略无法解析的报文 / Ignore undecodable datagrams. } }, onError: (Object error, StackTrace stackTrace) { @@ -151,45 +187,25 @@ class DnsService { ); timer = Timer(timeout, () { - if (!completer.isCompleted) { - completer.completeError( - TimeoutException('DNS query to $server timed out', timeout), - ); - } + if (!completer.isCompleted) completer.complete(); }); - udp.send(payload, target, 53); + udp.send(aPayload, target, 53); + udp.send(aaaaPayload, target, 53); - final response = await completer.future.timeout( - timeout, - onTimeout: () => - throw TimeoutException('DNS query to $server timed out', timeout), - ); + await completer.future; stopwatch.stop(); - if (response.responseCode != 0) { - return DnsTestResult( - server: server, - domain: domain, - isSuccess: false, - responseTime: stopwatch.elapsed, - errorMessage: 'DNS server returned RCODE ${response.responseCode}', - timestamp: DateTime.now(), - ); - } - + final uniqueAddresses = addresses.toSet().toList(growable: false); + final success = uniqueAddresses.isNotEmpty; return DnsTestResult( server: server, domain: domain, - isSuccess: response.isSuccess, + isSuccess: success, responseTime: stopwatch.elapsed, - resolvedIps: response.addresses, + resolvedIps: uniqueAddresses, timestamp: DateTime.now(), - errorMessage: response.isSuccess - ? null - : (response.truncated - ? 'Response truncated; retry over TCP' - : 'No A/AAAA record in the answer section'), + errorMessage: success ? null : 'No A/AAAA record in the answer section', ); } catch (error) { stopwatch.stop(); diff --git a/lib/src/services/dns_service_web.dart b/lib/src/services/dns_service_web.dart index 08ab8f5..681624d 100644 --- a/lib/src/services/dns_service_web.dart +++ b/lib/src/services/dns_service_web.dart @@ -113,7 +113,11 @@ class DnsService { case '8.8.4.4': return 'https://dns.google/dns-query'; default: - return 'https://$server/dns-query'; + // 文档约定:无已知 DoH 端点的服务器返回「不支持」,而非去尝试一个未必提供 + // DoH 的 HTTPS 地址 / Per the documented contract, servers without a known + // DoH endpoint are reported as unsupported instead of probing an arbitrary + // HTTPS URL that likely has no DoH. + return null; } } diff --git a/lib/src/services/ping_service_web.dart b/lib/src/services/ping_service_web.dart index 9afa6c1..b1de66e 100644 --- a/lib/src/services/ping_service_web.dart +++ b/lib/src/services/ping_service_web.dart @@ -97,10 +97,15 @@ class PingService { final client = http.Client(); final stopwatch = Stopwatch()..start(); try { - final response = await client - .get(uri, headers: {'Cache-Control': 'no-cache'}) - .timeout(timeout); + final request = http.Request('GET', uri) + ..headers['User-Agent'] = 'zero_network_kit/1.0'; + final response = await client.send(request).timeout(timeout); + // 往返耗时只计算到收到响应头,与真实 TCP / ICMP 的 RTT 一致;响应体随后丢弃, + // 不参与计时 / The round trip ends when the response headers arrive, matching + // a real TCP / ICMP RTT; the body is drained afterwards so it never inflates + // the measured latency. stopwatch.stop(); + unawaited(response.stream.drain().timeout(timeout)); if (response.statusCode >= 400) return null; return stopwatch.elapsedMicroseconds / 1000; } catch (_) { diff --git a/lib/src/services/speed_test_service.dart b/lib/src/services/speed_test_service.dart index 1d6f060..5edd26b 100644 --- a/lib/src/services/speed_test_service.dart +++ b/lib/src/services/speed_test_service.dart @@ -258,6 +258,10 @@ class SpeedTestService { final stopwatch = Stopwatch()..start(); final response = await client.send(request).timeout(timeout); + // 上传耗时只计到「请求体完全发出」那一刻,不应包含服务器回包下载时间 / + // The upload duration stops as soon as the request body is fully sent, so it + // never includes the server's response download time. + stopwatch.stop(); onProgress?.call( SpeedTestProgress( phase: SpeedTestPhase.upload, @@ -270,7 +274,6 @@ class SpeedTestService { ), ); await response.stream.drain().timeout(timeout); - stopwatch.stop(); if (response.statusCode >= 400) { throw SpeedTestException( diff --git a/macos/zero_network_kit.podspec b/macos/zero_network_kit.podspec index c49dd74..083973f 100644 --- a/macos/zero_network_kit.podspec +++ b/macos/zero_network_kit.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'zero_network_kit' - s.version = '1.0.4' + s.version = '1.0.5' s.summary = 'Flutter network diagnostics: ping, DNS, speed test, port scan and quality score.' s.description = <<-DESC A Flutter plugin for network diagnostics: connectivity check, ping, DNS diff --git a/pubspec.yaml b/pubspec.yaml index 70f2e19..334e242 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: zero_network_kit description: "Network diagnostics toolkit for Flutter: connectivity, ping, DNS, speed test, port scan and quality scoring across Android, iOS, macOS, Windows, Linux and Web." -version: 1.0.4 +version: 1.0.5 topics: - network - diagnostics diff --git a/test/speed_test_service_test.dart b/test/speed_test_service_test.dart new file mode 100644 index 0000000..0ec4611 --- /dev/null +++ b/test/speed_test_service_test.dart @@ -0,0 +1,51 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:zero_network_kit/advanced.dart'; + +void main() { + group('SpeedTestService', () { + test('measures positive download and upload throughput', () async { + final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); + addTearDown(server.close); + + server.listen((request) async { + if (request.method == 'POST') { + // 丢弃上传体 / Discard the upload body. + await request.drain(); + request.response.statusCode = 200; + request.response.headers.contentType = ContentType.text; + request.response.write('ok'); + await request.response.close(); + return; + } + // GET:返回一段固定大小的可下载数据 / A fixed-size download payload. + const body = 'zero_network_kit_speed_test\n'; + request.response.statusCode = 200; + request.response.headers.contentType = ContentType.text; + request.response.add(utf8.encode(body * 1000)); + await request.response.close(); + }); + + final base = + 'http://${InternetAddress.loopbackIPv4.address}:${server.port}'; + final result = await const SpeedTestService().runSpeedTest( + downloadUrl: '$base/', + uploadUrl: '$base/', + uploadPayloadBytes: 65536, + timeout: const Duration(seconds: 10), + maxDuration: const Duration(milliseconds: 500), + includePing: false, + ); + + expect(result.downloadedBytes, greaterThan(0)); + expect(result.downloadSpeed, greaterThan(0)); + expect(result.uploadedBytes, 65536); + expect(result.uploadSpeed, greaterThan(0)); + // 上传耗时应当只反映「请求体发出」,而非包含服务器回包下载 / + // The upload duration should reflect the request send only, not the drain. + expect(result.uploadDuration, greaterThan(Duration.zero)); + }); + }); +} From e365e51726dcd46ae682fe16dd74b56fbbf14c39 Mon Sep 17 00:00:00 2001 From: AmisKwok Date: Mon, 21 Sep 2026 04:45:26 +0800 Subject: [PATCH 2/2] docs: rebuild documentation site for v1.0.5 --- docs/404.html | 2 +- docs/404/index.html | 2 +- docs/API-Reference/index.html | 4 ++-- docs/Benchmark/index.html | 4 ++-- docs/Connectivity/index.html | 4 ++-- docs/DNS/index.html | 4 ++-- docs/FAQ/index.html | 4 ++-- docs/Full-Report/index.html | 4 ++-- docs/Getting-Started/index.html | 4 ++-- docs/Installation/index.html | 9 +++++---- docs/Ping/index.html | 4 ++-- docs/Platform-Support/index.html | 4 ++-- docs/Ports/index.html | 4 ++-- docs/Quality/index.html | 4 ++-- docs/Speed-Test/index.html | 4 ++-- docs/Usage/index.html | 4 ++-- .../_buildManifest.js | 2 +- .../_ssgManifest.js | 0 docs/_next/static/chunks/nextra-data-en-US.json | 2 +- .../static/chunks/pages/Installation-4297b711c2aa203c.js | 1 - .../static/chunks/pages/Installation-c75a9ee9feffc261.js | 1 + docs/index.html | 4 ++-- 22 files changed, 38 insertions(+), 37 deletions(-) rename docs/_next/static/{nuDvG3BQ_xqr8cZZcfr-R => _EQnS2NtxmlSi3SDEn1lD}/_buildManifest.js (91%) rename docs/_next/static/{nuDvG3BQ_xqr8cZZcfr-R => _EQnS2NtxmlSi3SDEn1lD}/_ssgManifest.js (100%) delete mode 100644 docs/_next/static/chunks/pages/Installation-4297b711c2aa203c.js create mode 100644 docs/_next/static/chunks/pages/Installation-c75a9ee9feffc261.js diff --git a/docs/404.html b/docs/404.html index bcadad3..48c3f09 100644 --- a/docs/404.html +++ b/docs/404.html @@ -1 +1 @@ -404: This page could not be found

404

This page could not be found.

\ No newline at end of file +404: This page could not be found

404

This page could not be found.

\ No newline at end of file diff --git a/docs/404/index.html b/docs/404/index.html index bcadad3..48c3f09 100644 --- a/docs/404/index.html +++ b/docs/404/index.html @@ -1 +1 @@ -404: This page could not be found

404

This page could not be found.

\ No newline at end of file +404: This page could not be found

404

This page could not be found.

\ No newline at end of file diff --git a/docs/API-Reference/index.html b/docs/API-Reference/index.html index bc866b9..2bc861e 100644 --- a/docs/API-Reference/index.html +++ b/docs/API-Reference/index.html @@ -1,4 +1,4 @@ -
📚 API Reference

API Reference / 接口参考

+
📚 API Reference

API Reference / 接口参考

A condensed field reference for every public model. For copy-paste examples of each method, see the capability pages.

这里是每个公开模型的字段速查。每个方法的复制即用示例请见各能力页面。

@@ -95,4 +95,4 @@

API.md 与 API_zh.md。

-


Zero Network Kit · MPL-2.0
\ No newline at end of file +

Zero Network Kit · MPL-2.0
\ No newline at end of file diff --git a/docs/Benchmark/index.html b/docs/Benchmark/index.html index 3cd5f49..7a022ab 100644 --- a/docs/Benchmark/index.html +++ b/docs/Benchmark/index.html @@ -1,4 +1,4 @@ -
📊 Benchmarks

Benchmarks / 微基准测试

+
📊 Benchmarks

Benchmarks / 微基准测试

NetworkBenchmark measures how fast the diagnostics API itself runs — useful when deciding whether a diagnostic belongs on your startup path.

NetworkBenchmark 衡量诊断 API 自身的开销——可用在判断某项诊断能否放在启动路径上。

@@ -33,4 +33,4 @@

minDuration, maxDuration, standardDeviation, operationsPerSecond, failures, timestamp.

BenchmarkSuiteResult: suiteName, results, totalDuration, timestamp, and -suite['name'].


Zero Network Kit · MPL-2.0
\ No newline at end of file +suite['name'].


Zero Network Kit · MPL-2.0
\ No newline at end of file diff --git a/docs/Connectivity/index.html b/docs/Connectivity/index.html index 4da6070..95429af 100644 --- a/docs/Connectivity/index.html +++ b/docs/Connectivity/index.html @@ -1,4 +1,4 @@ -
📡 Connectivity

Connectivity / 连通性

+
📡 Connectivity

Connectivity / 连通性

NetworkDiagnostic.checkConnection() captures a single snapshot of the current network; onConnectivityChanged emits a fresh snapshot on every transport change.

@@ -145,4 +145,4 @@

}, ); } -}


Zero Network Kit · MPL-2.0
\ No newline at end of file +}

Zero Network Kit · MPL-2.0
\ No newline at end of file diff --git a/docs/DNS/index.html b/docs/DNS/index.html index 5a552f8..833da74 100644 --- a/docs/DNS/index.html +++ b/docs/DNS/index.html @@ -1,4 +1,4 @@ -
🌍 DNS

DNS / DNS 解析

+
🌍 DNS

DNS / DNS 解析

NetworkDiagnostic.resolve() tests DNS resolution across multiple servers in parallel, so you can spot the fastest one. Each result is either the system resolver or a raw UDP query against an explicit server.

@@ -72,4 +72,4 @@

final fastest = results
     .where((r) => r.isSuccess)
     .reduce((a, b) => a.responseTimeMs <= b.responseTimeMs ? a : b);
-print('fastest resolver: ${fastest.server}');


Zero Network Kit · MPL-2.0
\ No newline at end of file +print('fastest resolver: ${fastest.server}');

Zero Network Kit · MPL-2.0
\ No newline at end of file diff --git a/docs/FAQ/index.html b/docs/FAQ/index.html index f42d4fe..6599fee 100644 --- a/docs/FAQ/index.html +++ b/docs/FAQ/index.html @@ -1,4 +1,4 @@ -
❓ FAQ

FAQ / 常见问题

+

Zero Network Kit · MPL-2.0
\ No newline at end of file +

采用 MPL-2.0。详见仓库 LICENSE。


Zero Network Kit · MPL-2.0
\ No newline at end of file diff --git a/docs/Full-Report/index.html b/docs/Full-Report/index.html index 4fef227..b463856 100644 --- a/docs/Full-Report/index.html +++ b/docs/Full-Report/index.html @@ -1,4 +1,4 @@ -
📋 Full Report

Full Report / 汇总报告

+
📋 Full Report

Full Report / 汇总报告

NetworkDiagnostic.diagnose() runs every probe you ask for and aggregates them into one NetworkDiagnosticReport. A failing sub-test never aborts the run — its field simply stays empty/null.

@@ -80,4 +80,4 @@

includePorts 默认 false,默认运行不会扫描端口。

The report is JSON-encodable via report.toMap(). -报告可通过 report.toMap() 序列化为 JSON。


Zero Network Kit · MPL-2.0
\ No newline at end of file +报告可通过 report.toMap() 序列化为 JSON。


Zero Network Kit · MPL-2.0
\ No newline at end of file diff --git a/docs/Getting-Started/index.html b/docs/Getting-Started/index.html index 9bb8458..01f9616 100644 --- a/docs/Getting-Started/index.html +++ b/docs/Getting-Started/index.html @@ -1,4 +1,4 @@ -
🚀 Getting Started

Getting Started / 快速开始

+
🚀 Getting Started

Getting Started / 快速开始

Quick Start / 快速开始

Import the package and start diagnosing — no init() required:

导入包即可开始诊断,无需 init():

@@ -83,4 +83,4 @@

Installation — Detailed installation methods / 详细安装方式
  • Usage — Global config & dependency injection / 全局配置与依赖注入
  • Platform Support — Capability matrix / 能力矩阵
  • -


    Zero Network Kit · MPL-2.0
    \ No newline at end of file +

    Zero Network Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Installation/index.html b/docs/Installation/index.html index 905ad72..45784f1 100644 --- a/docs/Installation/index.html +++ b/docs/Installation/index.html @@ -1,9 +1,9 @@ -
    📦 Installation

    Installation / 安装

    +

    Then run:

    然后运行:

    flutter pub get
    @@ -14,7 +14,7 @@

    zero_network_kit: git: url: https://github.com/zero-labsco/zero_network_kit.git - ref: release/v1.0.4

    + ref: release/v1.0.5

    Platform Setup / 平台配置

    Android

    The plugin manifest already declares INTERNET, ACCESS_NETWORK_STATE and @@ -62,4 +62,5 @@

  • Getting Started — Quick start guide / 快速开始
  • Usage — Full usage guide / 完整使用指南
  • -


    Zero Network Kit · MPL-2.0
    \ No newline at end of file + +

    Zero Network Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Ping/index.html b/docs/Ping/index.html index ac6d08e..5b84e3a 100644 --- a/docs/Ping/index.html +++ b/docs/Ping/index.html @@ -1,4 +1,4 @@ -
    ⏱ Latency / Ping

    Latency / Ping / 延迟探测

    +
    ⏱ Latency / Ping

    Latency / Ping / 延迟探测

    NetworkDiagnostic.ping() measures round-trip time towards a target host and reports sent / received counts, packet loss, min/avg/max and jitter.

    NetworkDiagnostic.ping() 测量到目标主机的往返时间,并给出发送/接收次数、丢包率、 @@ -75,4 +75,4 @@

    mode: PingMode.icmp on desktop to get closer to real ICMP. TCP 探测需要一个在监听的端口。对不接受 TCP/443 的主机探测 port: 443 会失败—— 那是“被过滤”,不是“离线”。在桌面上用 PingMode.icmp 更接近真实 ICMP。

    -


    Zero Network Kit · MPL-2.0
    \ No newline at end of file +

    Zero Network Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Platform-Support/index.html b/docs/Platform-Support/index.html index 1ca17e8..772c460 100644 --- a/docs/Platform-Support/index.html +++ b/docs/Platform-Support/index.html @@ -1,4 +1,4 @@ -
    🖥 Platform Support

    Platform Support / 平台支持

    +
    🖥 Platform Support

    Platform Support / 平台支持

    zero_network_kit is declared on six plugin platforms: Android, iOS, macOS, Windows, Linux and Web. Web support is partial: the services that depend on dart:io are swapped for browser-safe equivalents, and the @@ -276,4 +276,4 @@

    }

    details is always null on the web, and usually null on desktop too (see above).

    -

    Web 上 details 恒为 null,桌面端通常也为 null(见上文)。


    Zero Network Kit · MPL-2.0
    \ No newline at end of file +

    Web 上 details 恒为 null,桌面端通常也为 null(见上文)。


    Zero Network Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Ports/index.html b/docs/Ports/index.html index de5292b..f28d168 100644 --- a/docs/Ports/index.html +++ b/docs/Ports/index.html @@ -1,4 +1,4 @@ -
    🔌 Ports

    Ports / 端口检测

    +
    🔌 Ports

    Ports / 端口检测

    checkPort() returns a full PortCheckResult for one port; isPortOpen() is the plain boolean convenience; scanPorts() runs many ports concurrently with bounded in-flight connections.

    @@ -65,4 +65,4 @@

    checkPort() 返回 PortCheckResult(含 isOpen、responseTimeMs、 errorMessage);单端口布尔诉求请用 isPortOpen()。需要 RTT 或失败原因时可直接 调用 checkPort(),或给 scanPorts(..., ports: [443]) 取单条结果。

    -


    Zero Network Kit · MPL-2.0
    \ No newline at end of file +

    Zero Network Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Quality/index.html b/docs/Quality/index.html index 52d9735..f2f0991 100644 --- a/docs/Quality/index.html +++ b/docs/Quality/index.html @@ -1,4 +1,4 @@ -
    ⭐ Quality Score

    Quality Score / 质量评分

    +
    ⭐ Quality Score

    Quality Score / 质量评分

    NetworkDiagnostic.evaluateQuality() returns a weighted 0–100 score plus a level and human-readable suggestions, computed over whatever metrics are available.

    @@ -128,4 +128,4 @@

    ); print('${score.score.toStringAsFixed(1)} → ${score.level.label}');

    Tune the ideal values globally through NetworkDiagnosticConfig(qualityTargets: ...). -调整理想值请通过 NetworkDiagnosticConfig(qualityTargets: ...) 全局设置。


    Zero Network Kit · MPL-2.0
    \ No newline at end of file +调整理想值请通过 NetworkDiagnosticConfig(qualityTargets: ...) 全局设置。


    Zero Network Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Speed-Test/index.html b/docs/Speed-Test/index.html index 90646dc..9cf9764 100644 --- a/docs/Speed-Test/index.html +++ b/docs/Speed-Test/index.html @@ -1,4 +1,4 @@ -
    🚀 Speed Test

    Speed Test / 测速

    +
    🚀 Speed Test

    Speed Test / 测速

    NetworkDiagnostic.runSpeedTest() measures download and upload throughput and samples latency during the run, reporting progress through onProgress.

    NetworkDiagnostic.runSpeedTest() 测量下载与上传吞吐量,并在过程中采样延迟,通过 @@ -87,4 +87,4 @@

    downloadUrl / uploadUrl for production. 默认端点指向 Cloudflare 公共服务并会产生真实流量(默认下载约 25 MB)。在按量计费 网络上运行前请征得同意,并为生产环境替换 downloadUrl / uploadUrl。

    -


    Zero Network Kit · MPL-2.0
    \ No newline at end of file +

    Zero Network Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/Usage/index.html b/docs/Usage/index.html index f5e34d4..402b5c6 100644 --- a/docs/Usage/index.html +++ b/docs/Usage/index.html @@ -1,4 +1,4 @@ -
    📖 Usage

    Usage / 用法

    +

    Zero Network Kit · MPL-2.0
    \ No newline at end of file +

    Zero Network Kit · MPL-2.0
    \ No newline at end of file diff --git a/docs/_next/static/nuDvG3BQ_xqr8cZZcfr-R/_buildManifest.js b/docs/_next/static/_EQnS2NtxmlSi3SDEn1lD/_buildManifest.js similarity index 91% rename from docs/_next/static/nuDvG3BQ_xqr8cZZcfr-R/_buildManifest.js rename to docs/_next/static/_EQnS2NtxmlSi3SDEn1lD/_buildManifest.js index 64314df..e968385 100644 --- a/docs/_next/static/nuDvG3BQ_xqr8cZZcfr-R/_buildManifest.js +++ b/docs/_next/static/_EQnS2NtxmlSi3SDEn1lD/_buildManifest.js @@ -1 +1 @@ -self.__BUILD_MANIFEST=function(e){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[e,"static/chunks/pages/index-2a81fce2c8fa4aa7.js"],"/API-Reference":[e,"static/chunks/pages/API-Reference-e13a2917d7546319.js"],"/Benchmark":[e,"static/chunks/pages/Benchmark-a292e8cb0a504f00.js"],"/Connectivity":[e,"static/chunks/pages/Connectivity-f9b385023ad97f07.js"],"/DNS":[e,"static/chunks/pages/DNS-93340032eaa9ffdd.js"],"/FAQ":[e,"static/chunks/pages/FAQ-bccbbb52a0b53bb8.js"],"/Full-Report":[e,"static/chunks/pages/Full-Report-8fdda01136483e9e.js"],"/Getting-Started":[e,"static/chunks/pages/Getting-Started-0b9a79d0ebc87cdf.js"],"/Installation":[e,"static/chunks/pages/Installation-4297b711c2aa203c.js"],"/Ping":[e,"static/chunks/pages/Ping-e7614d3667bef7af.js"],"/Platform-Support":[e,"static/chunks/pages/Platform-Support-3994cdd8603e191b.js"],"/Ports":[e,"static/chunks/pages/Ports-743ec1523389b840.js"],"/Quality":[e,"static/chunks/pages/Quality-ac653fb19750eaeb.js"],"/Speed-Test":[e,"static/chunks/pages/Speed-Test-03d333b534600839.js"],"/Usage":[e,"static/chunks/pages/Usage-668e8555c3b8432b.js"],"/_error":["static/chunks/pages/_error-7a92967bea80186d.js"],"/_meta":["static/chunks/pages/_meta-b47e7a01bb6b7929.js"],sortedPages:["/","/API-Reference","/Benchmark","/Connectivity","/DNS","/FAQ","/Full-Report","/Getting-Started","/Installation","/Ping","/Platform-Support","/Ports","/Quality","/Speed-Test","/Usage","/_app","/_error","/_meta"]}}("static/chunks/812-02e96d01ac591260.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB(); \ No newline at end of file +self.__BUILD_MANIFEST=function(e){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[e,"static/chunks/pages/index-2a81fce2c8fa4aa7.js"],"/API-Reference":[e,"static/chunks/pages/API-Reference-e13a2917d7546319.js"],"/Benchmark":[e,"static/chunks/pages/Benchmark-a292e8cb0a504f00.js"],"/Connectivity":[e,"static/chunks/pages/Connectivity-f9b385023ad97f07.js"],"/DNS":[e,"static/chunks/pages/DNS-93340032eaa9ffdd.js"],"/FAQ":[e,"static/chunks/pages/FAQ-bccbbb52a0b53bb8.js"],"/Full-Report":[e,"static/chunks/pages/Full-Report-8fdda01136483e9e.js"],"/Getting-Started":[e,"static/chunks/pages/Getting-Started-0b9a79d0ebc87cdf.js"],"/Installation":[e,"static/chunks/pages/Installation-c75a9ee9feffc261.js"],"/Ping":[e,"static/chunks/pages/Ping-e7614d3667bef7af.js"],"/Platform-Support":[e,"static/chunks/pages/Platform-Support-3994cdd8603e191b.js"],"/Ports":[e,"static/chunks/pages/Ports-743ec1523389b840.js"],"/Quality":[e,"static/chunks/pages/Quality-ac653fb19750eaeb.js"],"/Speed-Test":[e,"static/chunks/pages/Speed-Test-03d333b534600839.js"],"/Usage":[e,"static/chunks/pages/Usage-668e8555c3b8432b.js"],"/_error":["static/chunks/pages/_error-7a92967bea80186d.js"],"/_meta":["static/chunks/pages/_meta-b47e7a01bb6b7929.js"],sortedPages:["/","/API-Reference","/Benchmark","/Connectivity","/DNS","/FAQ","/Full-Report","/Getting-Started","/Installation","/Ping","/Platform-Support","/Ports","/Quality","/Speed-Test","/Usage","/_app","/_error","/_meta"]}}("static/chunks/812-02e96d01ac591260.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB(); \ No newline at end of file diff --git a/docs/_next/static/nuDvG3BQ_xqr8cZZcfr-R/_ssgManifest.js b/docs/_next/static/_EQnS2NtxmlSi3SDEn1lD/_ssgManifest.js similarity index 100% rename from docs/_next/static/nuDvG3BQ_xqr8cZZcfr-R/_ssgManifest.js rename to docs/_next/static/_EQnS2NtxmlSi3SDEn1lD/_ssgManifest.js diff --git a/docs/_next/static/chunks/nextra-data-en-US.json b/docs/_next/static/chunks/nextra-data-en-US.json index 9774a81..e975c2d 100644 --- a/docs/_next/static/chunks/nextra-data-en-US.json +++ b/docs/_next/static/chunks/nextra-data-en-US.json @@ -1 +1 @@ -{"/API-Reference":{"title":"API Reference / 接口参考","data":{"":"A condensed field reference for every public model. For copy-paste examples of\neach method, see the capability pages.这里是每个公开模型的字段速查。每个方法的复制即用示例请见各能力页面。","networktype#NetworkType":"Member\tNotes\tvalues\tnone, wifi, mobile, ethernet, vpn, bluetooth, other\tlabel\tHuman readable: 'Wi-Fi', 'Mobile', …\tid\tStable English identifier (name), safe to persist\tisConnected\ttrue unless none\tNetworkType.fromRaw(Object?)\tParses a raw platform string","networkconnectioninfo#NetworkConnectionInfo":"isConnected, type, ssid, signalStrength (dBm), ipAddress,\nipv6Address, gateway, macAddress, isVpn, isReachable, timestamp,\nplus copyWith({bool? isReachable}).","pingresult#PingResult":"Fields host, port, mode, sent, received, times, timestamp;\nderived getters lost, packetLoss (%), minTime, maxTime, averageTime,\njitter, isSuccess.","dnstestresult#DnsTestResult":"Fields server, domain, isSuccess, responseTime, resolvedIps,\nerrorMessage, timestamp; derived responseTimeMs, primaryAddress.","portcheckresult#PortCheckResult":"Fields host, port, isOpen, responseTime, errorMessage, timestamp;\nderived responseTimeMs.","speedtestresult#SpeedTestResult":"Fields downloadSpeed, uploadSpeed (Mbps), ping (ms), jitter (ms),\npacketLoss (%), downloadedBytes, uploadedBytes, downloadDuration,\nuploadDuration, server, timestamp; derived duration; static helper\nSpeedTestResult.mbpsFromBytes(bytes, elapsed).","speedtestprogress--speedtestphase#SpeedTestProgress / SpeedTestPhase":"phase (download, upload, completed), bytes, elapsed, speedMbps.","networkqualityscore--networkqualitylevel#NetworkQualityScore / NetworkQualityLevel":"score (0–100), level, metrics (Map), suggestions,\ntimestamp; NetworkQualityLevel.fromScore(double), level.label.","networkdiagnosticreport#NetworkDiagnosticReport":"connection, ping, dnsResults, portResults, speedTest, quality,\ntimestamp.","benchmarkresult--benchmarksuiteresult#BenchmarkResult / BenchmarkSuiteResult":"BenchmarkResult: testName, iterations, totalDuration,\naverageDuration, minDuration, maxDuration, standardDeviation,\noperationsPerSecond, failures, timestamp.\nBenchmarkSuiteResult: suiteName, results, totalDuration, timestamp,\nand suite['name'].","gotchas--常见陷阱#Gotchas / 常见陷阱":"type.label, not displayName. NetworkType exposes label / id.\ncheckPort() returns PortCheckResult. Use isPortOpen() for a plain\nboolean, or scanPorts(..., ports: [p]).first for RTT / error message.\nincludeSystemResolver defaults to false. Pass true to add the\nsystem DNS row.\nA TCP ping needs a listening port. Probing host with port: 443 fails\nif that host does not accept TCP/443 — that is \"filtered\", not \"offline\". Use\nmode: PingMode.icmp on desktop to get closer to real ICMP.\nPingMode.icmp is desktop-only and silently degrades when the ping\nbinary is unavailable.\nThe speed test moves real traffic (≈25 MB download by default). Ask for\nconsent before running it on a metered connection.\nMissing permissions degrade, they never throw. A missing location\npermission yields ssid == null; the rest of the snapshot still arrives.\nincludePorts is false by default in diagnose().\ninit() is optional, but dispose() only closes the client the plugin\nowns — an httpClient you injected stays open, so close it yourself.\nFull source-level API docs live in the repo:\nAPI.md\n(English) and\nAPI_zh.md\n(简体中文).\n完整源码级 API 文档见仓库内的\nAPI.md\n与 API_zh.md。"}},"/Benchmark":{"title":"Benchmarks / 微基准测试","data":{"":"NetworkBenchmark measures how fast the diagnostics API itself runs — useful\nwhen deciding whether a diagnostic belongs on your startup path.NetworkBenchmark 衡量诊断 API 自身的开销——可用在判断某项诊断能否放在启动路径上。","all-at-once--一次性全跑#All at once / 一次性全跑":"final suite = await NetworkBenchmark.runAll(\n iterations: 20,\n warmupIterations: 3,\n host: '1.1.1.1',\n dnsDomain: 'example.com',\n port: 443,\n);\nfor (final result in suite.results) {\n print('${result.testName.padRight(12)} '\n 'avg ${(result.averageDuration.inMicroseconds / 1000).toStringAsFixed(2)} ms '\n '± ${(result.standardDeviation.inMicroseconds / 1000).toStringAsFixed(2)} ms '\n '${result.operationsPerSecond.toStringAsFixed(1)} ops/s '\n 'failures=${result.failures}');\n}\n// Look up one benchmark by name / 按名称查单个结果\nprint(suite['ping']?.averageDuration);\nprint(suite.totalDuration);","individual-suites--单个基准#Individual suites / 单个基准":"await NetworkBenchmark.benchmarkConnection(iterations: 20);\nawait NetworkBenchmark.benchmarkPing(iterations: 20, host: '1.1.1.1');\nawait NetworkBenchmark.benchmarkDns(iterations: 20, domain: 'example.com');\nawait NetworkBenchmark.benchmarkPortCheck(iterations: 20, port: 443);\nawait NetworkBenchmark.benchmarkPlatformChannel(iterations: 20);","result-fields--结果字段#Result fields / 结果字段":"BenchmarkResult: testName, iterations, totalDuration, averageDuration,\nminDuration, maxDuration, standardDeviation, operationsPerSecond,\nfailures, timestamp.BenchmarkSuiteResult: suiteName, results, totalDuration, timestamp, and\nsuite['name']."}},"/Connectivity":{"title":"Connectivity / 连通性","data":{"":"NetworkDiagnostic.checkConnection() captures a single snapshot of the current\nnetwork; onConnectivityChanged emits a fresh snapshot on every transport\nchange.NetworkDiagnostic.checkConnection() 捕获当前网络的一次快照;onConnectivityChanged\n在每次传输方式变化时推送新快照。","snapshot--快照#Snapshot / 快照":"final connection = await NetworkDiagnostic.checkConnection();\nprint('type : ${connection.type.label}'); // Wi-Fi / Mobile / Ethernet …\nprint('connected : ${connection.isConnected}');\nprint('ipv4 : ${connection.ipAddress}');\nprint('ipv6 : ${connection.ipv6Address}');\nprint('gateway : ${connection.gateway}');\nprint('ssid : ${connection.ssid}');\nprint('rssi : ${connection.signalStrength} dBm');\nprint('mac : ${connection.macAddress}');\nprint('vpn : ${connection.isVpn}');\nprint('timestamp : ${connection.timestamp}');\nField\tNotes\ttype\tNetworkType (none, wifi, mobile, ethernet, vpn, bluetooth, other)\tisConnected\ttrue unless type is none\tipAddress / ipv6Address\tLocal IPv4 / IPv6 (Dart NetworkInterface)\tgateway / macAddress\tNative; null on desktop except Windows\tssid / signalStrength\tNative; always null on desktop\tisVpn\tNative VPN detection\tisReachable\tOnly filled when probeReachability: true\ttimestamp\tWhen the snapshot was taken\t\nUse connection.type.label / connection.type.id. There is no\ndisplayName.\n请使用 connection.type.label / connection.type.id,没有 displayName。","parameters--参数#Parameters / 参数":"Parameter\tDefault\tMeaning\tincludeNativeDetails\ttrue\tAlso read SSID / gateway / MAC / VPN from the native side\tprobeReachability\tfalse\tMake a real request and fill isReachable\tprobeTimeout\t3s\tTimeout of that reachability probe\t\n// Skip the native channel (cheapest call — good on hot paths).\nfinal quick = await NetworkDiagnostic.checkConnection(includeNativeDetails: false);\n// Prove the internet is actually reachable, not just that a NIC is up.\nfinal verified = await NetworkDiagnostic.checkConnection(\n probeReachability: true,\n probeTimeout: const Duration(seconds: 5),\n);\nif (verified.isReachable == false) {\n print('Interface is up but the internet is unreachable.');\n}","change-stream--变化监听#Change stream / 变化监听":"final subscription = NetworkDiagnostic.onConnectivityChanged.listen(\n (info) => print('now on ${info.type.id} · ${info.ipAddress}'),\n);\n// Later:\nawait subscription.cancel();\nIn a widget:\nStreamBuilder(\n stream: NetworkDiagnostic.onConnectivityChanged,\n builder: (context, snapshot) {\n final info = snapshot.data;\n if (info == null) return const Text('Checking…');\n return Text('${info.type.label} · ${info.isConnected}');\n },\n)","connectivity-only-minimal-example--仅连通性最小示例#Connectivity-only: minimal example / 仅连通性:最小示例":"If all you need is \"am I online, on what transport, what IP, and react to\nchanges\", you only need this page — no init() required.如果只需要「在不在線、走什么網络、IP 是多少、切换时通知」,只看本页即可——\n无需 init()。\nclass ConnectivityScreen extends StatelessWidget {\n const ConnectivityScreen({super.key});\n @override\n Widget build(BuildContext context) {\n return StreamBuilder(\n stream: NetworkDiagnostic.onConnectivityChanged,\n builder: (context, snap) {\n final c = snap.data;\n if (c == null) {\n return const Center(child: CircularProgressIndicator());\n }\n return ListView(\n children: [\n ListTile(title: const Text('Type / 类型'), trailing: Text(c.type.label)),\n ListTile(title: const Text('Online / 在线'), trailing: Text('${c.isConnected}')),\n ListTile(title: const Text('IPv4'), trailing: Text(c.ipAddress ?? '—')),\n ListTile(title: const Text('IPv6'), trailing: Text(c.ipv6Address ?? '—')),\n ],\n );\n },\n );\n }\n}"}},"/DNS":{"title":"DNS / DNS 解析","data":{"":"NetworkDiagnostic.resolve() tests DNS resolution across multiple servers in\nparallel, so you can spot the fastest one. Each result is either the system\nresolver or a raw UDP query against an explicit server.NetworkDiagnostic.resolve() 并行测试多台 DNS 服务器的解析表现,从而挑出最快的一台。\n每条结果要么来自系统解析器,要么是对指定服务器的原始 UDP 查询。","basic--基础#Basic / 基础":"final results = await NetworkDiagnostic.resolve(\n domain: 'example.com',\n dnsServers: const ['1.1.1.1', '8.8.8.8', '114.114.114.114'],\n timeout: const Duration(seconds: 5),\n concurrent: true,\n includeSystemResolver: true,\n);\nfor (final r in results) {\n if (r.isSuccess) {\n print('${r.server.padRight(16)} → ${r.resolvedIps.join(\", \")} '\n '(${r.responseTimeMs.toStringAsFixed(1)} ms)');\n } else {\n print('${r.server.padRight(16)} ✗ ${r.errorMessage}');\n }\n}\nParameter\tDefault\tMeaning\tdomain\tconfig.dnsDomain\tDomain to resolve\tdnsServers\tconfig.dnsServers\tServers queried in parallel (or in series)\ttimeout\tconfig.dnsTimeout\tTimeout of one query\tconcurrent\ttrue\tQuery all servers at once\tincludeSystemResolver\tfalse\tAlso add a system row\t\nNotes / 说明:\nA row with server == 'system' comes from the OS resolver; the others are raw\nUDP queries against the listed IPs, encoded by the built-in DnsPacket wire\ncodec.\nserver == 'system' 的行来自系统解析器,其余是对指定 IP 的原始 UDP 查询,由内置\nDnsPacket 编解码。\nincludeSystemResolver defaults to false at the facade level — pass\ntrue to include it.\n门面层默认不包含系统解析器,需显式传 true。","pick-the-fastest--挑出最快的#Pick the fastest / 挑出最快的":"final fastest = results\n .where((r) => r.isSuccess)\n .reduce((a, b) => a.responseTimeMs <= b.responseTimeMs ? a : b);\nprint('fastest resolver: ${fastest.server}');"}},"/FAQ":{"title":"FAQ / 常见问题","data":{"do-i-need-to-call-init--需要调用-init-吗#Do I need to call init()? / 需要调用 init() 吗?":"No. Every API falls back to built-in defaults. Call ZeroNetworkKit.init() only\nwhen you want to override defaults (e.g. a custom reachability host or DNS\nservers). Connectivity alone works with zero setup.不需要。每个 API 都走内置默认值。只有在想覆盖默认参数(如自定义可达性主机或 DNS\n服务器)时才调用 ZeroNetworkKit.init()。单用连通性完全零配置。","which-platforms-are-supported--支持哪些平台#Which platforms are supported? / 支持哪些平台?":"Android, iOS, macOS, Windows, Linux and Web (partial — see\nPlatform Support).支持 Android、iOS、macOS、Windows、Linux 与 Web(部分支持)。详见\nPlatform Support。","why-is-ssid--signalstrength-null-on-desktop--为什么桌面上的-ssid信号强度是-null#Why is ssid / signalStrength null on desktop? / 为什么桌面上的 ssid/信号强度是 null?":"Desktop uses tier A: SSID and RSSI require system APIs that aren't exposed on\nmacOS/Linux/Windows the same way. They are always null on desktop; Windows\nadditionally provides gateway / MAC / DNS / VPN via GetAdaptersAddresses.桌面采用 A 档:SSID 与 RSSI 依赖系统 API,在桌面上不以同样方式暴露,因此恒为 null;\nWindows 额外通过 GetAdaptersAddresses 提供网关 / MAC / DNS / VPN。","why-does-getnativenetworkdetails-return-null--为什么-getnativenetworkdetails-返回-null#Why does getNativeNetworkDetails() return null? / 为什么 getNativeNetworkDetails 返回 null?":"Either the platform has no such data (e.g. desktop tier A), or the required\npermission is missing (e.g. location for Wi-Fi SSID on Android/iOS). The call\nnever throws — it degrades gracefully.要么是平台没有该数据(如桌面 A 档),要么是缺权限(如 Android/iOS 上读取 Wi-Fi SSID\n需定位权限)。该调用绝不抛异常,会优雅降级。","does-the-speed-test-use-my-bandwidth--测速会消耗我的流量吗#Does the speed test use my bandwidth? / 测速会消耗我的流量吗?":"Yes — the default endpoints are Cloudflare's public service and download ≈25 MB\nby default. Ask for consent on metered connections and override downloadUrl /\nuploadUrl for production.会——默认端点指向 Cloudflare 公共服务,默认下载约 25 MB。在按量计费网络上请征得同意,\n生产环境请替换 downloadUrl / uploadUrl。","how-do-i-test-my-integration-without-the-network--如何脱离网络测试我的集成#How do I test my integration without the network? / 如何脱离网络测试我的集成?":"Every service is injectable. Implement ConnectivityAdapter (or pass fake\nservice instances) and call NetworkDiagnostic.configure(...), then\nNetworkDiagnostic.reset() to restore defaults. See\nUsage → Dependency injection.每个服务都可注入。实现 ConnectivityAdapter(或直接传入假服务实例)后调用\nNetworkDiagnostic.configure(...),再用 NetworkDiagnostic.reset() 恢复默认。详见\n用法 → 依赖注入。","how-is-this-licensed--采用什么许可证#How is this licensed? / 采用什么许可证?":"MPL-2.0. See the repository LICENSE.采用 MPL-2.0。详见仓库 LICENSE。"}},"/Full-Report":{"title":"Full Report / 汇总报告","data":{"":"NetworkDiagnostic.diagnose() runs every probe you ask for and aggregates them\ninto one NetworkDiagnosticReport. A failing sub-test never aborts the run — its\nfield simply stays empty/null.NetworkDiagnostic.diagnose() 运行你指定的各项探测,并汇总为一份\nNetworkDiagnosticReport。任何子项失败都不会中断整体流程,对应字段保持空。","basic--基础#Basic / 基础":"final report = await NetworkDiagnostic.diagnose(\n includePing: true,\n includeDns: true,\n includePorts: true,\n includeSpeedTest: true,\n includeUpload: true,\n host: '1.1.1.1',\n dnsDomain: 'example.com',\n dnsServers: const ['1.1.1.1', '8.8.8.8'],\n ports: const [80, 443],\n);\nprint(report); // one-line summary\nprint(report.connection.type.label);\nprint(report.ping?.averageTime);\nprint(report.dnsResults.length);\nprint(report.portResults.where((p) => p.isOpen).length);\nprint(report.speedTest?.downloadSpeed);\nprint(report.quality.level.label);\nParameter\tDefault\tMeaning\tincludePing\ttrue\tSample latency / jitter / loss\tincludeDns\ttrue\tSample DNS latency\tincludePorts\tfalse\tScan ports (off by default)\tincludeSpeedTest\ttrue\tSample download / upload\tincludeUpload\ttrue\tInclude the upload metric\thost\tconfig.pingHost\tLatency / port target\tdnsDomain / dnsServers\tfrom config\tDNS target\tports\tconfig.probePorts\tPorts scanned when includePorts is true\t\nincludePorts defaults to false, so a default run does not scan ports.\nincludePorts 默认 false,默认运行不会扫描端口。\nThe report is JSON-encodable via report.toMap().\n报告可通过 report.toMap() 序列化为 JSON。"}},"/Getting-Started":{"title":"Getting Started / 快速开始","data":{"quick-start--快速开始#Quick Start / 快速开始":"Import the package and start diagnosing — no init() required:导入包即可开始诊断,无需 init():\nimport 'package:flutter/material.dart';\nimport 'package:zero_network_kit/zero_network_kit.dart';\nvoid main() {\n // Optional: apply your own global defaults once.\n // 可选:全局定制一次默认参数。\n ZeroNetworkKit.init(\n config: const NetworkDiagnosticConfig(pingHost: '1.1.1.1'),\n );\n runApp(const MyApp());\n}\nIf you only need connectivity, you don't even need init():如果只需要连通性,连 init() 都不需要:\nfinal connection = await NetworkDiagnostic.checkConnection();\nprint('${connection.type.label} · ${connection.ipAddress}');","what-you-get--你能得到什么#What you get / 你能得到什么":"Every method is a static, non-blocking call on the NetworkDiagnostic facade.\nThe plugin is pure Dart under the hood, so it is fully injectable and testable\nwithout touching the network.每个方法都是 NetworkDiagnostic 门面上的静态、非阻塞调用。插件内核是纯 Dart,\n因此完全可注入、可脱离网络做测试。\nNeed\tCall\tAm I online, on what transport?\tNetworkDiagnostic.checkConnection()\tReact to Wi-Fi ⇄ cellular switches\tNetworkDiagnostic.onConnectivityChanged\tLatency, jitter, packet loss\tNetworkDiagnostic.ping()\tIs DNS slow or broken?\tNetworkDiagnostic.resolve()\tIs host:port reachable?\tNetworkDiagnostic.checkPort() / isPortOpen()\tHow fast is down/up?\tNetworkDiagnostic.runSpeedTest()\tOne number for \"good or bad\"\tNetworkDiagnostic.evaluateQuality()\tEverything at once\tNetworkDiagnostic.diagnose()\tNative version / SSID / gateway / MAC\tZeroNetworkKit.getNativeNetworkDetails()\t\nSee Which API do I need? for the full map.","lifecycle--生命周期#Lifecycle / 生命周期":"ZeroNetworkKit.isInitialized; // false until init() runs\nZeroNetworkKit.config; // the effective NetworkDiagnosticConfig\nawait ZeroNetworkKit.dispose(); // releases the HTTP client the plugin owns\ndispose() only closes the HTTP client the plugin created itself. An\nhttp.Client you injected stays open — close it yourself.dispose() 只会关闭插件自己创建的 HTTP 客户端;你注入的 http.Client 不会被关闭。","next-steps--下一步#Next Steps / 下一步":"Installation — Detailed installation methods / 详细安装方式\nUsage — Global config & dependency injection / 全局配置与依赖注入\nPlatform Support — Capability matrix / 能力矩阵"}},"/Installation":{"title":"Installation / 安装","data":{"from-pubdev-recommended--从-pubdev-安装推荐#From pub.dev (Recommended) / 从 pub.dev 安装(推荐)":"Add the following to your pubspec.yaml:在 pubspec.yaml 中添加以下依赖:\ndependencies:\n zero_network_kit: ^1.0.4\nThen run:然后运行:\nflutter pub get","from-github--从-github-安装#From GitHub / 从 GitHub 安装":"Alternatively, install from GitHub:或者从 GitHub 安装:\ndependencies:\n zero_network_kit:\n git:\n url: https://github.com/zero-labsco/zero_network_kit.git\n ref: release/v1.0.4","platform-setup--平台配置#Platform Setup / 平台配置":"","android#Android":"The plugin manifest already declares INTERNET, ACCESS_NETWORK_STATE and\nACCESS_WIFI_STATE. Reading the Wi-Fi SSID additionally requires the\nlocation permission (ACCESS_FINE_LOCATION) on Android 8.1+, otherwise the\nsnapshot reports ssid: null.插件清单已声明 INTERNET、ACCESS_NETWORK_STATE 与 ACCESS_WIFI_STATE。读取 Wi-Fi\nSSID 还需在 Android 8.1+ 申请定位权限(ACCESS_FINE_LOCATION),否则快照中\nssid 为 null。","ios#iOS":"No additional configuration needed. Reading the Wi-Fi SSID additionally\nrequires the Access WiFi Information capability plus location authorisation.无需额外配置。读取 Wi-Fi SSID 还需开启 Access WiFi Information 能力并授权定位。","macos--windows--linux#macOS / Windows / Linux":"The plugin is declared on all three desktop platforms. On desktop, native\ndetails such as SSID / RSSI are always null (tier A). Windows additionally\nprovides gateway / MAC / DNS / VPN through GetAdaptersAddresses; macOS and\nLinux report only the Dart-side IP/IPv6. See\nPlatform Support for the full matrix.插件已在三个桌面平台声明。桌面上 SSID / 信号强度等原生详情恒为 null(A 档)。\nWindows 额外通过 GetAdaptersAddresses 提供网关 / MAC / DNS / VPN;macOS 与 Linux\n仅提供 Dart 侧的 IP/IPv6。完整矩阵见平台支持。","import--导入#Import / 导入":"import 'package:zero_network_kit/zero_network_kit.dart';","requirements--环境要求#Requirements / 环境要求":"Requirement\tVersion\tFlutter\t>= 3.3.0\tDart SDK\t>= 3.11.0 < 4.0.0","next-steps--下一步#Next Steps / 下一步":"Getting Started — Quick start guide / 快速开始\nUsage — Full usage guide / 完整使用指南"}},"/Ping":{"title":"Latency / Ping / 延迟探测","data":{"":"NetworkDiagnostic.ping() measures round-trip time towards a target host and\nreports sent / received counts, packet loss, min/avg/max and jitter.NetworkDiagnostic.ping() 测量到目标主机的往返时间,并给出发送/接收次数、丢包率、\n最小/平均/最大耗时与抖动。","basic--基础#Basic / 基础":"final ping = await NetworkDiagnostic.ping(\n host: '1.1.1.1',\n count: 5,\n timeout: const Duration(seconds: 2),\n interval: const Duration(milliseconds: 200),\n port: 443,\n);\nprint('received : ${ping.received}/${ping.sent}');\nprint('loss : ${ping.packetLoss.toStringAsFixed(1)} %');\nprint('min/avg/max: ${ping.minTime.toStringAsFixed(1)} / '\n '${ping.averageTime.toStringAsFixed(1)} / '\n '${ping.maxTime.toStringAsFixed(1)} ms');\nprint('jitter : ${ping.jitter.toStringAsFixed(2)} ms');\nprint('samples : ${ping.times}');\nParameter\tDefault\tMeaning\thost\tconfig.pingHost\tTarget host\tcount\tconfig.pingCount\tNumber of probes\ttimeout\tconfig.pingTimeout\tPer-probe timeout\tinterval\tconfig.pingInterval\tDelay between probes\tport\tconfig.pingPort\tTCP port probed in PingMode.tcp\tmode\tPingMode.tcp\tPingMode.tcp or PingMode.icmp","tcp-vs-icmp--tcp-与-icmp#TCP vs ICMP / TCP 与 ICMP":"PingMode.tcp performs a TCP handshake to host:port — the portable equivalent\nof ICMP and the only mode available on Android/iOS.PingMode.tcp 通过 TCP 握手到 host:port 测量往返,是移动端唯一可用的模式。\n// Desktop only — falls back gracefully if the `ping` binary is unavailable.\nawait NetworkDiagnostic.ping(host: '1.1.1.1', count: 4, mode: PingMode.icmp);\nping.isSuccess is true when at least one probe answered; check it before\ntrusting the averages.至少一次成功响应时 isSuccess 为 true,读平均值前建议先判断它。\nA TCP ping needs a listening port. Probing host with port: 443 fails if\nthat host does not accept TCP/443 — that is \"filtered\", not \"offline\". Use\nmode: PingMode.icmp on desktop to get closer to real ICMP.\nTCP 探测需要一个在监听的端口。对不接受 TCP/443 的主机探测 port: 443 会失败——\n那是“被过滤”,不是“离线”。在桌面上用 PingMode.icmp 更接近真实 ICMP。"}},"/Platform-Support":{"title":"Platform Support / 平台支持","data":{"":"zero_network_kit is declared on six plugin platforms: Android, iOS,\nmacOS, Windows, Linux and Web. Web support is partial: the services that\ndepend on dart:io are swapped for browser-safe equivalents, and the\ncapabilities the sandbox forbids degrade gracefully instead of failing.zero_network_kit 在 六 个插件平台上声明:Android、iOS、macOS、Windows、Linux\n与 Web。Web 为部分支持:依赖 dart:io 的服务已替换为浏览器安全的等价实现,\n浏览器沙箱禁止的能力会优雅降级而不会失败。","capability-matrix--能力矩阵#Capability matrix / 能力矩阵":"Capability\tAndroid / iOS\tDesktop (macOS / Windows / Linux)\tWeb\tConnectivity (connectivity_plus)\t✅\t✅\t✅\tLocal IP / IPv6 (NetworkInterface)\t✅\t✅\t❌\tNative details (SSID / gateway / MAC / VPN)\t✅\t⚠️ see below\t❌\tTCP ping\t✅\t✅\t⚠️ HTTPS round trip\tICMP ping (Process.run('ping'))\t❌\t✅\t❌\tHTTP ping\t✅\t✅\t✅\tDNS system resolver\t✅\t✅\t✅ DoH\tDNS raw UDP\t✅\t✅\t⚠️ DoH endpoint required\tPort check / scan\t✅\t✅\t❌\tSpeed test\t✅\t✅\t✅\tQuality score\t✅\t✅\t✅\tBenchmarks\t✅\t✅\t✅","networkcapabilities--能力查询#NetworkCapabilities / 能力查询":"Query the host platform before calling, so your UI can hide unsupported cards:在调用前查询当前平台能力,UI 即可据此隐藏不支持的卡片:\nfinal caps = NetworkDiagnostic.capabilities;\nprint(caps.platform); // 'android' | 'ios' | 'macos' | 'windows' | 'linux' | 'web'\nprint(caps.supports(NetworkCapability.nativeDetails)); // mobile: true, desktop: false\nprint(caps.supports(NetworkCapability.icmpPing)); // desktop: true, mobile: false\nRules baked into NetworkCapabilities.current():\nnativeDetails is mobile-only (SSID / gateway / MAC / VPN need system APIs).\nicmpPing is desktop-only (uses the system ping binary).\nOn web the supported set narrows to connectivity, tcpPing,\ndnsSystem, speedTest, quality and benchmark; every other capability is\nabsent.\nNetworkCapabilities.current() 内置规则:\nnativeDetails 仅移动端(SSID / 网关 / MAC / VPN 需要系统 API)。\nicmpPing 仅桌面(使用系统 ping 命令)。\nWeb 上的支持集合收缩为 connectivity、tcpPing、dnsSystem、speedTest、\nquality 与 benchmark,其余能力均不存在。","native-details-on-desktop--桌面原生详情#Native details on desktop / 桌面原生详情":"Desktop uses tier A by default: the native layer only reports the platform\nversion and an (often empty) details map. IP/IPv6 come from Dart\nNetworkInterface. As a result:桌面默认采用 A 档:原生层只报告平台版本与(通常为空的)详情 map,IP/IPv6 由 Dart\nNetworkInterface 兜底。因此:\nSSID / signal strength are always null on desktop.\n桌面上的 SSID / 信号强度恒为 null。\ngateway / macAddress / isVpn are null on macOS and Linux.\nmacOS 与 Linux 上 gateway / macAddress / isVpn 为 null。\nWindows additionally implements GetAdaptersAddresses, so it reports\ngateway / macAddress / DNS / isVpn (SSID still null). This is a bonus\ntier-B fragment kept as-is.\nWindows 额外实现了 GetAdaptersAddresses,因此上报 gateway / macAddress /\nDNS / isVpn(SSID 仍为 null)。这是保留的 B 档赠品。\nWhatever the platform, a missing permission or an unreachable native call never\nthrows — the field simply stays null and the rest of the result still arrives.无论在哪个平台,缺权限或原生调用不可达都不会抛异常——对应字段保持 null,其余\n结果照常返回。","web-support--web-支持#Web support / Web 支持":"The web build exposes the same static API. Capabilities that the browser sandbox\nforbids degrade gracefully — they return null or an \"unavailable\" result\ninstead of throwing:\nCapability\tWeb\tNotes\tConnectivity\t✅\tvia connectivity_plus\tPing (PingMode.tcp)\t⚠️\tmeasured as an HTTPS round trip; the target must send CORS headers\tPing (PingMode.icmp)\t❌\tthrows UnsupportedError\tDNS (system resolver)\t✅\tvia DNS-over-HTTPS\tDNS (explicit server)\t⚠️\tneeds a DoH endpoint, otherwise \"unsupported\"\tSpeed test\t✅\tHTTP download / upload\tQuality score\t✅\tpure function\tBenchmarks\t✅\tpure function\tPort check / scan\t❌\treturns \"unavailable\" results\tNative details (SSID, gateway, MAC, VPN)\t❌\tnull\t\nBrowsers expose no VPN API, so isVpn stays false on the web even when a\nsystem VPN or a local HTTP proxy is active.Web 构建提供同样的静态 API;浏览器沙箱禁止的能力会优雅降级(返回 null 或\"不可用\"\n结果,而不是抛异常):\n能力\tWeb\t说明\t连通性检测\t✅\t通过 connectivity_plus\tPing(PingMode.tcp)\t⚠️\t以 HTTPS 往返耗时度量,目标主机需下发 CORS 头\tPing(PingMode.icmp)\t❌\t抛出 UnsupportedError\tDNS(系统解析器)\t✅\t通过 DNS-over-HTTPS\tDNS(指定服务器)\t⚠️\t需要 DoH 端点,否则返回\"不支持\"\t带宽测速\t✅\tHTTP 下载 / 上传\t质量评分\t✅\t纯函数\t基准测试\t✅\t纯函数\t端口检测 / 扫描\t❌\t返回\"不可用\"结果\t原生详情(SSID、网关、MAC、VPN)\t❌\t返回 null\t\n浏览器不暴露 VPN 接口,因此即使系统开启了 VPN 或本地 HTTP 代理,Web 上的\nisVpn 仍为 false。","reading-native-data--读取原生数据#Reading native data / 读取原生数据":"final version = await ZeroNetworkKit.getPlatformVersion();\nprint(version); // e.g. 'Android 14' / 'iOS 18.0' / 'Web'\nfinal details = await ZeroNetworkKit.getNativeNetworkDetails();\nif (details != null) {\n print(details); // SSID, BSSID, gateway, MAC, VPN flag, RSSI …\n}\ndetails is always null on the web, and usually null on desktop too (see\nabove).Web 上 details 恒为 null,桌面端通常也为 null(见上文)。"}},"/Ports":{"title":"Ports / 端口检测","data":{"":"checkPort() returns a full PortCheckResult for one port; isPortOpen() is the\nplain boolean convenience; scanPorts() runs many ports concurrently with\nbounded in-flight connections.checkPort() 返回单个端口的完整 PortCheckResult;isPortOpen() 是便捷的布尔版;\nscanPorts() 以有界并发批量扫描多个端口。","single-port--单端口#Single port / 单端口":"// Boolean convenience / 便捷布尔版\nfinal open = await NetworkDiagnostic.isPortOpen(\n host: 'example.com',\n port: 443,\n timeout: const Duration(seconds: 3),\n);\nprint(open ? 'HTTPS reachable' : 'HTTPS unreachable');\n// Full result with RTT and error message / 含 RTT 与错误信息的完整结果\nfinal result = await NetworkDiagnostic.checkPort(host: 'example.com', port: 443);\nprint('open=${result.isOpen} rtt=${result.responseTimeMs} ms '\n 'err=${result.errorMessage}');","bulk-scan--批量扫描#Bulk scan / 批量扫描":"final scan = await NetworkDiagnostic.scanPorts(\n host: 'example.com',\n ports: const [22, 80, 443, 8080, 8443],\n concurrency: 8,\n);\nfor (final r in scan) {\n print('${r.host}:${r.port} '\n '${r.isOpen ? \"open\" : \"closed\"} '\n '${r.responseTimeMs.toStringAsFixed(1)} ms '\n '${r.errorMessage ?? \"\"}');\n}\nParameter\tDefault\tMeaning\tports (scanPorts)\tconfig.probePorts ([80, 443])\tPorts to probe\tconcurrency\t12\tMax in-flight connections\ttimeout\tconfig.portCheckTimeout\tTimeout per port\t\ncheckPort() returns a PortCheckResult (with isOpen, responseTimeMs\nand errorMessage); use isPortOpen() for a plain boolean. When you need the\nround-trip time or the failure reason for a single port, call checkPort()\ndirectly or pass a one-element list to scanPorts():\nscanPorts(host: 'example.com', ports: [443]).first.\ncheckPort() 返回 PortCheckResult(含 isOpen、responseTimeMs、\nerrorMessage);单端口布尔诉求请用 isPortOpen()。需要 RTT 或失败原因时可直接\n调用 checkPort(),或给 scanPorts(..., ports: [443]) 取单条结果。"}},"/Quality":{"title":"Quality Score / 质量评分","data":{"":"NetworkDiagnostic.evaluateQuality() returns a weighted 0–100 score plus a\nlevel and human-readable suggestions, computed over whatever metrics are\navailable.NetworkDiagnostic.evaluateQuality() 返回一个加权 0–100 分,附带等级与可读建议,\n它基于当前可用的各项指标计算得出。","basic--基础#Basic / 基础":"final quality = await NetworkDiagnostic.evaluateQuality(\n includePing: true,\n includeDns: true,\n includeSpeedTest: true,\n includeUpload: true,\n);\nprint('${quality.score.toStringAsFixed(1)}/100 — ${quality.level.label}');\nquality.metrics.forEach((metric, value) {\n print(' $metric = ${value.toStringAsFixed(2)}');\n});\nfor (final suggestion in quality.suggestions) {\n print('• $suggestion');\n}\nParameter\tDefault\tMeaning\tincludePing\ttrue\tSample latency / jitter / loss\tincludeDns\ttrue\tSample DNS latency\tincludeSpeedTest\ttrue\tSample download / upload (heavy)\tincludeUpload\ttrue\tInclude the upload metric\tpingHost / pingCount\tfrom config\tLatency target\tdnsDomain / dnsServers\tfrom config\tDNS target\tdownloadUrl / uploadUrl\tfrom config\tBandwidth endpoints","lightweight-variant--轻量变体#Lightweight variant / 轻量变体":"final quality = await NetworkDiagnostic.evaluateQuality(includeSpeedTest: false);","level-thresholds--等级阈值#Level thresholds / 等级阈值":"≥90 excellent · ≥75 good · ≥60 fair · ≥40 poor · otherwise bad\n(NetworkQualityLevel.fromScore).≥90 极佳 · ≥75 良好 · ≥60 一般 · ≥40 较差 · 其余为极差。","weights--权重#Weights / 权重":"Metric\tWeight\tSource\tlatency\t0.25\tPingResult.averageTime\tjitter\t0.10\tPingResult.jitter\tpacketLoss\t0.15\tPingResult.packetLoss\tdownload\t0.25\tSpeedTestResult.downloadSpeed\tupload\t0.15\tSpeedTestResult.uploadSpeed\tdns\t0.10\tmean of successful DnsTestResult.responseTimeMs\tsignalStrength\t0.10\tNetworkConnectionInfo.signalStrength","pure-function--纯函数#Pure function / 纯函数":"If you already have the metrics, skip the network entirely:如果你已有指标,可完全跳过网络:\nfinal score = NetworkQualityEvaluator.evaluate(\n latency: 42, jitter: 6, packetLoss: 0,\n download: 88.4, upload: 12.1, dns: 25, signalStrength: -55,\n targets: const QualityTargets(),\n);\nprint('${score.score.toStringAsFixed(1)} → ${score.level.label}');\nTune the ideal values globally through NetworkDiagnosticConfig(qualityTargets: ...).\n调整理想值请通过 NetworkDiagnosticConfig(qualityTargets: ...) 全局设置。"}},"/Speed-Test":{"title":"Speed Test / 测速","data":{"":"NetworkDiagnostic.runSpeedTest() measures download and upload throughput and\nsamples latency during the run, reporting progress through onProgress.NetworkDiagnostic.runSpeedTest() 测量下载与上传吞吐量,并在过程中采样延迟,通过\nonProgress 汇报进度。","basic--基础#Basic / 基础":"final speed = await NetworkDiagnostic.runSpeedTest(\n includeUpload: true,\n includePing: true,\n onProgress: (progress) {\n print('${progress.phase.name}: '\n '${progress.speedMbps.toStringAsFixed(1)} Mbps '\n '(${progress.bytes} bytes, ${progress.elapsed.inMilliseconds} ms)');\n },\n);\nprint('download : ${speed.downloadSpeed.toStringAsFixed(2)} Mbps');\nprint('upload : ${speed.uploadSpeed.toStringAsFixed(2)} Mbps');\nprint('ping : ${speed.ping.toStringAsFixed(1)} ms');\nprint('jitter : ${speed.jitter.toStringAsFixed(2)} ms');\nprint('loss : ${speed.packetLoss.toStringAsFixed(1)} %');\nprint('server : ${speed.server}');\nprint('duration : ${speed.duration.inMilliseconds} ms');\nParameter\tDefault\tMeaning\tdownloadUrl / uploadUrl\tfrom config\tEndpoints used\ttimeout\tconfig.speedTestTimeout\tPer-request timeout\tmaxDuration\tconfig.speedTestMaxDuration\tSampling window per phase\tuploadPayloadBytes\tconfig.uploadPayloadBytes\tUpload size\tpingHost / pingCount\tfrom config\tLatency sampled during the test\tincludeUpload\ttrue\tSkip the upload phase\tincludePing\ttrue\tSkip the latency sample\tonProgress\tnull\tCalled repeatedly during both phases\t\nProgress phases: SpeedTestPhase.download → SpeedTestPhase.upload →\nSpeedTestPhase.completed.进度阶段依次为 download → upload → completed。","fast-low-traffic-variant--轻量低流量变体#Fast, low-traffic variant / 轻量低流量变体":"final quick = await NetworkDiagnostic.runSpeedTest(\n includeUpload: false,\n includePing: false,\n maxDuration: const Duration(seconds: 5),\n);\nThe default endpoints point at Cloudflare's public speed service and move real\ntraffic (≈25 MB download by default). Ask for consent before running it on a\nmetered connection, and override downloadUrl / uploadUrl for production.\n默认端点指向 Cloudflare 公共服务并会产生真实流量(默认下载约 25 MB)。在按量计费\n网络上运行前请征得同意,并为生产环境替换 downloadUrl / uploadUrl。"}},"/Usage":{"title":"Usage / 用法","data":{"which-api-do-i-need--该用哪个-api#Which API do I need? / 该用哪个 API?":"I want to know…\tCall\tAm I online, and over what transport?\tNetworkDiagnostic.checkConnection()\tReact to Wi-Fi ⇄ cellular switches\tNetworkDiagnostic.onConnectivityChanged\tLatency, jitter, packet loss\tNetworkDiagnostic.ping()\tIs DNS slow or broken?\tNetworkDiagnostic.resolve()\tIs host:port reachable?\tNetworkDiagnostic.checkPort()\tWhich of these ports are open?\tNetworkDiagnostic.scanPorts()\tHow fast is down/up?\tNetworkDiagnostic.runSpeedTest()\tOne number for “good or bad”\tNetworkDiagnostic.evaluateQuality()\tEverything at once, as a report\tNetworkDiagnostic.diagnose()\tIs the diagnostics API itself expensive?\tNetworkBenchmark.runAll()\tNative version / SSID / gateway / MAC\tZeroNetworkKit.getPlatformVersion(), getNativeNetworkDetails()","global-configuration--全局配置#Global configuration / 全局配置":"NetworkDiagnosticConfig centralises every default. Override only what you\nneed; the rest keep the defaults below.NetworkDiagnosticConfig 集中了全部默认参数,只需覆盖你关心的字段。\nZeroNetworkKit.init(\n config: const NetworkDiagnosticConfig(\n pingHost: '1.1.1.1',\n pingPort: 443,\n pingCount: 5,\n dnsDomain: 'example.com',\n dnsServers: ['1.1.1.1', '8.8.8.8'],\n downloadUrl: 'https://my-cdn.example.com/speedtest.bin',\n uploadUrl: 'https://my-cdn.example.com/upload',\n ),\n);\nField\tDefault\tMeaning\tpingHost\t'1.1.1.1'\tDefault ping / port-scan target\tpingPort\t443\tPort used by the TCP ping\tpingCount\t4\tProbes per ping run\tpingTimeout\t3s\tTimeout of one probe\tpingInterval\t200ms\tDelay between probes\tdnsDomain\t'www.google.com'\tDefault domain for DNS tests\tdnsServers\t['1.1.1.1', '8.8.8.8', '114.114.114.114']\tServers queried by default\tdnsTimeout\t5s\tTimeout per DNS server\tdownloadUrl\tCloudflare __down?bytes=25000000\tDownload endpoint\tuploadUrl\tCloudflare __up\tUpload endpoint\tuploadPayloadBytes\t1048576 (1 MiB)\tUpload payload size\tspeedTestTimeout\t30s\tPer-request timeout\tspeedTestMaxDuration\t10s\tSampling window per phase\tportCheckTimeout\t3s\tTimeout of one port check\tprobePorts\t[80, 443]\tPorts used by scanPorts() by default\tqualityTargets\tconst QualityTargets()\tIdeal values for scoring\t\nThe default speed-test endpoints are Cloudflare's public service. Swap them for\nyour own before shipping production traffic.\n默认测速端点是 Cloudflare 的公共服务,正式项目请替换为自建端点。","quality-targets--质量评分理想值#Quality targets / 质量评分理想值":"const NetworkDiagnosticConfig(\n qualityTargets: QualityTargets(\n excellentLatency: 30, // ms\n acceptableLatency: 150, // ms\n excellentJitter: 5, // ms\n acceptableJitter: 40, // ms\n acceptablePacketLoss: 5, // %\n excellentDownload: 50, // Mbps\n acceptableDownload: 5, // Mbps\n excellentUpload: 20, // Mbps\n acceptableUpload: 2, // Mbps\n excellentDns: 30, // ms\n acceptableDns: 200, // ms\n ),\n);","json-serialisation--序列化#JSON serialisation / 序列化":"Every result object implements toMap() and is JSON encodable:每个结果对象都实现了 toMap(),可直接 JSON 序列化:\nimport 'dart:convert';\nfinal report = await NetworkDiagnostic.diagnose(includeSpeedTest: false);\nfinal json = jsonEncode(report.toMap());\nprint(json);\n// Round-trip a connection snapshot.\nfinal decoded = NetworkConnectionInfo.fromMap(jsonDecode(jsonEncode(\n report.connection.toMap(),\n)) as Map);\nType\tSerialisation\tNetworkConnectionInfo\ttoMap() + fromMap()\tPingResult\ttoMap()\tDnsTestResult\ttoMap()\tPortCheckResult\ttoMap()\tSpeedTestResult\ttoMap()\tNetworkQualityScore\ttoMap()\tNetworkDiagnosticReport\ttoMap()\tBenchmarkResult / BenchmarkSuiteResult\ttoMap()","dependency-injection--tests--注入与测试#Dependency injection & tests / 注入与测试":"Every service accepts its collaborators, so your integration points are\nunit-testable without touching the network.每个服务都支持注入协作对象,可以完全脱离网络做单测。\nclass FakeConnectivityAdapter implements ConnectivityAdapter {\n @override\n Future> checkConnectivity() async => ['wifi'];\n @override\n Stream> get onConnectivityChanged => const Stream.empty();\n}\nNetworkDiagnostic.configure(\n config: const NetworkDiagnosticConfig(pingHost: '127.0.0.1'),\n connectivity: ConnectivityService(adapter: FakeConnectivityAdapter()),\n ping: PingService(),\n dns: DnsService(),\n ports: PortService(),\n speedTest: SpeedTestService(client: fakeClient),\n);\n// Restore defaults afterwards.\nNetworkDiagnostic.reset();\nSwap individual services directly when you prefer:也可以直接替换单个服务:\nNetworkDiagnostic.pingService = MyFakePingService();\nNetworkDiagnostic.dnsService = MyFakeDnsService();\nFor advanced service classes, the DNS wire codec and low-level APIs, import\npackage:zero_network_kit/advanced.dart.\n进阶的服务类、DNS 报文编解码器等底层 API 请通过\npackage:zero_network_kit/advanced.dart 引入。"}},"/":{"title":"Zero Network Kit","data":{"":"A Flutter plugin for network diagnostics: connectivity inspection, latency\nprobing, DNS resolution, port checks, bandwidth measurement, quality scoring and\nmicro-benchmarks — on Android, iOS, macOS, Windows and Linux.一个 Flutter 插件,提供网络诊断能力:连通性检测、延迟探测、DNS 解析、端口\n检测、带宽测速、质量评分与微基准测试——覆盖 Android、iOS、macOS、Windows 与 Linux。","-features--功能特性#✨ Features / 功能特性":"Feature\tDescription\tZero setup\tWorks out of the box; init() only to override defaults / 开箱即用,仅需在想覆盖默认值时才 init()\tConnectivity\tTransport type, IPv4/IPv6, gateway, SSID, RSSI, MAC, VPN / 传输类型、IPv4/IPv6、网关、SSID、信号强度、MAC、VPN\tLatency\tTCP handshake RTT everywhere; system ICMP on desktop / TCP 握手 RTT 全平台可用,桌面额外支持系统 ICMP\tDNS\tSystem resolver + raw UDP against explicit servers / 系统解析器 + 针对指定服务器的原始 UDP 查询\tPorts\tBounded-concurrency TCP reachability / 有界并发的 TCP 可达性检测\tSpeed test\tDownload/upload throughput with progress callbacks / 上下行测速,带进度回调\tQuality\tWeighted 0–100 score + level + suggestions / 加权 0–100 分 + 等级 + 建议\tFull report\tOne-shot aggregate of every probe / 一次性汇总所有探测\tBenchmarks\tMeasures how fast the diagnostics API itself runs / 衡量诊断 API 自身开销\tCapabilities\tQuery-then-call; hide unsupported cards (e.g. SSID on desktop) / 先查询后调用,隐藏不支持的卡片\tGraceful degradation\tMissing permission or unreachable sub-service never throws / 缺权限或子服务不可达时只返回 null,绝不抛异常\tHermetic tests\tEvery service accepts injected collaborators / 每个服务都支持注入协作对象,便于单测","-table-of-contents--目录#📚 Table of Contents / 目录":"Page\tDescription\tGetting Started\tQuick start guide / 快速开始\tInstallation\tHow to install / 安装方式\tUsage\tGlobal config, serialisation, dependency injection / 全局配置、序列化、依赖注入\tConnectivity\tConnection snapshot & change stream / 连通性快照与变化监听\tLatency / Ping\tTCP & ICMP latency probing / 延迟探测\tDNS\tResolver comparison across servers / DNS 解析\tPorts\tSingle port & bulk scan / 端口检测\tSpeed Test\tDownload/upload throughput / 测速\tQuality Score\tWeighted scoring / 质量评分\tFull Report\tOne-shot aggregate / 汇总报告\tBenchmarks\tMicro-benchmarks / 微基准\tPlatform Support\tCapability matrix & native details / 能力矩阵与原生详情\tAPI Reference\tModel field reference & gotchas / 模型字段速查与陷阱\tFAQ\tFrequently asked questions / 常见问题","-links--链接#🔗 Links / 链接":"GitHub\nOfficial Website\npub.dev","-license--许可证#📄 License / 许可证":"This project is licensed under the MPL-2.0.本项目采用 MPL-2.0 许可证。This plugin is provided \"as is\", without warranty of any kind. The author assumes\nno responsibility or liability for the functionality, security, or any\nconsequences arising from the use of modified versions or derivative projects.本插件按\"原样\"提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及\n任何使用后果承担责任。"}}} \ No newline at end of file +{"/API-Reference":{"title":"API Reference / 接口参考","data":{"":"A condensed field reference for every public model. For copy-paste examples of\neach method, see the capability pages.这里是每个公开模型的字段速查。每个方法的复制即用示例请见各能力页面。","networktype#NetworkType":"Member\tNotes\tvalues\tnone, wifi, mobile, ethernet, vpn, bluetooth, other\tlabel\tHuman readable: 'Wi-Fi', 'Mobile', …\tid\tStable English identifier (name), safe to persist\tisConnected\ttrue unless none\tNetworkType.fromRaw(Object?)\tParses a raw platform string","networkconnectioninfo#NetworkConnectionInfo":"isConnected, type, ssid, signalStrength (dBm), ipAddress,\nipv6Address, gateway, macAddress, isVpn, isReachable, timestamp,\nplus copyWith({bool? isReachable}).","pingresult#PingResult":"Fields host, port, mode, sent, received, times, timestamp;\nderived getters lost, packetLoss (%), minTime, maxTime, averageTime,\njitter, isSuccess.","dnstestresult#DnsTestResult":"Fields server, domain, isSuccess, responseTime, resolvedIps,\nerrorMessage, timestamp; derived responseTimeMs, primaryAddress.","portcheckresult#PortCheckResult":"Fields host, port, isOpen, responseTime, errorMessage, timestamp;\nderived responseTimeMs.","speedtestresult#SpeedTestResult":"Fields downloadSpeed, uploadSpeed (Mbps), ping (ms), jitter (ms),\npacketLoss (%), downloadedBytes, uploadedBytes, downloadDuration,\nuploadDuration, server, timestamp; derived duration; static helper\nSpeedTestResult.mbpsFromBytes(bytes, elapsed).","speedtestprogress--speedtestphase#SpeedTestProgress / SpeedTestPhase":"phase (download, upload, completed), bytes, elapsed, speedMbps.","networkqualityscore--networkqualitylevel#NetworkQualityScore / NetworkQualityLevel":"score (0–100), level, metrics (Map), suggestions,\ntimestamp; NetworkQualityLevel.fromScore(double), level.label.","networkdiagnosticreport#NetworkDiagnosticReport":"connection, ping, dnsResults, portResults, speedTest, quality,\ntimestamp.","benchmarkresult--benchmarksuiteresult#BenchmarkResult / BenchmarkSuiteResult":"BenchmarkResult: testName, iterations, totalDuration,\naverageDuration, minDuration, maxDuration, standardDeviation,\noperationsPerSecond, failures, timestamp.\nBenchmarkSuiteResult: suiteName, results, totalDuration, timestamp,\nand suite['name'].","gotchas--常见陷阱#Gotchas / 常见陷阱":"type.label, not displayName. NetworkType exposes label / id.\ncheckPort() returns PortCheckResult. Use isPortOpen() for a plain\nboolean, or scanPorts(..., ports: [p]).first for RTT / error message.\nincludeSystemResolver defaults to false. Pass true to add the\nsystem DNS row.\nA TCP ping needs a listening port. Probing host with port: 443 fails\nif that host does not accept TCP/443 — that is \"filtered\", not \"offline\". Use\nmode: PingMode.icmp on desktop to get closer to real ICMP.\nPingMode.icmp is desktop-only and silently degrades when the ping\nbinary is unavailable.\nThe speed test moves real traffic (≈25 MB download by default). Ask for\nconsent before running it on a metered connection.\nMissing permissions degrade, they never throw. A missing location\npermission yields ssid == null; the rest of the snapshot still arrives.\nincludePorts is false by default in diagnose().\ninit() is optional, but dispose() only closes the client the plugin\nowns — an httpClient you injected stays open, so close it yourself.\nFull source-level API docs live in the repo:\nAPI.md\n(English) and\nAPI_zh.md\n(简体中文).\n完整源码级 API 文档见仓库内的\nAPI.md\n与 API_zh.md。"}},"/Benchmark":{"title":"Benchmarks / 微基准测试","data":{"":"NetworkBenchmark measures how fast the diagnostics API itself runs — useful\nwhen deciding whether a diagnostic belongs on your startup path.NetworkBenchmark 衡量诊断 API 自身的开销——可用在判断某项诊断能否放在启动路径上。","all-at-once--一次性全跑#All at once / 一次性全跑":"final suite = await NetworkBenchmark.runAll(\n iterations: 20,\n warmupIterations: 3,\n host: '1.1.1.1',\n dnsDomain: 'example.com',\n port: 443,\n);\nfor (final result in suite.results) {\n print('${result.testName.padRight(12)} '\n 'avg ${(result.averageDuration.inMicroseconds / 1000).toStringAsFixed(2)} ms '\n '± ${(result.standardDeviation.inMicroseconds / 1000).toStringAsFixed(2)} ms '\n '${result.operationsPerSecond.toStringAsFixed(1)} ops/s '\n 'failures=${result.failures}');\n}\n// Look up one benchmark by name / 按名称查单个结果\nprint(suite['ping']?.averageDuration);\nprint(suite.totalDuration);","individual-suites--单个基准#Individual suites / 单个基准":"await NetworkBenchmark.benchmarkConnection(iterations: 20);\nawait NetworkBenchmark.benchmarkPing(iterations: 20, host: '1.1.1.1');\nawait NetworkBenchmark.benchmarkDns(iterations: 20, domain: 'example.com');\nawait NetworkBenchmark.benchmarkPortCheck(iterations: 20, port: 443);\nawait NetworkBenchmark.benchmarkPlatformChannel(iterations: 20);","result-fields--结果字段#Result fields / 结果字段":"BenchmarkResult: testName, iterations, totalDuration, averageDuration,\nminDuration, maxDuration, standardDeviation, operationsPerSecond,\nfailures, timestamp.BenchmarkSuiteResult: suiteName, results, totalDuration, timestamp, and\nsuite['name']."}},"/Connectivity":{"title":"Connectivity / 连通性","data":{"":"NetworkDiagnostic.checkConnection() captures a single snapshot of the current\nnetwork; onConnectivityChanged emits a fresh snapshot on every transport\nchange.NetworkDiagnostic.checkConnection() 捕获当前网络的一次快照;onConnectivityChanged\n在每次传输方式变化时推送新快照。","snapshot--快照#Snapshot / 快照":"final connection = await NetworkDiagnostic.checkConnection();\nprint('type : ${connection.type.label}'); // Wi-Fi / Mobile / Ethernet …\nprint('connected : ${connection.isConnected}');\nprint('ipv4 : ${connection.ipAddress}');\nprint('ipv6 : ${connection.ipv6Address}');\nprint('gateway : ${connection.gateway}');\nprint('ssid : ${connection.ssid}');\nprint('rssi : ${connection.signalStrength} dBm');\nprint('mac : ${connection.macAddress}');\nprint('vpn : ${connection.isVpn}');\nprint('timestamp : ${connection.timestamp}');\nField\tNotes\ttype\tNetworkType (none, wifi, mobile, ethernet, vpn, bluetooth, other)\tisConnected\ttrue unless type is none\tipAddress / ipv6Address\tLocal IPv4 / IPv6 (Dart NetworkInterface)\tgateway / macAddress\tNative; null on desktop except Windows\tssid / signalStrength\tNative; always null on desktop\tisVpn\tNative VPN detection\tisReachable\tOnly filled when probeReachability: true\ttimestamp\tWhen the snapshot was taken\t\nUse connection.type.label / connection.type.id. There is no\ndisplayName.\n请使用 connection.type.label / connection.type.id,没有 displayName。","parameters--参数#Parameters / 参数":"Parameter\tDefault\tMeaning\tincludeNativeDetails\ttrue\tAlso read SSID / gateway / MAC / VPN from the native side\tprobeReachability\tfalse\tMake a real request and fill isReachable\tprobeTimeout\t3s\tTimeout of that reachability probe\t\n// Skip the native channel (cheapest call — good on hot paths).\nfinal quick = await NetworkDiagnostic.checkConnection(includeNativeDetails: false);\n// Prove the internet is actually reachable, not just that a NIC is up.\nfinal verified = await NetworkDiagnostic.checkConnection(\n probeReachability: true,\n probeTimeout: const Duration(seconds: 5),\n);\nif (verified.isReachable == false) {\n print('Interface is up but the internet is unreachable.');\n}","change-stream--变化监听#Change stream / 变化监听":"final subscription = NetworkDiagnostic.onConnectivityChanged.listen(\n (info) => print('now on ${info.type.id} · ${info.ipAddress}'),\n);\n// Later:\nawait subscription.cancel();\nIn a widget:\nStreamBuilder(\n stream: NetworkDiagnostic.onConnectivityChanged,\n builder: (context, snapshot) {\n final info = snapshot.data;\n if (info == null) return const Text('Checking…');\n return Text('${info.type.label} · ${info.isConnected}');\n },\n)","connectivity-only-minimal-example--仅连通性最小示例#Connectivity-only: minimal example / 仅连通性:最小示例":"If all you need is \"am I online, on what transport, what IP, and react to\nchanges\", you only need this page — no init() required.如果只需要「在不在線、走什么網络、IP 是多少、切换时通知」,只看本页即可——\n无需 init()。\nclass ConnectivityScreen extends StatelessWidget {\n const ConnectivityScreen({super.key});\n @override\n Widget build(BuildContext context) {\n return StreamBuilder(\n stream: NetworkDiagnostic.onConnectivityChanged,\n builder: (context, snap) {\n final c = snap.data;\n if (c == null) {\n return const Center(child: CircularProgressIndicator());\n }\n return ListView(\n children: [\n ListTile(title: const Text('Type / 类型'), trailing: Text(c.type.label)),\n ListTile(title: const Text('Online / 在线'), trailing: Text('${c.isConnected}')),\n ListTile(title: const Text('IPv4'), trailing: Text(c.ipAddress ?? '—')),\n ListTile(title: const Text('IPv6'), trailing: Text(c.ipv6Address ?? '—')),\n ],\n );\n },\n );\n }\n}"}},"/DNS":{"title":"DNS / DNS 解析","data":{"":"NetworkDiagnostic.resolve() tests DNS resolution across multiple servers in\nparallel, so you can spot the fastest one. Each result is either the system\nresolver or a raw UDP query against an explicit server.NetworkDiagnostic.resolve() 并行测试多台 DNS 服务器的解析表现,从而挑出最快的一台。\n每条结果要么来自系统解析器,要么是对指定服务器的原始 UDP 查询。","basic--基础#Basic / 基础":"final results = await NetworkDiagnostic.resolve(\n domain: 'example.com',\n dnsServers: const ['1.1.1.1', '8.8.8.8', '114.114.114.114'],\n timeout: const Duration(seconds: 5),\n concurrent: true,\n includeSystemResolver: true,\n);\nfor (final r in results) {\n if (r.isSuccess) {\n print('${r.server.padRight(16)} → ${r.resolvedIps.join(\", \")} '\n '(${r.responseTimeMs.toStringAsFixed(1)} ms)');\n } else {\n print('${r.server.padRight(16)} ✗ ${r.errorMessage}');\n }\n}\nParameter\tDefault\tMeaning\tdomain\tconfig.dnsDomain\tDomain to resolve\tdnsServers\tconfig.dnsServers\tServers queried in parallel (or in series)\ttimeout\tconfig.dnsTimeout\tTimeout of one query\tconcurrent\ttrue\tQuery all servers at once\tincludeSystemResolver\tfalse\tAlso add a system row\t\nNotes / 说明:\nA row with server == 'system' comes from the OS resolver; the others are raw\nUDP queries against the listed IPs, encoded by the built-in DnsPacket wire\ncodec.\nserver == 'system' 的行来自系统解析器,其余是对指定 IP 的原始 UDP 查询,由内置\nDnsPacket 编解码。\nincludeSystemResolver defaults to false at the facade level — pass\ntrue to include it.\n门面层默认不包含系统解析器,需显式传 true。","pick-the-fastest--挑出最快的#Pick the fastest / 挑出最快的":"final fastest = results\n .where((r) => r.isSuccess)\n .reduce((a, b) => a.responseTimeMs <= b.responseTimeMs ? a : b);\nprint('fastest resolver: ${fastest.server}');"}},"/FAQ":{"title":"FAQ / 常见问题","data":{"do-i-need-to-call-init--需要调用-init-吗#Do I need to call init()? / 需要调用 init() 吗?":"No. Every API falls back to built-in defaults. Call ZeroNetworkKit.init() only\nwhen you want to override defaults (e.g. a custom reachability host or DNS\nservers). Connectivity alone works with zero setup.不需要。每个 API 都走内置默认值。只有在想覆盖默认参数(如自定义可达性主机或 DNS\n服务器)时才调用 ZeroNetworkKit.init()。单用连通性完全零配置。","which-platforms-are-supported--支持哪些平台#Which platforms are supported? / 支持哪些平台?":"Android, iOS, macOS, Windows, Linux and Web (partial — see\nPlatform Support).支持 Android、iOS、macOS、Windows、Linux 与 Web(部分支持)。详见\nPlatform Support。","why-is-ssid--signalstrength-null-on-desktop--为什么桌面上的-ssid信号强度是-null#Why is ssid / signalStrength null on desktop? / 为什么桌面上的 ssid/信号强度是 null?":"Desktop uses tier A: SSID and RSSI require system APIs that aren't exposed on\nmacOS/Linux/Windows the same way. They are always null on desktop; Windows\nadditionally provides gateway / MAC / DNS / VPN via GetAdaptersAddresses.桌面采用 A 档:SSID 与 RSSI 依赖系统 API,在桌面上不以同样方式暴露,因此恒为 null;\nWindows 额外通过 GetAdaptersAddresses 提供网关 / MAC / DNS / VPN。","why-does-getnativenetworkdetails-return-null--为什么-getnativenetworkdetails-返回-null#Why does getNativeNetworkDetails() return null? / 为什么 getNativeNetworkDetails 返回 null?":"Either the platform has no such data (e.g. desktop tier A), or the required\npermission is missing (e.g. location for Wi-Fi SSID on Android/iOS). The call\nnever throws — it degrades gracefully.要么是平台没有该数据(如桌面 A 档),要么是缺权限(如 Android/iOS 上读取 Wi-Fi SSID\n需定位权限)。该调用绝不抛异常,会优雅降级。","does-the-speed-test-use-my-bandwidth--测速会消耗我的流量吗#Does the speed test use my bandwidth? / 测速会消耗我的流量吗?":"Yes — the default endpoints are Cloudflare's public service and download ≈25 MB\nby default. Ask for consent on metered connections and override downloadUrl /\nuploadUrl for production.会——默认端点指向 Cloudflare 公共服务,默认下载约 25 MB。在按量计费网络上请征得同意,\n生产环境请替换 downloadUrl / uploadUrl。","how-do-i-test-my-integration-without-the-network--如何脱离网络测试我的集成#How do I test my integration without the network? / 如何脱离网络测试我的集成?":"Every service is injectable. Implement ConnectivityAdapter (or pass fake\nservice instances) and call NetworkDiagnostic.configure(...), then\nNetworkDiagnostic.reset() to restore defaults. See\nUsage → Dependency injection.每个服务都可注入。实现 ConnectivityAdapter(或直接传入假服务实例)后调用\nNetworkDiagnostic.configure(...),再用 NetworkDiagnostic.reset() 恢复默认。详见\n用法 → 依赖注入。","how-is-this-licensed--采用什么许可证#How is this licensed? / 采用什么许可证?":"MPL-2.0. See the repository LICENSE.采用 MPL-2.0。详见仓库 LICENSE。"}},"/Full-Report":{"title":"Full Report / 汇总报告","data":{"":"NetworkDiagnostic.diagnose() runs every probe you ask for and aggregates them\ninto one NetworkDiagnosticReport. A failing sub-test never aborts the run — its\nfield simply stays empty/null.NetworkDiagnostic.diagnose() 运行你指定的各项探测,并汇总为一份\nNetworkDiagnosticReport。任何子项失败都不会中断整体流程,对应字段保持空。","basic--基础#Basic / 基础":"final report = await NetworkDiagnostic.diagnose(\n includePing: true,\n includeDns: true,\n includePorts: true,\n includeSpeedTest: true,\n includeUpload: true,\n host: '1.1.1.1',\n dnsDomain: 'example.com',\n dnsServers: const ['1.1.1.1', '8.8.8.8'],\n ports: const [80, 443],\n);\nprint(report); // one-line summary\nprint(report.connection.type.label);\nprint(report.ping?.averageTime);\nprint(report.dnsResults.length);\nprint(report.portResults.where((p) => p.isOpen).length);\nprint(report.speedTest?.downloadSpeed);\nprint(report.quality.level.label);\nParameter\tDefault\tMeaning\tincludePing\ttrue\tSample latency / jitter / loss\tincludeDns\ttrue\tSample DNS latency\tincludePorts\tfalse\tScan ports (off by default)\tincludeSpeedTest\ttrue\tSample download / upload\tincludeUpload\ttrue\tInclude the upload metric\thost\tconfig.pingHost\tLatency / port target\tdnsDomain / dnsServers\tfrom config\tDNS target\tports\tconfig.probePorts\tPorts scanned when includePorts is true\t\nincludePorts defaults to false, so a default run does not scan ports.\nincludePorts 默认 false,默认运行不会扫描端口。\nThe report is JSON-encodable via report.toMap().\n报告可通过 report.toMap() 序列化为 JSON。"}},"/Getting-Started":{"title":"Getting Started / 快速开始","data":{"quick-start--快速开始#Quick Start / 快速开始":"Import the package and start diagnosing — no init() required:导入包即可开始诊断,无需 init():\nimport 'package:flutter/material.dart';\nimport 'package:zero_network_kit/zero_network_kit.dart';\nvoid main() {\n // Optional: apply your own global defaults once.\n // 可选:全局定制一次默认参数。\n ZeroNetworkKit.init(\n config: const NetworkDiagnosticConfig(pingHost: '1.1.1.1'),\n );\n runApp(const MyApp());\n}\nIf you only need connectivity, you don't even need init():如果只需要连通性,连 init() 都不需要:\nfinal connection = await NetworkDiagnostic.checkConnection();\nprint('${connection.type.label} · ${connection.ipAddress}');","what-you-get--你能得到什么#What you get / 你能得到什么":"Every method is a static, non-blocking call on the NetworkDiagnostic facade.\nThe plugin is pure Dart under the hood, so it is fully injectable and testable\nwithout touching the network.每个方法都是 NetworkDiagnostic 门面上的静态、非阻塞调用。插件内核是纯 Dart,\n因此完全可注入、可脱离网络做测试。\nNeed\tCall\tAm I online, on what transport?\tNetworkDiagnostic.checkConnection()\tReact to Wi-Fi ⇄ cellular switches\tNetworkDiagnostic.onConnectivityChanged\tLatency, jitter, packet loss\tNetworkDiagnostic.ping()\tIs DNS slow or broken?\tNetworkDiagnostic.resolve()\tIs host:port reachable?\tNetworkDiagnostic.checkPort() / isPortOpen()\tHow fast is down/up?\tNetworkDiagnostic.runSpeedTest()\tOne number for \"good or bad\"\tNetworkDiagnostic.evaluateQuality()\tEverything at once\tNetworkDiagnostic.diagnose()\tNative version / SSID / gateway / MAC\tZeroNetworkKit.getNativeNetworkDetails()\t\nSee Which API do I need? for the full map.","lifecycle--生命周期#Lifecycle / 生命周期":"ZeroNetworkKit.isInitialized; // false until init() runs\nZeroNetworkKit.config; // the effective NetworkDiagnosticConfig\nawait ZeroNetworkKit.dispose(); // releases the HTTP client the plugin owns\ndispose() only closes the HTTP client the plugin created itself. An\nhttp.Client you injected stays open — close it yourself.dispose() 只会关闭插件自己创建的 HTTP 客户端;你注入的 http.Client 不会被关闭。","next-steps--下一步#Next Steps / 下一步":"Installation — Detailed installation methods / 详细安装方式\nUsage — Global config & dependency injection / 全局配置与依赖注入\nPlatform Support — Capability matrix / 能力矩阵"}},"/Installation":{"title":"Installation / 安装","data":{"from-pubdev-recommended--从-pubdev-安装推荐#From pub.dev (Recommended) / 从 pub.dev 安装(推荐)":"Add the following to your pubspec.yaml:在 pubspec.yaml 中添加以下依赖:\ndependencies:\n zero_network_kit: ^1.0.5\nThen run:然后运行:\nflutter pub get","from-github--从-github-安装#From GitHub / 从 GitHub 安装":"Alternatively, install from GitHub:或者从 GitHub 安装:\ndependencies:\n zero_network_kit:\n git:\n url: https://github.com/zero-labsco/zero_network_kit.git\n ref: release/v1.0.5","platform-setup--平台配置#Platform Setup / 平台配置":"","android#Android":"The plugin manifest already declares INTERNET, ACCESS_NETWORK_STATE and\nACCESS_WIFI_STATE. Reading the Wi-Fi SSID additionally requires the\nlocation permission (ACCESS_FINE_LOCATION) on Android 8.1+, otherwise the\nsnapshot reports ssid: null.插件清单已声明 INTERNET、ACCESS_NETWORK_STATE 与 ACCESS_WIFI_STATE。读取 Wi-Fi\nSSID 还需在 Android 8.1+ 申请定位权限(ACCESS_FINE_LOCATION),否则快照中\nssid 为 null。","ios#iOS":"No additional configuration needed. Reading the Wi-Fi SSID additionally\nrequires the Access WiFi Information capability plus location authorisation.无需额外配置。读取 Wi-Fi SSID 还需开启 Access WiFi Information 能力并授权定位。","macos--windows--linux#macOS / Windows / Linux":"The plugin is declared on all three desktop platforms. On desktop, native\ndetails such as SSID / RSSI are always null (tier A). Windows additionally\nprovides gateway / MAC / DNS / VPN through GetAdaptersAddresses; macOS and\nLinux report only the Dart-side IP/IPv6. See\nPlatform Support for the full matrix.插件已在三个桌面平台声明。桌面上 SSID / 信号强度等原生详情恒为 null(A 档)。\nWindows 额外通过 GetAdaptersAddresses 提供网关 / MAC / DNS / VPN;macOS 与 Linux\n仅提供 Dart 侧的 IP/IPv6。完整矩阵见平台支持。","import--导入#Import / 导入":"import 'package:zero_network_kit/zero_network_kit.dart';","requirements--环境要求#Requirements / 环境要求":"Requirement\tVersion\tFlutter\t>= 3.3.0\tDart SDK\t>= 3.11.0 < 4.0.0","next-steps--下一步#Next Steps / 下一步":"Getting Started — Quick start guide / 快速开始\nUsage — Full usage guide / 完整使用指南"}},"/Ping":{"title":"Latency / Ping / 延迟探测","data":{"":"NetworkDiagnostic.ping() measures round-trip time towards a target host and\nreports sent / received counts, packet loss, min/avg/max and jitter.NetworkDiagnostic.ping() 测量到目标主机的往返时间,并给出发送/接收次数、丢包率、\n最小/平均/最大耗时与抖动。","basic--基础#Basic / 基础":"final ping = await NetworkDiagnostic.ping(\n host: '1.1.1.1',\n count: 5,\n timeout: const Duration(seconds: 2),\n interval: const Duration(milliseconds: 200),\n port: 443,\n);\nprint('received : ${ping.received}/${ping.sent}');\nprint('loss : ${ping.packetLoss.toStringAsFixed(1)} %');\nprint('min/avg/max: ${ping.minTime.toStringAsFixed(1)} / '\n '${ping.averageTime.toStringAsFixed(1)} / '\n '${ping.maxTime.toStringAsFixed(1)} ms');\nprint('jitter : ${ping.jitter.toStringAsFixed(2)} ms');\nprint('samples : ${ping.times}');\nParameter\tDefault\tMeaning\thost\tconfig.pingHost\tTarget host\tcount\tconfig.pingCount\tNumber of probes\ttimeout\tconfig.pingTimeout\tPer-probe timeout\tinterval\tconfig.pingInterval\tDelay between probes\tport\tconfig.pingPort\tTCP port probed in PingMode.tcp\tmode\tPingMode.tcp\tPingMode.tcp or PingMode.icmp","tcp-vs-icmp--tcp-与-icmp#TCP vs ICMP / TCP 与 ICMP":"PingMode.tcp performs a TCP handshake to host:port — the portable equivalent\nof ICMP and the only mode available on Android/iOS.PingMode.tcp 通过 TCP 握手到 host:port 测量往返,是移动端唯一可用的模式。\n// Desktop only — falls back gracefully if the `ping` binary is unavailable.\nawait NetworkDiagnostic.ping(host: '1.1.1.1', count: 4, mode: PingMode.icmp);\nping.isSuccess is true when at least one probe answered; check it before\ntrusting the averages.至少一次成功响应时 isSuccess 为 true,读平均值前建议先判断它。\nA TCP ping needs a listening port. Probing host with port: 443 fails if\nthat host does not accept TCP/443 — that is \"filtered\", not \"offline\". Use\nmode: PingMode.icmp on desktop to get closer to real ICMP.\nTCP 探测需要一个在监听的端口。对不接受 TCP/443 的主机探测 port: 443 会失败——\n那是“被过滤”,不是“离线”。在桌面上用 PingMode.icmp 更接近真实 ICMP。"}},"/Platform-Support":{"title":"Platform Support / 平台支持","data":{"":"zero_network_kit is declared on six plugin platforms: Android, iOS,\nmacOS, Windows, Linux and Web. Web support is partial: the services that\ndepend on dart:io are swapped for browser-safe equivalents, and the\ncapabilities the sandbox forbids degrade gracefully instead of failing.zero_network_kit 在 六 个插件平台上声明:Android、iOS、macOS、Windows、Linux\n与 Web。Web 为部分支持:依赖 dart:io 的服务已替换为浏览器安全的等价实现,\n浏览器沙箱禁止的能力会优雅降级而不会失败。","capability-matrix--能力矩阵#Capability matrix / 能力矩阵":"Capability\tAndroid / iOS\tDesktop (macOS / Windows / Linux)\tWeb\tConnectivity (connectivity_plus)\t✅\t✅\t✅\tLocal IP / IPv6 (NetworkInterface)\t✅\t✅\t❌\tNative details (SSID / gateway / MAC / VPN)\t✅\t⚠️ see below\t❌\tTCP ping\t✅\t✅\t⚠️ HTTPS round trip\tICMP ping (Process.run('ping'))\t❌\t✅\t❌\tHTTP ping\t✅\t✅\t✅\tDNS system resolver\t✅\t✅\t✅ DoH\tDNS raw UDP\t✅\t✅\t⚠️ DoH endpoint required\tPort check / scan\t✅\t✅\t❌\tSpeed test\t✅\t✅\t✅\tQuality score\t✅\t✅\t✅\tBenchmarks\t✅\t✅\t✅","networkcapabilities--能力查询#NetworkCapabilities / 能力查询":"Query the host platform before calling, so your UI can hide unsupported cards:在调用前查询当前平台能力,UI 即可据此隐藏不支持的卡片:\nfinal caps = NetworkDiagnostic.capabilities;\nprint(caps.platform); // 'android' | 'ios' | 'macos' | 'windows' | 'linux' | 'web'\nprint(caps.supports(NetworkCapability.nativeDetails)); // mobile: true, desktop: false\nprint(caps.supports(NetworkCapability.icmpPing)); // desktop: true, mobile: false\nRules baked into NetworkCapabilities.current():\nnativeDetails is mobile-only (SSID / gateway / MAC / VPN need system APIs).\nicmpPing is desktop-only (uses the system ping binary).\nOn web the supported set narrows to connectivity, tcpPing,\ndnsSystem, speedTest, quality and benchmark; every other capability is\nabsent.\nNetworkCapabilities.current() 内置规则:\nnativeDetails 仅移动端(SSID / 网关 / MAC / VPN 需要系统 API)。\nicmpPing 仅桌面(使用系统 ping 命令)。\nWeb 上的支持集合收缩为 connectivity、tcpPing、dnsSystem、speedTest、\nquality 与 benchmark,其余能力均不存在。","native-details-on-desktop--桌面原生详情#Native details on desktop / 桌面原生详情":"Desktop uses tier A by default: the native layer only reports the platform\nversion and an (often empty) details map. IP/IPv6 come from Dart\nNetworkInterface. As a result:桌面默认采用 A 档:原生层只报告平台版本与(通常为空的)详情 map,IP/IPv6 由 Dart\nNetworkInterface 兜底。因此:\nSSID / signal strength are always null on desktop.\n桌面上的 SSID / 信号强度恒为 null。\ngateway / macAddress / isVpn are null on macOS and Linux.\nmacOS 与 Linux 上 gateway / macAddress / isVpn 为 null。\nWindows additionally implements GetAdaptersAddresses, so it reports\ngateway / macAddress / DNS / isVpn (SSID still null). This is a bonus\ntier-B fragment kept as-is.\nWindows 额外实现了 GetAdaptersAddresses,因此上报 gateway / macAddress /\nDNS / isVpn(SSID 仍为 null)。这是保留的 B 档赠品。\nWhatever the platform, a missing permission or an unreachable native call never\nthrows — the field simply stays null and the rest of the result still arrives.无论在哪个平台,缺权限或原生调用不可达都不会抛异常——对应字段保持 null,其余\n结果照常返回。","web-support--web-支持#Web support / Web 支持":"The web build exposes the same static API. Capabilities that the browser sandbox\nforbids degrade gracefully — they return null or an \"unavailable\" result\ninstead of throwing:\nCapability\tWeb\tNotes\tConnectivity\t✅\tvia connectivity_plus\tPing (PingMode.tcp)\t⚠️\tmeasured as an HTTPS round trip; the target must send CORS headers\tPing (PingMode.icmp)\t❌\tthrows UnsupportedError\tDNS (system resolver)\t✅\tvia DNS-over-HTTPS\tDNS (explicit server)\t⚠️\tneeds a DoH endpoint, otherwise \"unsupported\"\tSpeed test\t✅\tHTTP download / upload\tQuality score\t✅\tpure function\tBenchmarks\t✅\tpure function\tPort check / scan\t❌\treturns \"unavailable\" results\tNative details (SSID, gateway, MAC, VPN)\t❌\tnull\t\nBrowsers expose no VPN API, so isVpn stays false on the web even when a\nsystem VPN or a local HTTP proxy is active.Web 构建提供同样的静态 API;浏览器沙箱禁止的能力会优雅降级(返回 null 或\"不可用\"\n结果,而不是抛异常):\n能力\tWeb\t说明\t连通性检测\t✅\t通过 connectivity_plus\tPing(PingMode.tcp)\t⚠️\t以 HTTPS 往返耗时度量,目标主机需下发 CORS 头\tPing(PingMode.icmp)\t❌\t抛出 UnsupportedError\tDNS(系统解析器)\t✅\t通过 DNS-over-HTTPS\tDNS(指定服务器)\t⚠️\t需要 DoH 端点,否则返回\"不支持\"\t带宽测速\t✅\tHTTP 下载 / 上传\t质量评分\t✅\t纯函数\t基准测试\t✅\t纯函数\t端口检测 / 扫描\t❌\t返回\"不可用\"结果\t原生详情(SSID、网关、MAC、VPN)\t❌\t返回 null\t\n浏览器不暴露 VPN 接口,因此即使系统开启了 VPN 或本地 HTTP 代理,Web 上的\nisVpn 仍为 false。","reading-native-data--读取原生数据#Reading native data / 读取原生数据":"final version = await ZeroNetworkKit.getPlatformVersion();\nprint(version); // e.g. 'Android 14' / 'iOS 18.0' / 'Web'\nfinal details = await ZeroNetworkKit.getNativeNetworkDetails();\nif (details != null) {\n print(details); // SSID, BSSID, gateway, MAC, VPN flag, RSSI …\n}\ndetails is always null on the web, and usually null on desktop too (see\nabove).Web 上 details 恒为 null,桌面端通常也为 null(见上文)。"}},"/Ports":{"title":"Ports / 端口检测","data":{"":"checkPort() returns a full PortCheckResult for one port; isPortOpen() is the\nplain boolean convenience; scanPorts() runs many ports concurrently with\nbounded in-flight connections.checkPort() 返回单个端口的完整 PortCheckResult;isPortOpen() 是便捷的布尔版;\nscanPorts() 以有界并发批量扫描多个端口。","single-port--单端口#Single port / 单端口":"// Boolean convenience / 便捷布尔版\nfinal open = await NetworkDiagnostic.isPortOpen(\n host: 'example.com',\n port: 443,\n timeout: const Duration(seconds: 3),\n);\nprint(open ? 'HTTPS reachable' : 'HTTPS unreachable');\n// Full result with RTT and error message / 含 RTT 与错误信息的完整结果\nfinal result = await NetworkDiagnostic.checkPort(host: 'example.com', port: 443);\nprint('open=${result.isOpen} rtt=${result.responseTimeMs} ms '\n 'err=${result.errorMessage}');","bulk-scan--批量扫描#Bulk scan / 批量扫描":"final scan = await NetworkDiagnostic.scanPorts(\n host: 'example.com',\n ports: const [22, 80, 443, 8080, 8443],\n concurrency: 8,\n);\nfor (final r in scan) {\n print('${r.host}:${r.port} '\n '${r.isOpen ? \"open\" : \"closed\"} '\n '${r.responseTimeMs.toStringAsFixed(1)} ms '\n '${r.errorMessage ?? \"\"}');\n}\nParameter\tDefault\tMeaning\tports (scanPorts)\tconfig.probePorts ([80, 443])\tPorts to probe\tconcurrency\t12\tMax in-flight connections\ttimeout\tconfig.portCheckTimeout\tTimeout per port\t\ncheckPort() returns a PortCheckResult (with isOpen, responseTimeMs\nand errorMessage); use isPortOpen() for a plain boolean. When you need the\nround-trip time or the failure reason for a single port, call checkPort()\ndirectly or pass a one-element list to scanPorts():\nscanPorts(host: 'example.com', ports: [443]).first.\ncheckPort() 返回 PortCheckResult(含 isOpen、responseTimeMs、\nerrorMessage);单端口布尔诉求请用 isPortOpen()。需要 RTT 或失败原因时可直接\n调用 checkPort(),或给 scanPorts(..., ports: [443]) 取单条结果。"}},"/Speed-Test":{"title":"Speed Test / 测速","data":{"":"NetworkDiagnostic.runSpeedTest() measures download and upload throughput and\nsamples latency during the run, reporting progress through onProgress.NetworkDiagnostic.runSpeedTest() 测量下载与上传吞吐量,并在过程中采样延迟,通过\nonProgress 汇报进度。","basic--基础#Basic / 基础":"final speed = await NetworkDiagnostic.runSpeedTest(\n includeUpload: true,\n includePing: true,\n onProgress: (progress) {\n print('${progress.phase.name}: '\n '${progress.speedMbps.toStringAsFixed(1)} Mbps '\n '(${progress.bytes} bytes, ${progress.elapsed.inMilliseconds} ms)');\n },\n);\nprint('download : ${speed.downloadSpeed.toStringAsFixed(2)} Mbps');\nprint('upload : ${speed.uploadSpeed.toStringAsFixed(2)} Mbps');\nprint('ping : ${speed.ping.toStringAsFixed(1)} ms');\nprint('jitter : ${speed.jitter.toStringAsFixed(2)} ms');\nprint('loss : ${speed.packetLoss.toStringAsFixed(1)} %');\nprint('server : ${speed.server}');\nprint('duration : ${speed.duration.inMilliseconds} ms');\nParameter\tDefault\tMeaning\tdownloadUrl / uploadUrl\tfrom config\tEndpoints used\ttimeout\tconfig.speedTestTimeout\tPer-request timeout\tmaxDuration\tconfig.speedTestMaxDuration\tSampling window per phase\tuploadPayloadBytes\tconfig.uploadPayloadBytes\tUpload size\tpingHost / pingCount\tfrom config\tLatency sampled during the test\tincludeUpload\ttrue\tSkip the upload phase\tincludePing\ttrue\tSkip the latency sample\tonProgress\tnull\tCalled repeatedly during both phases\t\nProgress phases: SpeedTestPhase.download → SpeedTestPhase.upload →\nSpeedTestPhase.completed.进度阶段依次为 download → upload → completed。","fast-low-traffic-variant--轻量低流量变体#Fast, low-traffic variant / 轻量低流量变体":"final quick = await NetworkDiagnostic.runSpeedTest(\n includeUpload: false,\n includePing: false,\n maxDuration: const Duration(seconds: 5),\n);\nThe default endpoints point at Cloudflare's public speed service and move real\ntraffic (≈25 MB download by default). Ask for consent before running it on a\nmetered connection, and override downloadUrl / uploadUrl for production.\n默认端点指向 Cloudflare 公共服务并会产生真实流量(默认下载约 25 MB)。在按量计费\n网络上运行前请征得同意,并为生产环境替换 downloadUrl / uploadUrl。"}},"/Quality":{"title":"Quality Score / 质量评分","data":{"":"NetworkDiagnostic.evaluateQuality() returns a weighted 0–100 score plus a\nlevel and human-readable suggestions, computed over whatever metrics are\navailable.NetworkDiagnostic.evaluateQuality() 返回一个加权 0–100 分,附带等级与可读建议,\n它基于当前可用的各项指标计算得出。","basic--基础#Basic / 基础":"final quality = await NetworkDiagnostic.evaluateQuality(\n includePing: true,\n includeDns: true,\n includeSpeedTest: true,\n includeUpload: true,\n);\nprint('${quality.score.toStringAsFixed(1)}/100 — ${quality.level.label}');\nquality.metrics.forEach((metric, value) {\n print(' $metric = ${value.toStringAsFixed(2)}');\n});\nfor (final suggestion in quality.suggestions) {\n print('• $suggestion');\n}\nParameter\tDefault\tMeaning\tincludePing\ttrue\tSample latency / jitter / loss\tincludeDns\ttrue\tSample DNS latency\tincludeSpeedTest\ttrue\tSample download / upload (heavy)\tincludeUpload\ttrue\tInclude the upload metric\tpingHost / pingCount\tfrom config\tLatency target\tdnsDomain / dnsServers\tfrom config\tDNS target\tdownloadUrl / uploadUrl\tfrom config\tBandwidth endpoints","lightweight-variant--轻量变体#Lightweight variant / 轻量变体":"final quality = await NetworkDiagnostic.evaluateQuality(includeSpeedTest: false);","level-thresholds--等级阈值#Level thresholds / 等级阈值":"≥90 excellent · ≥75 good · ≥60 fair · ≥40 poor · otherwise bad\n(NetworkQualityLevel.fromScore).≥90 极佳 · ≥75 良好 · ≥60 一般 · ≥40 较差 · 其余为极差。","weights--权重#Weights / 权重":"Metric\tWeight\tSource\tlatency\t0.25\tPingResult.averageTime\tjitter\t0.10\tPingResult.jitter\tpacketLoss\t0.15\tPingResult.packetLoss\tdownload\t0.25\tSpeedTestResult.downloadSpeed\tupload\t0.15\tSpeedTestResult.uploadSpeed\tdns\t0.10\tmean of successful DnsTestResult.responseTimeMs\tsignalStrength\t0.10\tNetworkConnectionInfo.signalStrength","pure-function--纯函数#Pure function / 纯函数":"If you already have the metrics, skip the network entirely:如果你已有指标,可完全跳过网络:\nfinal score = NetworkQualityEvaluator.evaluate(\n latency: 42, jitter: 6, packetLoss: 0,\n download: 88.4, upload: 12.1, dns: 25, signalStrength: -55,\n targets: const QualityTargets(),\n);\nprint('${score.score.toStringAsFixed(1)} → ${score.level.label}');\nTune the ideal values globally through NetworkDiagnosticConfig(qualityTargets: ...).\n调整理想值请通过 NetworkDiagnosticConfig(qualityTargets: ...) 全局设置。"}},"/Usage":{"title":"Usage / 用法","data":{"which-api-do-i-need--该用哪个-api#Which API do I need? / 该用哪个 API?":"I want to know…\tCall\tAm I online, and over what transport?\tNetworkDiagnostic.checkConnection()\tReact to Wi-Fi ⇄ cellular switches\tNetworkDiagnostic.onConnectivityChanged\tLatency, jitter, packet loss\tNetworkDiagnostic.ping()\tIs DNS slow or broken?\tNetworkDiagnostic.resolve()\tIs host:port reachable?\tNetworkDiagnostic.checkPort()\tWhich of these ports are open?\tNetworkDiagnostic.scanPorts()\tHow fast is down/up?\tNetworkDiagnostic.runSpeedTest()\tOne number for “good or bad”\tNetworkDiagnostic.evaluateQuality()\tEverything at once, as a report\tNetworkDiagnostic.diagnose()\tIs the diagnostics API itself expensive?\tNetworkBenchmark.runAll()\tNative version / SSID / gateway / MAC\tZeroNetworkKit.getPlatformVersion(), getNativeNetworkDetails()","global-configuration--全局配置#Global configuration / 全局配置":"NetworkDiagnosticConfig centralises every default. Override only what you\nneed; the rest keep the defaults below.NetworkDiagnosticConfig 集中了全部默认参数,只需覆盖你关心的字段。\nZeroNetworkKit.init(\n config: const NetworkDiagnosticConfig(\n pingHost: '1.1.1.1',\n pingPort: 443,\n pingCount: 5,\n dnsDomain: 'example.com',\n dnsServers: ['1.1.1.1', '8.8.8.8'],\n downloadUrl: 'https://my-cdn.example.com/speedtest.bin',\n uploadUrl: 'https://my-cdn.example.com/upload',\n ),\n);\nField\tDefault\tMeaning\tpingHost\t'1.1.1.1'\tDefault ping / port-scan target\tpingPort\t443\tPort used by the TCP ping\tpingCount\t4\tProbes per ping run\tpingTimeout\t3s\tTimeout of one probe\tpingInterval\t200ms\tDelay between probes\tdnsDomain\t'www.google.com'\tDefault domain for DNS tests\tdnsServers\t['1.1.1.1', '8.8.8.8', '114.114.114.114']\tServers queried by default\tdnsTimeout\t5s\tTimeout per DNS server\tdownloadUrl\tCloudflare __down?bytes=25000000\tDownload endpoint\tuploadUrl\tCloudflare __up\tUpload endpoint\tuploadPayloadBytes\t1048576 (1 MiB)\tUpload payload size\tspeedTestTimeout\t30s\tPer-request timeout\tspeedTestMaxDuration\t10s\tSampling window per phase\tportCheckTimeout\t3s\tTimeout of one port check\tprobePorts\t[80, 443]\tPorts used by scanPorts() by default\tqualityTargets\tconst QualityTargets()\tIdeal values for scoring\t\nThe default speed-test endpoints are Cloudflare's public service. Swap them for\nyour own before shipping production traffic.\n默认测速端点是 Cloudflare 的公共服务,正式项目请替换为自建端点。","quality-targets--质量评分理想值#Quality targets / 质量评分理想值":"const NetworkDiagnosticConfig(\n qualityTargets: QualityTargets(\n excellentLatency: 30, // ms\n acceptableLatency: 150, // ms\n excellentJitter: 5, // ms\n acceptableJitter: 40, // ms\n acceptablePacketLoss: 5, // %\n excellentDownload: 50, // Mbps\n acceptableDownload: 5, // Mbps\n excellentUpload: 20, // Mbps\n acceptableUpload: 2, // Mbps\n excellentDns: 30, // ms\n acceptableDns: 200, // ms\n ),\n);","json-serialisation--序列化#JSON serialisation / 序列化":"Every result object implements toMap() and is JSON encodable:每个结果对象都实现了 toMap(),可直接 JSON 序列化:\nimport 'dart:convert';\nfinal report = await NetworkDiagnostic.diagnose(includeSpeedTest: false);\nfinal json = jsonEncode(report.toMap());\nprint(json);\n// Round-trip a connection snapshot.\nfinal decoded = NetworkConnectionInfo.fromMap(jsonDecode(jsonEncode(\n report.connection.toMap(),\n)) as Map);\nType\tSerialisation\tNetworkConnectionInfo\ttoMap() + fromMap()\tPingResult\ttoMap()\tDnsTestResult\ttoMap()\tPortCheckResult\ttoMap()\tSpeedTestResult\ttoMap()\tNetworkQualityScore\ttoMap()\tNetworkDiagnosticReport\ttoMap()\tBenchmarkResult / BenchmarkSuiteResult\ttoMap()","dependency-injection--tests--注入与测试#Dependency injection & tests / 注入与测试":"Every service accepts its collaborators, so your integration points are\nunit-testable without touching the network.每个服务都支持注入协作对象,可以完全脱离网络做单测。\nclass FakeConnectivityAdapter implements ConnectivityAdapter {\n @override\n Future> checkConnectivity() async => ['wifi'];\n @override\n Stream> get onConnectivityChanged => const Stream.empty();\n}\nNetworkDiagnostic.configure(\n config: const NetworkDiagnosticConfig(pingHost: '127.0.0.1'),\n connectivity: ConnectivityService(adapter: FakeConnectivityAdapter()),\n ping: PingService(),\n dns: DnsService(),\n ports: PortService(),\n speedTest: SpeedTestService(client: fakeClient),\n);\n// Restore defaults afterwards.\nNetworkDiagnostic.reset();\nSwap individual services directly when you prefer:也可以直接替换单个服务:\nNetworkDiagnostic.pingService = MyFakePingService();\nNetworkDiagnostic.dnsService = MyFakeDnsService();\nFor advanced service classes, the DNS wire codec and low-level APIs, import\npackage:zero_network_kit/advanced.dart.\n进阶的服务类、DNS 报文编解码器等底层 API 请通过\npackage:zero_network_kit/advanced.dart 引入。"}},"/":{"title":"Zero Network Kit","data":{"":"A Flutter plugin for network diagnostics: connectivity inspection, latency\nprobing, DNS resolution, port checks, bandwidth measurement, quality scoring and\nmicro-benchmarks — on Android, iOS, macOS, Windows and Linux.一个 Flutter 插件,提供网络诊断能力:连通性检测、延迟探测、DNS 解析、端口\n检测、带宽测速、质量评分与微基准测试——覆盖 Android、iOS、macOS、Windows 与 Linux。","-features--功能特性#✨ Features / 功能特性":"Feature\tDescription\tZero setup\tWorks out of the box; init() only to override defaults / 开箱即用,仅需在想覆盖默认值时才 init()\tConnectivity\tTransport type, IPv4/IPv6, gateway, SSID, RSSI, MAC, VPN / 传输类型、IPv4/IPv6、网关、SSID、信号强度、MAC、VPN\tLatency\tTCP handshake RTT everywhere; system ICMP on desktop / TCP 握手 RTT 全平台可用,桌面额外支持系统 ICMP\tDNS\tSystem resolver + raw UDP against explicit servers / 系统解析器 + 针对指定服务器的原始 UDP 查询\tPorts\tBounded-concurrency TCP reachability / 有界并发的 TCP 可达性检测\tSpeed test\tDownload/upload throughput with progress callbacks / 上下行测速,带进度回调\tQuality\tWeighted 0–100 score + level + suggestions / 加权 0–100 分 + 等级 + 建议\tFull report\tOne-shot aggregate of every probe / 一次性汇总所有探测\tBenchmarks\tMeasures how fast the diagnostics API itself runs / 衡量诊断 API 自身开销\tCapabilities\tQuery-then-call; hide unsupported cards (e.g. SSID on desktop) / 先查询后调用,隐藏不支持的卡片\tGraceful degradation\tMissing permission or unreachable sub-service never throws / 缺权限或子服务不可达时只返回 null,绝不抛异常\tHermetic tests\tEvery service accepts injected collaborators / 每个服务都支持注入协作对象,便于单测","-table-of-contents--目录#📚 Table of Contents / 目录":"Page\tDescription\tGetting Started\tQuick start guide / 快速开始\tInstallation\tHow to install / 安装方式\tUsage\tGlobal config, serialisation, dependency injection / 全局配置、序列化、依赖注入\tConnectivity\tConnection snapshot & change stream / 连通性快照与变化监听\tLatency / Ping\tTCP & ICMP latency probing / 延迟探测\tDNS\tResolver comparison across servers / DNS 解析\tPorts\tSingle port & bulk scan / 端口检测\tSpeed Test\tDownload/upload throughput / 测速\tQuality Score\tWeighted scoring / 质量评分\tFull Report\tOne-shot aggregate / 汇总报告\tBenchmarks\tMicro-benchmarks / 微基准\tPlatform Support\tCapability matrix & native details / 能力矩阵与原生详情\tAPI Reference\tModel field reference & gotchas / 模型字段速查与陷阱\tFAQ\tFrequently asked questions / 常见问题","-links--链接#🔗 Links / 链接":"GitHub\nOfficial Website\npub.dev","-license--许可证#📄 License / 许可证":"This project is licensed under the MPL-2.0.本项目采用 MPL-2.0 许可证。This plugin is provided \"as is\", without warranty of any kind. The author assumes\nno responsibility or liability for the functionality, security, or any\nconsequences arising from the use of modified versions or derivative projects.本插件按\"原样\"提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及\n任何使用后果承担责任。"}}} \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Installation-4297b711c2aa203c.js b/docs/_next/static/chunks/pages/Installation-4297b711c2aa203c.js deleted file mode 100644 index 0075cf4..0000000 --- a/docs/_next/static/chunks/pages/Installation-4297b711c2aa203c.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[139],{9720:function(e,i,t){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Installation",function(){return t(1220)}])},1220:function(e,i,t){"use strict";t.r(i),t.d(i,{useTOC:function(){return l}});var n=t(5893),s=t(7812),r=t(7080),d=t(8925),a=t(5192);function l(e){return[{value:"From pub.dev (Recommended) / 从 pub.dev 安装(推荐)",id:"from-pubdev-recommended--从-pubdev-安装推荐",depth:2},{value:"From GitHub / 从 GitHub 安装",id:"from-github--从-github-安装",depth:2},{value:"Platform Setup / 平台配置",id:"platform-setup--平台配置",depth:2},{value:"Android",id:"android",depth:3},{value:"iOS",id:"ios",depth:3},{value:"macOS / Windows / Linux",id:"macos--windows--linux",depth:3},{value:"Import / 导入",id:"import--导入",depth:2},{value:"Requirements / 环境要求",id:"requirements--环境要求",depth:2},{value:"Next Steps / 下一步",id:"next-steps--下一步",depth:2}]}i.default=(0,s.c)(function(e){let{toc:i=l(e)}=e,t={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.h1,{children:"Installation / 安装"}),"\n",(0,n.jsx)(t.h2,{id:i[0].id,children:i[0].value}),"\n",(0,n.jsxs)(t.p,{children:["Add the following to your ",(0,n.jsx)(t.code,{children:"pubspec.yaml"}),":"]}),"\n",(0,n.jsxs)(t.p,{children:["在 ",(0,n.jsx)(t.code,{children:"pubspec.yaml"})," 中添加以下依赖:"]}),"\n",(0,n.jsx)(t.pre,{tabIndex:"0","data-language":"yaml","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(t.code,{children:[(0,n.jsxs)(t.span,{children:[(0,n.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:"dependencies"}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,n.jsxs)(t.span,{children:[(0,n.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" zero_network_kit"}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:": "}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"^1.0.4"})]})]})}),"\n",(0,n.jsx)(t.p,{children:"Then run:"}),"\n",(0,n.jsx)(t.p,{children:"然后运行:"}),"\n",(0,n.jsx)(t.pre,{icon:a.Fx,tabIndex:"0","data-language":"bash","data-word-wrap":"","data-copy":"",children:(0,n.jsx)(t.code,{children:(0,n.jsxs)(t.span,{children:[(0,n.jsx)(t.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"flutter"}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" pub"}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" get"})]})})}),"\n",(0,n.jsx)(t.h2,{id:i[1].id,children:i[1].value}),"\n",(0,n.jsx)(t.p,{children:"Alternatively, install from GitHub:"}),"\n",(0,n.jsx)(t.p,{children:"或者从 GitHub 安装:"}),"\n",(0,n.jsx)(t.pre,{tabIndex:"0","data-language":"yaml","data-word-wrap":"","data-copy":"",children:(0,n.jsxs)(t.code,{children:[(0,n.jsxs)(t.span,{children:[(0,n.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:"dependencies"}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,n.jsxs)(t.span,{children:[(0,n.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" zero_network_kit"}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,n.jsxs)(t.span,{children:[(0,n.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" git"}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,n.jsxs)(t.span,{children:[(0,n.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" url"}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:": "}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"https://github.com/zero-labsco/zero_network_kit.git"})]}),"\n",(0,n.jsxs)(t.span,{children:[(0,n.jsx)(t.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" ref"}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:": "}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"release/v1.0.4"})]})]})}),"\n",(0,n.jsx)(t.h2,{id:i[2].id,children:i[2].value}),"\n",(0,n.jsx)(t.h3,{id:i[3].id,children:i[3].value}),"\n",(0,n.jsxs)(t.p,{children:["The plugin manifest already declares ",(0,n.jsx)(t.code,{children:"INTERNET"}),", ",(0,n.jsx)(t.code,{children:"ACCESS_NETWORK_STATE"})," and\n",(0,n.jsx)(t.code,{children:"ACCESS_WIFI_STATE"}),". Reading the Wi-Fi ",(0,n.jsx)(t.strong,{children:"SSID"})," additionally requires the\nlocation permission (",(0,n.jsx)(t.code,{children:"ACCESS_FINE_LOCATION"}),") on Android 8.1+, otherwise the\nsnapshot reports ",(0,n.jsx)(t.code,{children:"ssid: null"}),"."]}),"\n",(0,n.jsxs)(t.p,{children:["插件清单已声明 ",(0,n.jsx)(t.code,{children:"INTERNET"}),"、",(0,n.jsx)(t.code,{children:"ACCESS_NETWORK_STATE"})," 与 ",(0,n.jsx)(t.code,{children:"ACCESS_WIFI_STATE"}),"。读取 Wi-Fi\n",(0,n.jsx)(t.strong,{children:"SSID"})," 还需在 Android 8.1+ 申请定位权限(",(0,n.jsx)(t.code,{children:"ACCESS_FINE_LOCATION"}),"),否则快照中\n",(0,n.jsx)(t.code,{children:"ssid"})," 为 ",(0,n.jsx)(t.code,{children:"null"}),"。"]}),"\n",(0,n.jsx)(t.h3,{id:i[4].id,children:i[4].value}),"\n",(0,n.jsxs)(t.p,{children:["No additional configuration needed. Reading the Wi-Fi ",(0,n.jsx)(t.strong,{children:"SSID"})," additionally\nrequires the ",(0,n.jsx)(t.em,{children:"Access WiFi Information"})," capability plus location authorisation."]}),"\n",(0,n.jsxs)(t.p,{children:["无需额外配置。读取 Wi-Fi ",(0,n.jsx)(t.strong,{children:"SSID"})," 还需开启 ",(0,n.jsx)(t.em,{children:"Access WiFi Information"})," 能力并授权定位。"]}),"\n",(0,n.jsx)(t.h3,{id:i[5].id,children:i[5].value}),"\n",(0,n.jsxs)(t.p,{children:["The plugin is declared on all three desktop platforms. On desktop, native\ndetails such as SSID / RSSI are ",(0,n.jsxs)(t.strong,{children:["always ",(0,n.jsx)(t.code,{children:"null"})]})," (tier A). Windows additionally\nprovides gateway / MAC / DNS / VPN through ",(0,n.jsx)(t.code,{children:"GetAdaptersAddresses"}),"; macOS and\nLinux report only the Dart-side IP/IPv6. See\n",(0,n.jsx)(t.a,{href:"Platform-Support",children:"Platform Support"})," for the full matrix."]}),"\n",(0,n.jsxs)(t.p,{children:["插件已在三个桌面平台声明。桌面上 SSID / 信号强度等原生详情",(0,n.jsxs)(t.strong,{children:["恒为 ",(0,n.jsx)(t.code,{children:"null"})]}),"(A 档)。\nWindows 额外通过 ",(0,n.jsx)(t.code,{children:"GetAdaptersAddresses"})," 提供网关 / MAC / DNS / VPN;macOS 与 Linux\n仅提供 Dart 侧的 IP/IPv6。完整矩阵见",(0,n.jsx)(t.a,{href:"Platform-Support",children:"平台支持"}),"。"]}),"\n",(0,n.jsx)(t.h2,{id:i[6].id,children:i[6].value}),"\n",(0,n.jsx)(t.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,n.jsx)(t.code,{children:(0,n.jsxs)(t.span,{children:[(0,n.jsx)(t.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"import"}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'package:zero_network_kit/zero_network_kit.dart'"}),(0,n.jsx)(t.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]})})}),"\n",(0,n.jsx)(t.h2,{id:i[7].id,children:i[7].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,n.jsxs)(t.table,{children:[(0,n.jsx)(t.thead,{children:(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.th,{children:"Requirement"}),(0,n.jsx)(t.th,{children:"Version"})]})}),(0,n.jsxs)(t.tbody,{children:[(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"Flutter"}),(0,n.jsx)(t.td,{children:">= 3.3.0"})]}),(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"Dart SDK"}),(0,n.jsx)(t.td,{children:">= 3.11.0 < 4.0.0"})]})]})]}),"\n",(0,n.jsx)(t.h2,{id:i[8].id,children:i[8].value}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"Getting-Started",children:"Getting Started"})," — Quick start guide / 快速开始"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"Usage",children:"Usage"})," — Full usage guide / 完整使用指南"]}),"\n"]})]})},"/Installation",{filePath:"pages/Installation.md",timestamp:1789334081e3,pageMap:r.v,frontMatter:{},title:"Installation / 安装"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,i,t){"use strict";t.d(i,{v:function(){return n}});let n=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--capabilities":{type:"separator",title:"\uD83D\uDD27 Capabilities"},Connectivity:"\uD83D\uDCE1 Connectivity",Ping:"⏱ Latency / Ping",DNS:"\uD83C\uDF0D DNS",Ports:"\uD83D\uDD0C Ports","Speed-Test":"\uD83D\uDE80 Speed Test",Quality:"⭐ Quality Score","Full-Report":"\uD83D\uDCCB Full Report",Benchmark:"\uD83D\uDCCA Benchmarks","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},"Platform-Support":"\uD83D\uDDA5 Platform Support","API-Reference":"\uD83D\uDCDA API Reference","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"API-Reference",route:"/API-Reference",frontMatter:{sidebarTitle:"API Reference"}},{name:"Benchmark",route:"/Benchmark",frontMatter:{sidebarTitle:"Benchmark"}},{name:"Connectivity",route:"/Connectivity",frontMatter:{sidebarTitle:"Connectivity"}},{name:"DNS",route:"/DNS",frontMatter:{sidebarTitle:"DNS"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"Full-Report",route:"/Full-Report",frontMatter:{sidebarTitle:"Full Report"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Ping",route:"/Ping",frontMatter:{sidebarTitle:"Ping"}},{name:"Platform-Support",route:"/Platform-Support",frontMatter:{sidebarTitle:"Platform Support"}},{name:"Ports",route:"/Ports",frontMatter:{sidebarTitle:"Ports"}},{name:"Quality",route:"/Quality",frontMatter:{sidebarTitle:"Quality"}},{name:"Speed-Test",route:"/Speed-Test",frontMatter:{sidebarTitle:"Speed Test"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=9720)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/_next/static/chunks/pages/Installation-c75a9ee9feffc261.js b/docs/_next/static/chunks/pages/Installation-c75a9ee9feffc261.js new file mode 100644 index 0000000..cd0071b --- /dev/null +++ b/docs/_next/static/chunks/pages/Installation-c75a9ee9feffc261.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[139],{9720:function(e,i,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/Installation",function(){return n(1220)}])},1220:function(e,i,n){"use strict";n.r(i),n.d(i,{useTOC:function(){return l}});var t=n(5893),s=n(7812),r=n(7080),d=n(8925),a=n(5192);function l(e){return[{value:"From pub.dev (Recommended) / 从 pub.dev 安装(推荐)",id:"from-pubdev-recommended--从-pubdev-安装推荐",depth:2},{value:"From GitHub / 从 GitHub 安装",id:"from-github--从-github-安装",depth:2},{value:"Platform Setup / 平台配置",id:"platform-setup--平台配置",depth:2},{value:"Android",id:"android",depth:3},{value:"iOS",id:"ios",depth:3},{value:"macOS / Windows / Linux",id:"macos--windows--linux",depth:3},{value:"Import / 导入",id:"import--导入",depth:2},{value:"Requirements / 环境要求",id:"requirements--环境要求",depth:2},{value:"Next Steps / 下一步",id:"next-steps--下一步",depth:2}]}i.default=(0,s.c)(function(e){let{toc:i=l(e)}=e,n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.a)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{children:"Installation / 安装"}),"\n",(0,t.jsx)(n.h2,{id:i[0].id,children:i[0].value}),"\n",(0,t.jsxs)(n.p,{children:["Add the following to your ",(0,t.jsx)(n.code,{children:"pubspec.yaml"}),":"]}),"\n",(0,t.jsxs)(n.p,{children:["在 ",(0,t.jsx)(n.code,{children:"pubspec.yaml"})," 中添加以下依赖:"]}),"\n",(0,t.jsx)(n.pre,{tabIndex:"0","data-language":"yaml","data-word-wrap":"","data-copy":"",children:(0,t.jsxs)(n.code,{children:[(0,t.jsxs)(n.span,{children:[(0,t.jsx)(n.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:"dependencies"}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,t.jsxs)(n.span,{children:[(0,t.jsx)(n.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" zero_network_kit"}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:": "}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"^1.0.5"})]})]})}),"\n",(0,t.jsx)(n.p,{children:"Then run:"}),"\n",(0,t.jsx)(n.p,{children:"然后运行:"}),"\n",(0,t.jsx)(n.pre,{icon:a.Fx,tabIndex:"0","data-language":"bash","data-word-wrap":"","data-copy":"",children:(0,t.jsx)(n.code,{children:(0,t.jsxs)(n.span,{children:[(0,t.jsx)(n.span,{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"},children:"flutter"}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" pub"}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" get"})]})})}),"\n",(0,t.jsx)(n.h2,{id:i[1].id,children:i[1].value}),"\n",(0,t.jsx)(n.p,{children:"Alternatively, install from GitHub:"}),"\n",(0,t.jsx)(n.p,{children:"或者从 GitHub 安装:"}),"\n",(0,t.jsx)(n.pre,{tabIndex:"0","data-language":"yaml","data-word-wrap":"","data-copy":"",children:(0,t.jsxs)(n.code,{children:[(0,t.jsxs)(n.span,{children:[(0,t.jsx)(n.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:"dependencies"}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,t.jsxs)(n.span,{children:[(0,t.jsx)(n.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" zero_network_kit"}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,t.jsxs)(n.span,{children:[(0,t.jsx)(n.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" git"}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:":"})]}),"\n",(0,t.jsxs)(n.span,{children:[(0,t.jsx)(n.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" url"}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:": "}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"https://github.com/zero-labsco/zero_network_kit.git"})]}),"\n",(0,t.jsxs)(n.span,{children:[(0,t.jsx)(n.span,{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"},children:" ref"}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:": "}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:"release/v1.0.5"})]})]})}),"\n",(0,t.jsx)(n.h2,{id:i[2].id,children:i[2].value}),"\n",(0,t.jsx)(n.h3,{id:i[3].id,children:i[3].value}),"\n",(0,t.jsxs)(n.p,{children:["The plugin manifest already declares ",(0,t.jsx)(n.code,{children:"INTERNET"}),", ",(0,t.jsx)(n.code,{children:"ACCESS_NETWORK_STATE"})," and\n",(0,t.jsx)(n.code,{children:"ACCESS_WIFI_STATE"}),". Reading the Wi-Fi ",(0,t.jsx)(n.strong,{children:"SSID"})," additionally requires the\nlocation permission (",(0,t.jsx)(n.code,{children:"ACCESS_FINE_LOCATION"}),") on Android 8.1+, otherwise the\nsnapshot reports ",(0,t.jsx)(n.code,{children:"ssid: null"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["插件清单已声明 ",(0,t.jsx)(n.code,{children:"INTERNET"}),"、",(0,t.jsx)(n.code,{children:"ACCESS_NETWORK_STATE"})," 与 ",(0,t.jsx)(n.code,{children:"ACCESS_WIFI_STATE"}),"。读取 Wi-Fi\n",(0,t.jsx)(n.strong,{children:"SSID"})," 还需在 Android 8.1+ 申请定位权限(",(0,t.jsx)(n.code,{children:"ACCESS_FINE_LOCATION"}),"),否则快照中\n",(0,t.jsx)(n.code,{children:"ssid"})," 为 ",(0,t.jsx)(n.code,{children:"null"}),"。"]}),"\n",(0,t.jsx)(n.h3,{id:i[4].id,children:i[4].value}),"\n",(0,t.jsxs)(n.p,{children:["No additional configuration needed. Reading the Wi-Fi ",(0,t.jsx)(n.strong,{children:"SSID"})," additionally\nrequires the ",(0,t.jsx)(n.em,{children:"Access WiFi Information"})," capability plus location authorisation."]}),"\n",(0,t.jsxs)(n.p,{children:["无需额外配置。读取 Wi-Fi ",(0,t.jsx)(n.strong,{children:"SSID"})," 还需开启 ",(0,t.jsx)(n.em,{children:"Access WiFi Information"})," 能力并授权定位。"]}),"\n",(0,t.jsx)(n.h3,{id:i[5].id,children:i[5].value}),"\n",(0,t.jsxs)(n.p,{children:["The plugin is declared on all three desktop platforms. On desktop, native\ndetails such as SSID / RSSI are ",(0,t.jsxs)(n.strong,{children:["always ",(0,t.jsx)(n.code,{children:"null"})]})," (tier A). Windows additionally\nprovides gateway / MAC / DNS / VPN through ",(0,t.jsx)(n.code,{children:"GetAdaptersAddresses"}),"; macOS and\nLinux report only the Dart-side IP/IPv6. See\n",(0,t.jsx)(n.a,{href:"Platform-Support",children:"Platform Support"})," for the full matrix."]}),"\n",(0,t.jsxs)(n.p,{children:["插件已在三个桌面平台声明。桌面上 SSID / 信号强度等原生详情",(0,t.jsxs)(n.strong,{children:["恒为 ",(0,t.jsx)(n.code,{children:"null"})]}),"(A 档)。\nWindows 额外通过 ",(0,t.jsx)(n.code,{children:"GetAdaptersAddresses"})," 提供网关 / MAC / DNS / VPN;macOS 与 Linux\n仅提供 Dart 侧的 IP/IPv6。完整矩阵见",(0,t.jsx)(n.a,{href:"Platform-Support",children:"平台支持"}),"。"]}),"\n",(0,t.jsx)(n.h2,{id:i[6].id,children:i[6].value}),"\n",(0,t.jsx)(n.pre,{tabIndex:"0","data-language":"dart","data-word-wrap":"","data-copy":"",children:(0,t.jsx)(n.code,{children:(0,t.jsxs)(n.span,{children:[(0,t.jsx)(n.span,{style:{"--shiki-light":"#D73A49","--shiki-dark":"#F97583"},children:"import"}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"},children:" 'package:zero_network_kit/zero_network_kit.dart'"}),(0,t.jsx)(n.span,{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"},children:";"})]})})}),"\n",(0,t.jsx)(n.h2,{id:i[7].id,children:i[7].value}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Requirement"}),(0,t.jsx)(n.th,{children:"Version"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Flutter"}),(0,t.jsx)(n.td,{children:">= 3.3.0"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Dart SDK"}),(0,t.jsx)(n.td,{children:">= 3.11.0 < 4.0.0"})]})]})]}),"\n",(0,t.jsx)(n.h2,{id:i[8].id,children:i[8].value}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"Getting-Started",children:"Getting Started"})," — Quick start guide / 快速开始"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"Usage",children:"Usage"})," — Full usage guide / 完整使用指南"]}),"\n"]}),"\n"]})},"/Installation",{filePath:"pages/Installation.md",timestamp:1789334081e3,pageMap:r.v,frontMatter:{},title:"Installation / 安装"},"undefined"==typeof RemoteContent?l:RemoteContent.useTOC)},7080:function(e,i,n){"use strict";n.d(i,{v:function(){return t}});let t=[{data:{index:"\uD83C\uDFE0 Home","Getting-Started":"\uD83D\uDE80 Getting Started",Installation:"\uD83D\uDCE6 Installation",Usage:"\uD83D\uDCD6 Usage","--capabilities":{type:"separator",title:"\uD83D\uDD27 Capabilities"},Connectivity:"\uD83D\uDCE1 Connectivity",Ping:"⏱ Latency / Ping",DNS:"\uD83C\uDF0D DNS",Ports:"\uD83D\uDD0C Ports","Speed-Test":"\uD83D\uDE80 Speed Test",Quality:"⭐ Quality Score","Full-Report":"\uD83D\uDCCB Full Report",Benchmark:"\uD83D\uDCCA Benchmarks","--advanced":{type:"separator",title:"\uD83D\uDEE0 Advanced"},"Platform-Support":"\uD83D\uDDA5 Platform Support","API-Reference":"\uD83D\uDCDA API Reference","--info":{type:"separator",title:"ℹ️ Info"},FAQ:"❓ FAQ"}},{name:"API-Reference",route:"/API-Reference",frontMatter:{sidebarTitle:"API Reference"}},{name:"Benchmark",route:"/Benchmark",frontMatter:{sidebarTitle:"Benchmark"}},{name:"Connectivity",route:"/Connectivity",frontMatter:{sidebarTitle:"Connectivity"}},{name:"DNS",route:"/DNS",frontMatter:{sidebarTitle:"DNS"}},{name:"FAQ",route:"/FAQ",frontMatter:{sidebarTitle:"Faq"}},{name:"Full-Report",route:"/Full-Report",frontMatter:{sidebarTitle:"Full Report"}},{name:"Getting-Started",route:"/Getting-Started",frontMatter:{sidebarTitle:"Getting Started"}},{name:"index",route:"/",frontMatter:{sidebarTitle:"Index"}},{name:"Installation",route:"/Installation",frontMatter:{sidebarTitle:"Installation"}},{name:"Ping",route:"/Ping",frontMatter:{sidebarTitle:"Ping"}},{name:"Platform-Support",route:"/Platform-Support",frontMatter:{sidebarTitle:"Platform Support"}},{name:"Ports",route:"/Ports",frontMatter:{sidebarTitle:"Ports"}},{name:"Quality",route:"/Quality",frontMatter:{sidebarTitle:"Quality"}},{name:"Speed-Test",route:"/Speed-Test",frontMatter:{sidebarTitle:"Speed Test"}},{name:"Usage",route:"/Usage",frontMatter:{sidebarTitle:"Usage"}}]}},function(e){e.O(0,[812,888,774,179],function(){return e(e.s=9720)}),_N_E=e.O()}]); \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 092b853..92df91e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,4 +1,4 @@ -
    🏠 Home

    Zero Network Kit

    +
    🏠 Home

    Zero Network Kit

    A Flutter plugin for network diagnostics: connectivity inspection, latency probing, DNS resolution, port checks, bandwidth measurement, quality scoring and micro-benchmarks — on Android, iOS, macOS, Windows and Linux.

    @@ -143,4 +143,4 @@

    本插件按”原样”提供,不提供任何担保。作者不对修改版或衍生项目的功能、安全性及 -任何使用后果承担责任。


    Zero Network Kit · MPL-2.0
    \ No newline at end of file +任何使用后果承担责任。


    Zero Network Kit · MPL-2.0
    \ No newline at end of file