diff --git a/Coder-Desktop/Coder-Desktop/UpdaterService.swift b/Coder-Desktop/Coder-Desktop/UpdaterService.swift index ce7bc9d..c0f5eaa 100644 --- a/Coder-Desktop/Coder-Desktop/UpdaterService.swift +++ b/Coder-Desktop/Coder-Desktop/UpdaterService.swift @@ -2,42 +2,59 @@ import Sparkle import SwiftUI final class UpdaterService: NSObject, ObservableObject { - private lazy var inner: SPUStandardUpdaterController = .init( - startingUpdater: true, - updaterDelegate: self, - userDriverDelegate: self - ) - private var updater: SPUUpdater! + // The auto-updater can be entirely disabled by setting the + // `disableUpdater` UserDefaults key to `true`. This is designed for use in + // MDM configurations, where the value can be set to `true` permanently. + let disabled: Bool = UserDefaults.standard.bool(forKey: Keys.disableUpdater) + @Published var canCheckForUpdates = true @Published var autoCheckForUpdates: Bool! { didSet { if let autoCheckForUpdates, autoCheckForUpdates != oldValue { - updater.automaticallyChecksForUpdates = autoCheckForUpdates + inner?.updater.automaticallyChecksForUpdates = autoCheckForUpdates } } } @Published var updateChannel: UpdateChannel { didSet { - UserDefaults.standard.set(updateChannel.rawValue, forKey: Self.updateChannelKey) + UserDefaults.standard.set(updateChannel.rawValue, forKey: Keys.updateChannel) } } - static let updateChannelKey = "updateChannel" + private var inner: (controller: SPUStandardUpdaterController, updater: SPUUpdater)? override init() { - updateChannel = UserDefaults.standard.string(forKey: Self.updateChannelKey) + updateChannel = UserDefaults.standard.string(forKey: Keys.updateChannel) .flatMap { UpdateChannel(rawValue: $0) } ?? .stable super.init() - updater = inner.updater + + guard !disabled else { + return + } + + let inner = SPUStandardUpdaterController( + startingUpdater: true, + updaterDelegate: self, + userDriverDelegate: self + ) + + let updater = inner.updater + self.inner = (inner, updater) + autoCheckForUpdates = updater.automaticallyChecksForUpdates updater.publisher(for: \.canCheckForUpdates).assign(to: &$canCheckForUpdates) } func checkForUpdates() { - guard canCheckForUpdates else { return } - updater.checkForUpdates() + guard let inner, canCheckForUpdates else { return } + inner.updater.checkForUpdates() + } + + enum Keys { + static let disableUpdater = "disableUpdater" + static let updateChannel = "updateChannel" } } diff --git a/Coder-Desktop/Coder-Desktop/VPN/VPNService.swift b/Coder-Desktop/Coder-Desktop/VPN/VPNService.swift index 6ebcba9..de8fa86 100644 --- a/Coder-Desktop/Coder-Desktop/VPN/VPNService.swift +++ b/Coder-Desktop/Coder-Desktop/VPN/VPNService.swift @@ -56,7 +56,7 @@ final class CoderVPNService: NSObject, VPNService { var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "vpn") lazy var xpc: HelperXPCClient = .init(vpn: self) - @Published var tunnelState: VPNServiceState = .disabled { + @Published private(set) var tunnelState: VPNServiceState = .disabled { didSet { if tunnelState == .connecting { progress = .init(stage: .initial, downloadProgress: nil) @@ -80,9 +80,9 @@ final class CoderVPNService: NSObject, VPNService { return tunnelState } - @Published var progress: VPNProgress = .init(stage: .initial, downloadProgress: nil) + @Published private(set) var progress: VPNProgress = .init(stage: .initial, downloadProgress: nil) - @Published var menuState: VPNMenuState = .init() + @Published private(set) var menuState: VPNMenuState = .init() // Whether the VPN should start as soon as possible var startWhenReady: Bool = false diff --git a/Coder-Desktop/Coder-Desktop/Views/FileSync/FilePicker.swift b/Coder-Desktop/Coder-Desktop/Views/FileSync/FilePicker.swift index 9ec2623..24e938a 100644 --- a/Coder-Desktop/Coder-Desktop/Views/FileSync/FilePicker.swift +++ b/Coder-Desktop/Coder-Desktop/Views/FileSync/FilePicker.swift @@ -69,9 +69,9 @@ struct FilePicker: View { @MainActor class FilePickerModel: ObservableObject { - @Published var rootEntries: [FilePickerEntryModel] = [] - @Published var rootIsLoading: Bool = false - @Published var error: SDKError? + @Published private(set) var rootEntries: [FilePickerEntryModel] = [] + @Published private(set) var rootIsLoading: Bool = false + @Published private(set) var error: SDKError? // It's important that `AgentClient` is a reference type (class) // as we were having performance issues with a struct (unless it was a binding). @@ -153,9 +153,9 @@ class FilePickerEntryModel: Identifiable, Hashable, ObservableObject { let client: AgentClient - @Published var entries: [FilePickerEntryModel]? - @Published var isLoading = false - @Published var error: SDKError? + @Published private(set) var entries: [FilePickerEntryModel]? + @Published private(set) var isLoading = false + @Published private(set) var error: SDKError? @Published private var innerIsExpanded = false var isExpanded: Bool { get { innerIsExpanded } diff --git a/Coder-Desktop/Coder-Desktop/Views/Settings/GeneralTab.swift b/Coder-Desktop/Coder-Desktop/Views/Settings/GeneralTab.swift index 7af41e4..d779a9a 100644 --- a/Coder-Desktop/Coder-Desktop/Views/Settings/GeneralTab.swift +++ b/Coder-Desktop/Coder-Desktop/Views/Settings/GeneralTab.swift @@ -19,18 +19,25 @@ struct GeneralTab: View { Text("Start Coder Connect on launch") } } - Section { - Toggle(isOn: $updater.autoCheckForUpdates) { - Text("Automatically check for updates") - } - Picker("Update channel", selection: $updater.updateChannel) { - ForEach(UpdateChannel.allCases) { channel in - Text(channel.name).tag(channel) + if !updater.disabled { + Section { + Toggle(isOn: $updater.autoCheckForUpdates) { + Text("Automatically check for updates") + } + Picker("Update channel", selection: $updater.updateChannel) { + ForEach(UpdateChannel.allCases) { channel in + Text(channel.name).tag(channel) + } + } + HStack { + Spacer() + Button("Check for updates") { updater.checkForUpdates() }.disabled(!updater.canCheckForUpdates) } } - HStack { - Spacer() - Button("Check for updates") { updater.checkForUpdates() }.disabled(!updater.canCheckForUpdates) + } else { + Section { + Text("The app updater has been disabled by a device management policy.") + .foregroundColor(.secondary) } } }.formStyle(.grouped)
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: