Skip to content

Conversation

@doromaraujo
Copy link
Contributor

@doromaraujo doromaraujo commented Jan 23, 2026

When VPN is already running, the app is reporting a wrong UI state.

This happens because polling for connection status is happening before a session is established, and as it is, it is only being established if the user taps the button and goes to a Connected state.

checkExtensionState would eventually update extensionStatus to connected, and thus when the user tapped the button from a cold start, it'd attempt to stop a connection without the vpnManager even being set, so it'd do nothing.

This PR adds a method to set the current connection state by filtering the list returned from NETunnelProviderManager.loadAllFromPreferences() by the extension name and return the current VPN status.
When app becomes active, this method will be called before polling starts to properly set the session and report the right connection state to UI.

Summary by CodeRabbit

  • Bug Fixes

    • App now loads and displays the actual VPN/extension connection state immediately on activation, preventing a brief “disconnected” display at launch.
    • Polling of connection details now starts when the app becomes active and stops when it goes inactive or backgrounded, keeping status updates timely.
  • Chores

    • App version updated to 0.0.16.

✏️ Tip: You can customize this high-level summary in your review settings.

By checking if there's a VPN Manager instance from preferences
whose name matches the extension's. If so, it sets the extension's
session with its connection, and returns the current VPN status.
Returns false, otherwise.
This will be used to both report the current connection state properly
and configure the ExtensionAdpater's session before polling.

This doesn't start or create a new VPN manager, it only checks for one
that is already running in case the VPN is connected by the time the app
cold starts.
Copilot AI review requested due to automatic review settings January 23, 2026 14:56
@coderabbitai
Copy link

coderabbitai bot commented Jan 23, 2026

📝 Walkthrough

Walkthrough

NetBirdApp now creates and cancels a MainActor-bound activationTask on activation/resign events to asynchronously load the current VPN extension state via NetworkExtensionAdapter.loadCurrentConnectionState(), apply it to the view model, then continue with existing checks and polling. A new async API was added to NetworkExtensionAdapter and project version/configs were bumped.

Changes

Cohort / File(s) Summary
App lifecycle / activation task
NetBird/Source/App/NetBirdApp.swift
Added @State private var activationTask: Task<Void, Never>?. On app/scene becoming active: cancel prior task, start a MainActor Task that calls networkExtensionAdapter.loadCurrentConnectionState(), applies initial extension state to viewModel.extensionState, then calls existing checkExtensionState, checkLoginRequiredFlag, and startPollingDetails. On resign/inactive/background: cancel task and stop polling.
Network extension adapter API
NetbirdKit/NetworkExtensionAdapter.swift
Added public async method func loadCurrentConnectionState() async -> NEVPNStatus? which loads VPN managers from preferences, selects the manager matching the extension name, updates internal vpnManager/session, and returns current NEVPNStatus or nil on error/not found.
Project metadata / build settings
NetBird.xcodeproj/project.pbxproj
Bumped MARKETING_VERSION 0.0.15 → 0.0.16 and adjusted LIBRARY_SEARCH_PATHS to "$(inherited)"; changed CURRENT_PROJECT_VERSION values in target configurations. Review build settings for intended versioning and search-path semantics.

Sequence Diagram(s)

sequenceDiagram
    participant App as NetBirdApp (Lifecycle)
    participant Task as activationTask
    participant Adapter as NetworkExtensionAdapter
    participant Manager as NEVPNManager
    participant ViewModel as ViewModel

    App->>Task: cancel existing (if any)
    App->>Task: start MainActor Task
    activate Task
    Task->>Adapter: loadCurrentConnectionState()
    activate Adapter
    Adapter->>Manager: loadFromPreferences()
    Manager-->>Adapter: vpnManagers
    Adapter->>Adapter: select matching extension\nupdate vpnManager & session
    Adapter-->>Task: NEVPNStatus? (or nil)
    deactivate Adapter
    Task->>ViewModel: set extensionState (initialStatus)
    Task->>App: checkExtensionState()
    Task->>App: checkLoginRequiredFlag()
    Task->>App: startPollingDetails()
    deactivate Task
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • pascal-fischer
  • mlsmaycon

Poem

🐰
Hopped awake as the app came alive,
I fetched the state so displays won't contrive,
Task cancelled, restarted, kept tidy and neat,
Now connection shows true on every heartbeat —
A rabbit's hop to make startup complete.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main issue being fixed: preventing the UI from reporting incorrect VPN connection state when the VPN is already running.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@NetBird/Source/App/NetBirdApp.swift`:
- Around line 63-76: The async loadCurrentConnectionState() call can finish
after the app has become inactive and inadvertently trigger
startPollingDetails(); to fix, create and store the Task when handling
UIApplication.didBecomeActiveNotification (and the tvOS equivalent), keep a
cancellable reference on the view (or viewModel), and cancel that Task when
willResignActiveNotification fires; after awaiting loadCurrentConnectionState(),
check the current active state (or Task.isCancelled) before setting
viewModel.extensionState and before calling viewModel.checkExtensionState(),
viewModel.checkLoginRequiredFlag(), and viewModel.startPollingDetails() so
polling is not restarted while the app is backgrounded.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@doromaraujo doromaraujo merged commit 72ab3a5 into main Jan 23, 2026
5 checks passed
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.

3 participants