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")! } }