mirror of
https://github.com/netbirdio/ios-client.git
synced 2026-09-23 09:28:33 -07:00
* Fix exit node * Bound the route IPC round-trip so its completion always fires sendProviderMessage only calls its reply handler if the extension answers. An extension that never does - wedged in a synchronous Go call, gone, or returning out of handleAppMessage before replying - stranded the completion that getRoutes/selectRoutes/deselectRoutes promise on every path. That also hung the DispatchGroup RoutesViewModel.selectRoute balances around the sibling deselects, so the pending select was never sent and the optimistic exit node selection was never reverted. Bound the wait with a DispatchWorkItem and deliver exactly one outcome through an NSLock-guarded latch, cancelling the timeout when a reply or a send error completes the request - the same pattern fetchData already uses for the status round-trip. Adds RouteRequestError.timedOut. Also promotes sendSelectAndReconcile's comment to a doc comment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Keep the On Demand preference when a re-arm is refused rearmOnDemandIfNeeded wrote connectOnDemand = false on .failed, guarded only by `guard self.connectOnDemand`. But true is exactly the value a newer, successful re-arm - or the user's own Settings toggle - leaves behind, so that guard cannot tell a stale failure from a fresh success. Nothing pinned the completion to the request that started it. The window is wide: applyOnDemandState only reports .failed after recoverFromFailedOnDemandWrite has run a full loadFromPreferences round-trip. Two overlapping re-arms are enough to hit it with no user involved - applyStatusSideEffects(.connected) runs from both applyExtensionStatus and startActivation, and the very reload that clears configurationStale is what lets the second attempt succeed. The late failure from the first then clobbered rules that were genuinely armed. The write was also wrong in kind. Unlike setConnectOnDemand it makes no optimistic write of its own, so there is no previous value to roll back to; and false closes the guard, so a single transient NEVPNError.configurationStale would discard the user's setting for good instead of letting the next connect retry. "Preference on, rules not armed yet" is a state the app already creates on purpose - see checkLoginRequiredFlag(). Log the refusal and leave the intent standing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>