@@ -233,7 +233,7 @@ class TunnelManager: ObservableObject {
233233 }
234234 VPNLogger . shared. log ( " VPN status updated: \( self . tunnelStatus. rawValue) " )
235235 if connectionStatus == . connected && heartbeatStartPending {
236- startHeartbeatInBackground ( )
236+ startHeartbeatInBackground ( showErrorUI : heartbeatPendingShowUI )
237237 }
238238 }
239239 }
@@ -502,6 +502,7 @@ class DNSChecker: ObservableObject {
502502var pubHeartBeat = false
503503private var heartbeatStartPending = false
504504private var heartbeatStartInProgress = false
505+ private var heartbeatPendingShowUI = true
505506
506507@main
507508struct HeartbeatApp : App {
@@ -516,6 +517,7 @@ struct HeartbeatApp: App {
516517 @StateObject private var mount = MountingProgress . shared
517518 @StateObject private var themeExpansionManager = ThemeExpansionManager ( )
518519 @Environment ( \. scenePhase) private var scenePhase // Observe scene lifecycle
520+ @State private var shouldAttemptHeartbeatRestart = false
519521
520522 init ( ) {
521523 registerAdvancedOptionsDefault ( )
@@ -565,6 +567,20 @@ struct HeartbeatApp: App {
565567 }
566568 }
567569
570+ private func handleScenePhaseChange( _ newPhase: ScenePhase ) {
571+ switch newPhase {
572+ case . background:
573+ shouldAttemptHeartbeatRestart = true
574+ case . active:
575+ if shouldAttemptHeartbeatRestart {
576+ shouldAttemptHeartbeatRestart = false
577+ startHeartbeatInBackground ( showErrorUI: false )
578+ }
579+ default :
580+ break
581+ }
582+ }
583+
568584 private var globalAccent : Color {
569585 themeExpansionManager. resolvedAccentColor ( from: customAccentColorHex)
570586 }
@@ -629,6 +645,9 @@ struct HeartbeatApp: App {
629645 HeartbeatApp . updateUIKitTint ( customHex: newHex,
630646 hasAccess: themeExpansionManager. hasThemeExpansion)
631647 }
648+ . onChange ( of: scenePhase) { newPhase in
649+ handleScenePhaseChange ( newPhase)
650+ }
632651 . sheet ( isPresented: $showWelcomeSheet) {
633652 WelcomeSheetView {
634653 // When the user taps "Continue", mark the app as launched and start the VPN if allowed.
@@ -753,12 +772,13 @@ func isPairing() -> Bool {
753772 return true
754773}
755774
756- func startHeartbeatInBackground( requireVPNConnection: Bool ? = nil ) {
775+ func startHeartbeatInBackground( requireVPNConnection: Bool ? = nil , showErrorUI : Bool = true ) {
757776 assert ( Thread . isMainThread, " startHeartbeatInBackground must be called on the main thread " )
758777 let pairingFileURL = URL . documentsDirectory. appendingPathComponent ( " pairingFile.plist " )
759778
760779 guard FileManager . default. fileExists ( atPath: pairingFileURL. path) else {
761780 heartbeatStartPending = false
781+ heartbeatPendingShowUI = true
762782 return
763783 }
764784
@@ -768,6 +788,7 @@ func startHeartbeatInBackground(requireVPNConnection: Bool? = nil) {
768788 if !heartbeatStartPending {
769789 print ( " Heartbeat start deferred until VPN connects " )
770790 }
791+ heartbeatPendingShowUI = showErrorUI
771792 heartbeatStartPending = true
772793 return
773794 }
@@ -777,6 +798,7 @@ func startHeartbeatInBackground(requireVPNConnection: Bool? = nil) {
777798 }
778799
779800 heartbeatStartPending = false
801+ heartbeatPendingShowUI = true
780802 heartbeatStartInProgress = true
781803
782804 DispatchQueue . global ( qos: . userInteractive) . async {
@@ -798,6 +820,7 @@ func startHeartbeatInBackground(requireVPNConnection: Bool? = nil) {
798820 let err2 = error as NSError
799821 let code = err2. code
800822 print ( " Error: \( error. localizedDescription) (Code: \( code) ) " )
823+ guard showErrorUI else { return }
801824 DispatchQueue . main. async {
802825 if code == - 9 {
803826 do {
0 commit comments