Skip to content

fix(ohos): sync dashboard core status on startup#2176

Open
phenix3443 wants to merge 50 commits into
chen08209:mainfrom
phenix3443:fix/dashboard-core-status
Open

fix(ohos): sync dashboard core status on startup#2176
phenix3443 wants to merge 50 commits into
chen08209:mainfrom
phenix3443:fix/dashboard-core-status

Conversation

@phenix3443

Copy link
Copy Markdown

Summary

  • default app startup core status to disconnected instead of connecting
  • sync HarmonyOS startup dashboard status from native VPN state when UI core startup is skipped
  • add regression tests for the startup status sync path

Testing

  • flutter analyze lib/models/app.dart lib/models/generated/app.freezed.dart lib/state.dart test/providers/app_test.dart test/providers/action_test.dart
  • flutter test test/providers/app_test.dart
  • flutter test test/providers/action_test.dart

Refs phenix3443#4

github-actions Bot and others added 20 commits July 23, 2025 00:27
…YouTube (#1)

* feat(ohos): add HarmonyOS support

* fix(ohos): align packaging scaffold with runtime support

* fix(ohos): remove unrelated branch changes

* fix(ohos): stop tracking local build artifacts

* fix(ohos): keep host config reproducible

* fix(ohos): tighten scaffold PR scope

* docs(ohos): add emulator smoke test flow

* docs(ohos): tighten emulator smoke test script

* feat(ohos): add HarmonyOS support

* fix(ohos): harden interactions on real devices

* fix(ohos): preserve custom plugin registration during builds

* fix(ohos): run core invocations off the main thread

* fix(ohos): restore delay test pending indicator

* fix(ohos): route browser TCP through the proxy via the gVisor TUN stack

On HarmonyOS the default "mixed" TUN stack handled UDP via gVisor but TCP via
the system NAT, which rewrites each SYN to the tun address and relies on a
kernel loopback to a local listener. That loopback does not happen inside the
OHOS VpnExtension, so every TCP connection through the tun was silently dropped
(connection table stayed at count=0) and no browser could load any foreign site,
while a loopback curl to the same proxy node worked.

Fix (two parts):
- core/tun/tun.go: force the gVisor stack on OHOS so TCP is handled entirely in
  userspace, no kernel loopback.
- scripts/ohos/patch_gvisor_tun_fd.sh: gVisor's fdbased.New Fstats the tun fd to
  detect sockets, which the OHOS sandbox denies; patch isSocketFD to fall back to
  non-socket (readv) dispatch. Applied idempotently before every OHOS lib build
  from GoBuilder._patchOhosGvisorTunFd.

Verified on Mate 80 Pro: both Chrome and the Huawei native browser fully render
YouTube through the VPN.

Bundles the supporting feat/ohos-support WIP it depends on: TUN options refactor
(core/tun/options*.go), vendored patched sing-tun, fake-ip/DNS and VPN-config
changes, NAPI bridge updates, build-tool/script updates, docs, and tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ohos): connect the UI to the running VPN core for live status, stats and connections

When the VPN runs in the com.follow.clash:vpn process, the main app UI had no
live channel to the core serving traffic: the dashboard stuck at "连接中…",
traffic stats read 0, the connections page stayed empty, and live outbound-mode /
node switches did not take effect (they only applied on the next VPN restart).

The Go core is the socket client (startServer -> dial) and Dart's CoreService is
the server (ServerSocket.bind(unixSocketPath)), but nothing made the VPN-process
core dial the main app's socket. Thread the main app's unixSocketPath through
startVpn (app.dart, vpn_manager.dart, state.dart) -> AppPlugin.ets ->
FlClashVpnAbility, which after startTun calls startEmbeddedCore(coreSocketPath,
filesDir). That dlopens the same libclash.so and runs startServerProcessDetached
-> dial, so the in-process VPN core connects back to the listening UI socket. The
two OHOS-VPN _handleStart(syncCoreState: false) calls are flipped to
_handleStart() so the UI subscribes/polls once connected.

Also add scripts/ohos/verify_all.sh: a one-command real-device regression suite
(uitest find-text/tap-text + 7890 loopback probes) covering install, core, VPN,
both browsers loading YouTube, dashboard, proxy tab, profiles, all tools/settings
pages, backup, and the two live-link features. 22/22 PASS on Mate 80 Pro. Report
in docs/ohos-real-device-test-report.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ohos): show "connected" status once the VPN core link is established

The dashboard status chip stayed on "连接中…" (connecting) whenever the VPN was
on, because that path never reached CoreStatus.connected: when the VPN runs in
the com.follow.clash:vpn process the main app does not go through
connectCore()/preload(). Now that the VPN-process core dials the main app's
CoreService socket, set CoreStatus.connected after the OHOS-VPN _handleStart()
(the link is up by then) and CoreStatus.disconnected when the VPN stops.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ci): unblock the release pipeline (analyze + test gates green)

The branch dropped the `test` dev_dependency (it conflicts with flutter_test's
pinned test_api on the current SDK), but 17 tests still imported
`package:test/test.dart`, so `flutter analyze` reported 875 errors and the CI
test job failed (build needs it). Fixes:

- Migrate the 17 pure-Dart tests to `package:flutter_test/flutter_test.dart`
  (re-exports test/group/expect/setUp/tearDown), compatible with both the
  pinned SDK and CI's newer Flutter.
- database_test.dart: import common.dart for the List.copyAndPut extension.
- config_test.dart: the youtube fake-ip-filter asserts now check youtube is
  NOT excluded (matches the OHOS DNS de-poisoning fix); mock path_provider for
  the Script.saveWithPath test so it no longer hangs on MissingPluginException.
- analysis_options: exclude third_party/** (vendored forks) and tool/** (dev
  scripts) from the lint surface.
- Remove unused imports (picker/task/service), a redundant null check, and 3
  never-passed key params (tools.dart) flagged as warnings.

Result: `flutter analyze --no-fatal-infos` exits 0 (0 errors, 0 warnings) and
`flutter test` passes all 437 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(ohos): drop leftover [BOOT] debug print tracing from the boot path

The boot sequence in main.dart and GlobalState (state.dart) printed ~40 bare
`print('[BOOT] ...')` traces that ran on every platform (Windows/macOS/Linux
too) and violated the project's own avoid_print lint. Remove them; uncaught
boot errors are still surfaced via InitErrorScreen and commonPrint.log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(ohos): fix build, harden core concurrency, verify VPN on real device

Build blockers (HAP now builds):
- setup.dart: remove duplicate _readAppVersion definition
- AppPlugin.ets/FilePickerDelegate.ets: fix ArkTS any/unknown + null-assignability errors
- revert local-plugin registration that made hvigor fail OhmUrl resolution
  (proxy/wifi_ssid/window_ext source dirs are not buildable modules;
  AppPlugin/FilePickerPlugin still register via CustomPluginRegistrant)

Core concurrency:
- server_cgo.go: serialize frame writes on a dedicated writeMu so a stalled
  Write cannot deadlock stopServer; generation-guarded serveConnection closes
  the TOCTOU window against stopServerProcessDetached
- bridge.cpp: stop dlclose/clearing embedded tracked-core state right after the
  non-blocking detached launch (was turning StopTrackedCore into a silent no-op)

Cleanup:
- strip leftover OHOS bring-up debug logging across lib/
- drop one-off probe/repro scripts; gitignore __pycache__

Tests/docs:
- add OHOS node + dart regression tests (server_cgo, tracked-core, vpn decisions,
  app-link/pending-debug, path, etc.)
- sync docs/harmonyos.md

Verified end-to-end on Mate 80 Pro: built-in browser + Chrome reach Google/YouTube
and the X app loads its timeline through the VPN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(ohos): reconcile node regression tests with build fixes

The build fixes changed source that four node source-pattern tests asserted
verbatim:
- AppPlugin error-callback params are now typed (Any) for ArkTS strict mode
- FilePickerDelegate gained a null guard before handlePickedUris
- proxy/wifi_ssid/window_ext registration + ohos.pluginClass were reverted
  (they broke hvigor OhmUrl resolution). Invert those two tests into guards
  that the har-only plugins stay excluded so the build cannot regress.

Also gitignore test/provider.txt (local subscription secret).

Gates green: dart analyze 0/0, flutter test, go test, node ohos 92/92.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ohos): avoid wifi_ssid MissingPluginException on OHOS

wifi_ssid is har-only and not registered on OHOS (it was excluded to keep the
hvigor build working), so calling into WifiSsidManager throws
MissingPluginException there.

- Permissions.checkLocationPermissions() runs unconditionally from _initApp on
  every launch and called WifiSsidManager.checkPermission() on OHOS; skip it
  (OHOS SSID detection is unavailable, so the location/SSID permission is moot).
- Hide the "on demand" (Wi-Fi SSID) advanced-config entry on OHOS; the feature
  cannot work there (currentSSID is always null via connectivity_manager's OHOS
  skip) and its permission flow would otherwise throw.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ohos): recover VPN from a stale 'started' status after unclean shutdown

When the VPN extension is killed uncleanly (process death / force-stop) it can
no longer write 'stopped' to flclash-vpn-status.txt, leaving a stale 'started'.
That permanently blocked recovery: stopVpn's waitForVpnStoppedStatus never saw
'stopped' and returned "vpn extension did not stop: started", and startVpn's
isVpnStatusRunning short-circuited as if the VPN were live.

FlClashVpnAbility is a system-managed singleton VPN, so a lingering 'started'
after the stop request + timeout means the extension is gone, not refusing to
stop. Reconcile that case to stopped (reset the status file, report success) so
the next start is not blocked.

Verified on Mate 80 Pro: force-stop while running -> restart now recovers with
no "did not stop: started" hang (previously required a manual status reset).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant