From 9951e827270339b4eeef8a3bdb994103b1448ff7 Mon Sep 17 00:00:00 2001 From: Jamil Date: Fri, 16 May 2025 02:27:57 -0700 Subject: [PATCH] feat(apple): Disable the update checker for MDM and App store (#9167) For App Store installed macOS clients, it doesn't make sense to run an update checker, because the system is managing the updates, and will notify the user if there's an update available for Firezone (the user has configured the system to manage app updates). Related: #7664 Related: #4505 --- .../FirezoneKit/Models/Configuration.swift | 6 +++ .../Sources/FirezoneKit/Views/MenuBar.swift | 3 +- .../Views/UpdateNotification.swift | 46 +++++++++++++++---- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Models/Configuration.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Models/Configuration.swift index 8229d21e8..386bcedbc 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Models/Configuration.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Models/Configuration.swift @@ -13,6 +13,7 @@ public class Configuration: Codable { public static let defaultAccountSlug = "" public static let defaultConnectOnStart = true + public static let defaultDisableUpdateCheck = false public struct Keys { public static let authURL = "authURL" @@ -23,6 +24,7 @@ public class Configuration: Codable { public static let firezoneId = "firezoneId" public static let hideAdminPortalMenuItem = "hideAdminPortalMenuItem" public static let connectOnStart = "connectOnStart" + public static let disableUpdateCheck = "disableUpdateCheck" } public var authURL: String? @@ -33,6 +35,7 @@ public class Configuration: Codable { public var internetResourceEnabled: Bool? public var hideAdminPortalMenuItem: Bool? public var connectOnStart: Bool? + public var disableUpdateCheck: Bool? private var overriddenKeys: Set = [] @@ -52,6 +55,9 @@ public class Configuration: Codable { setValue(forKey: Keys.connectOnStart, from: managedDict, and: userDict) { [weak self] in self?.connectOnStart = $0 } + setValue(forKey: Keys.disableUpdateCheck, from: managedDict, and: userDict) { [weak self] in + self?.disableUpdateCheck = $0 + } } func isOverridden(_ key: String) -> Bool { diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift index ddead0adf..28503b3f4 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift @@ -25,7 +25,7 @@ public final class MenuBar: NSObject, ObservableObject { var lastShownOthers: [Resource] = [] var wasInternetResourceEnabled: Bool? var cancellables: Set = [] - var updateChecker: UpdateChecker = UpdateChecker() + var updateChecker: UpdateChecker var updateMenuDisplayed: Bool = false var signedOutIcon: NSImage? var signedInConnectedIcon: NSImage? @@ -167,6 +167,7 @@ public final class MenuBar: NSObject, ObservableObject { public init(store: Store) { statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) self.store = store + self.updateChecker = UpdateChecker(store: store) self.signedOutIcon = NSImage(named: "MenuBarIconSignedOut") self.signedInConnectedIcon = NSImage(named: "MenuBarIconSignedInConnected") self.signedOutIconNotification = NSImage(named: "MenuBarIconSignedOutNotification") diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/UpdateNotification.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/UpdateNotification.swift index daf9240f0..5a4b46561 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/UpdateNotification.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/UpdateNotification.swift @@ -7,9 +7,11 @@ // Note: it should be easy to expand this module to iOS #if os(macOS) import Foundation +import Combine import UserNotifications import Cocoa +@MainActor class UpdateChecker { enum UpdateError: Error { case invalidVersion(String) @@ -26,10 +28,13 @@ class UpdateChecker { private let notificationAdapter: NotificationAdapter = NotificationAdapter() private let versionCheckUrl: URL private let marketingVersion: SemanticVersion + private let store: Store - @MainActor @Published private(set) var updateAvailable: Bool = false + private var cancellables: Set = [] - init() { + @Published private(set) var updateAvailable: Bool = false + + init(store: Store) { guard let versionCheckUrl = URL(string: "https://www.firezone.dev/api/releases"), let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String, let marketingVersion = try? SemanticVersion(versionString) @@ -40,20 +45,49 @@ class UpdateChecker { self.versionCheckUrl = versionCheckUrl self.marketingVersion = marketingVersion - startCheckingForUpdates() + self.store = store + + store.$configuration + .receive(on: RunLoop.main) + .sink { [weak self] _ in + self?.handleConfigurationChange() + } + .store(in: &cancellables) + + handleConfigurationChange() + } + + private func handleConfigurationChange() { + let disabled = ( + store.configuration?.disableUpdateCheck ?? Configuration.defaultDisableUpdateCheck + ) || BundleHelper.isAppStore() + + if disabled { + stopCheckingForUpdates() + } else { + startCheckingForUpdates() + } } private func startCheckingForUpdates() { - timer = Timer.scheduledTimer( + guard timer == nil else { return } + + self.timer = Timer.scheduledTimer( timeInterval: 6 * 60 * 60, target: self, selector: #selector(checkForUpdates), userInfo: nil, repeats: true ) + checkForUpdates() } + private func stopCheckingForUpdates() { + timer?.invalidate() + self.timer = nil + } + deinit { timer?.invalidate() } @@ -110,10 +144,6 @@ class UpdateChecker { } static func downloadURL() -> URL { - if BundleHelper.isAppStore() { - return URL(string: "https://apps.apple.com/app/firezone/id6443661826")! - } - return URL(string: "https://www.firezone.dev/dl/firezone-client-macos/latest")! } }