diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26c5962..e42f031 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
# Changelog
+## 1.0.4
+
+### Fixed / 修复
+
+- **Web/WASM build** — the platform-specific implementations are now selected by
+ gating the `dart:io` variants behind `dart.library.io`, using the browser-based
+ variants as the fallback. Previously the conditional exports defaulted to the
+ `dart:io` variants, so an environment that does not define `dart.library.html`
+ still resolved them and pulled `dart:io` into the Web build. Runtime behaviour
+ is unchanged on every supported platform: native still uses the `dart:io`
+ variants, the Web still uses the browser-based ones.
+ - **Web/WASM 构建**——各平台实现改为按 `dart.library.io` 条件引入原生版本,并以
+ 浏览器版本作为兜底。此前条件导出默认使用 `dart:io` 版本,未定义
+ `dart.library.html` 的环境仍会解析到它们,从而把 `dart:io` 带入 Web 构建。
+ 各平台运行时行为不变:原生仍使用 `dart:io` 版本,Web 仍使用基于浏览器的版本。
+
## 1.0.3
### Fixed / 修复
diff --git a/README.md b/README.md
index 8b55174..e58847f 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ micro-benchmarks — for Android, iOS, macOS, Windows, Linux and Web (partial).
[](https://dart.dev)
[](https://pub.dev/packages/effective_dart)
-> **🔔 Upgrade recommended:** `1.0.3` makes the package WASM-compatible — the web build no longer pulls in `connectivity_plus`'s Linux-only `nm` dependency, so `flutter build web --wasm` and pub.dev's platform score (20/20) both pass. Upgrade to `^1.0.3`.
+> **🔔 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`.
🌐 **[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.3
+ zero_network_kit: ^1.0.4
```
### Android permissions
diff --git a/README_zh.md b/README_zh.md
index b323ce5..7ccdca6 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -18,7 +18,7 @@
[](https://dart.dev)
[](https://pub.dev/packages/effective_dart)
-> **🔔 推荐升级:** `1.0.3` 让包具备 WASM 兼容性——Web 构建不再引入 `connectivity_plus` 仅限 Linux 的 `nm` 依赖,因此 `flutter build web --wasm` 与 pub.dev 的平台评分(20/20)均可通过。建议升级到 `^1.0.3`。
+> **🔔 推荐升级:** `1.0.4` 移除了 Web 构建中残留的 `dart:io` 引用——各平台实现改为按 `dart.library.io` 条件引入原生版本,Web 侧因此只解析到基于浏览器的实现,可用 `flutter build web --wasm` 编译。建议升级到 `^1.0.4`。
🌐 **[官方网站](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.3
+ zero_network_kit: ^1.0.4
```
### Android 权限
diff --git a/docs/404.html b/docs/404.html
index 02656d9..bcadad3 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 02656d9..bcadad3 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 8ddb2a8..bc866b9 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.
\ No newline at end of file
diff --git a/docs/Benchmark/index.html b/docs/Benchmark/index.html
index adc5857..3cd5f49 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 自身的开销——可用在判断某项诊断能否放在启动路径上。
\ No newline at end of file
diff --git a/docs/Connectivity/index.html b/docs/Connectivity/index.html
index ad43266..4da6070 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.
\ No newline at end of file
diff --git a/docs/DNS/index.html b/docs/DNS/index.html
index 836e599..5a552f8 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}');
\ No newline at end of file
diff --git a/docs/FAQ/index.html b/docs/FAQ/index.html
index 8645b5a..f42d4fe 100644
--- a/docs/FAQ/index.html
+++ b/docs/FAQ/index.html
@@ -1,4 +1,4 @@
-
❓ FAQ
FAQ / 常见问题
+
❓ FAQ
FAQ / 常见问题
Do I need to call init()? / 需要调用 init() 吗?
No. Every API falls back to built-in defaults. Call ZeroNetworkKit.init() only
when you want to override defaults (e.g. a custom reachability host or DNS
@@ -38,4 +38,4 @@
\ No newline at end of file
diff --git a/docs/Full-Report/index.html b/docs/Full-Report/index.html
index c2145b2..4fef227 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。
\ No newline at end of file
diff --git a/docs/Getting-Started/index.html b/docs/Getting-Started/index.html
index d93e0bb..9bb8458 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:
\ No newline at end of file
diff --git a/docs/Installation/index.html b/docs/Installation/index.html
index b7f40a6..905ad72 100644
--- a/docs/Installation/index.html
+++ b/docs/Installation/index.html
@@ -1,9 +1,9 @@
-
\ No newline at end of file
diff --git a/docs/Ping/index.html b/docs/Ping/index.html
index 8f8d4a6..ac6d08e 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.
mode: PingMode.icmp on desktop to get closer to real ICMP.
TCP 探测需要一个在监听的端口。对不接受 TCP/443 的主机探测 port: 443 会失败——
那是“被过滤”,不是“离线”。在桌面上用 PingMode.icmp 更接近真实 ICMP。
-
\ No newline at end of file
diff --git a/docs/Platform-Support/index.html b/docs/Platform-Support/index.html
index a03cfba..1ca17e8 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).
\ No newline at end of file
diff --git a/docs/Ports/index.html b/docs/Ports/index.html
index a630cf8..de5292b 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.
\ No newline at end of file
diff --git a/docs/Quality/index.html b/docs/Quality/index.html
index d7fd5c5..52d9735 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.
\ No newline at end of file
diff --git a/docs/Speed-Test/index.html b/docs/Speed-Test/index.html
index d01587a..90646dc 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.
\ No newline at end of file
diff --git a/docs/Usage/index.html b/docs/Usage/index.html
index 7ec87f1..f5e34d4 100644
--- a/docs/Usage/index.html
+++ b/docs/Usage/index.html
@@ -1,4 +1,4 @@
-
📖 Usage
Usage / 用法
+
📖 Usage
Usage / 用法
Which API do I need? / 该用哪个 API?
@@ -268,4 +268,4 @@
package:zero_network_kit/advanced.dart.
进阶的服务类、DNS 报文编解码器等底层 API 请通过
package:zero_network_kit/advanced.dart 引入。
-
\ No newline at end of file
diff --git a/docs/_next/static/chunks/nextra-data-en-US.json b/docs/_next/static/chunks/nextra-data-en-US.json
index dc07340..9774a81 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.3\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.3","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