refactor(apple): Consolidate configuration to host app#9196
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
|
@thomaseizinger I'm still testing this PR, but it's ready for review. Let me know if you want to pair on it. |
dd498d0 to
a969bd9
Compare
a969bd9 to
b62343d
Compare
25f84a9 to
7cab3ba
Compare
|
@thomaseizinger This is ready. Tested for about 2-3 hours, setting various keys manually and via the command line. Seems pretty solid but I will do another round of QA once I get the managed profiles set up with Kandji. |
There was a problem hiding this comment.
Pull Request Overview
This PR consolidates configuration handling into the host app by removing the dual-dictionary approach in UserDefaults and eliminating the old ConfigurationManager and Settings classes. The tunnel service now uses a simplified TunnelConfiguration type for IPC, and UI and networking components reference the centralized Configuration singleton.
- Centralize UserDefaults-backed config in new
Configurationclass andTunnelConfigurationfor IPC - Update
PacketTunnelProviderandIPCClientto load/save viaTunnelConfiguration - Remove legacy
ConfigurationManager/Settings, update UI views and store to useConfigurationsignals
Reviewed Changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| PacketTunnelProvider.swift | Load/save TunnelConfiguration, migrate firezone-ID, new error |
| ConfigurationManager.swift | Deleted redundant legacy manager |
| UpdateNotification.swift | Refactored to use Configuration, removed reactive start logic |
| SettingsView.swift | Removed SettingsViewModel dep on Settings, use Configuration |
| SettingsViewModel.swift | New view model bound to Configuration |
| SessionView.swift | Switch to Configuration.shared for resource title |
| ResourceView.swift | Introduce ToggleInternetResourceButton with Configuration |
| MenuBar.swift | Inject Configuration, react to forced settings signals |
| Store.swift | Replace optional config with Configuration |
| WebAuthSession.swift | Pass Configuration into auth flow |
| Configuration.swift | New Configuration observable wrapper and TunnelConfiguration |
| IPCClient.swift | Remove old getConfiguration, use TunnelConfiguration |
Comments suppressed due to low confidence (1)
swift/apple/FirezoneKit/Sources/FirezoneKit/Views/SessionView.swift:83
- This helper still reads from
store.configurationinstead of the newConfiguration.sharedforced logic, so it won’t reflect managed overrides correctly. Update it to useconfiguration.isInternetResourceForcedand published values.
let status = store.configuration?.internetResourceEnabled == true ? StatusSymbol.enabled : StatusSymbol.disabled
| guard let apiURL = legacyConfiguration?["apiURL"] ?? tunnelConfiguration?.apiURL, | ||
| let logFilter = legacyConfiguration?["logFilter"] ?? tunnelConfiguration?.logFilter, |
There was a problem hiding this comment.
We dropped the previous default fallbacks (Configuration.defaultApiURL and Configuration.defaultLogFilter). If the stored values are ever missing, this guard will now fail instead of using sensible defaults—consider preserving the original defaults as a fallback.
| guard let apiURL = legacyConfiguration?["apiURL"] ?? tunnelConfiguration?.apiURL, | |
| let logFilter = legacyConfiguration?["logFilter"] ?? tunnelConfiguration?.logFilter, | |
| guard let apiURL = legacyConfiguration?["apiURL"] ?? tunnelConfiguration?.apiURL ?? "https://default.api.url", | |
| let logFilter = legacyConfiguration?["logFilter"] ?? tunnelConfiguration?.logFilter ?? "default", |
| guard let apiURL = legacyConfiguration?["apiURL"] ?? tunnelConfiguration?.apiURL, | ||
| let logFilter = legacyConfiguration?["logFilter"] ?? tunnelConfiguration?.logFilter, | ||
| let accountSlug = legacyConfiguration?["accountSlug"] ?? tunnelConfiguration?.accountSlug | ||
| else { | ||
| throw PacketTunnelProviderError.accountSlugIsInvalid | ||
| throw PacketTunnelProviderError.tunnelConfigurationIsInvalid | ||
| } |
There was a problem hiding this comment.
The single tunnelConfigurationIsInvalid error case hides which specific value was missing (apiURL, logFilter, or accountSlug). Splitting into more descriptive error cases or including context in the error would aid debugging.
| let filename = "firezone-id" | ||
| let key = "firezoneId" |
There was a problem hiding this comment.
[nitpick] Hardcoded string keys like "firezone-id" and later "configurationCache" are duplicated; extracting them into shared constants would reduce typo risk and improve maintainability.
| let filename = "firezone-id" | |
| let key = "firezoneId" | |
| let filename = Constants.firezoneIdFilename | |
| let key = Constants.firezoneIdKey |
| self.store = store | ||
| _viewModel = StateObject(wrappedValue: SettingsViewModel(store: store)) | ||
| self.configuration = configuration ?? Configuration.shared | ||
| _viewModel = StateObject(wrappedValue: SettingsViewModel()) |
There was a problem hiding this comment.
The view model is always initialized with Configuration.shared, even if a custom configuration was injected into SettingsView. Consider passing self.configuration to the SettingsViewModel initializer to keep them in sync.
| _viewModel = StateObject(wrappedValue: SettingsViewModel()) | |
| _viewModel = StateObject(wrappedValue: SettingsViewModel(configuration: self.configuration)) |
09c748d to
163a7da
Compare
| public class Configuration: ObservableObject { | ||
| static let shared = Configuration() | ||
|
|
||
| @Published private(set) var publishedInternetResourceEnabled = false |
There was a problem hiding this comment.
We need separate Published properties here because:
- computed properties cannot be published
- the observers need the actual changed value - observing
configuration.objectWillChangedoesn't provide that
There was a problem hiding this comment.
One thing that might clean this up a little bit is to define a base ConfigurationItem struct that each item inherits from, storing them all in a collection here in the parent. That would help reduce some of the repetitiveness here I think.
There was a problem hiding this comment.
On second thought, we do deviate from pure similarity in cases like calling UserDefaults methods and updating published properties, so maybe it's not worth the cognitive overhead.
jamilbk
left a comment
There was a problem hiding this comment.
@thomaseizinger Will probably need this in main to test further so that I can have a release installer package to provision with Kandji.
Opening a new PR because #9196 is stacked. - Fixes the disabling of the Reset button to properly handle cases where only some fields are overridden - Binds configuration updates to the SettingsViewModel so that fields are properly disable after app launch if the admin sets a configuration
Adds managed configuration support for Android in line with other platforms. Related: firezone#4505 Related: firezone#9203 Related: firezone#9196


On Apple platforms,
UserDefaultsprovides a convenient way to store and fetch simple plist-compatible data for your app. Unbeknownst to the author at the time of original implementation was the fact this these keys are already designed for managed configurations to "mask" any user-configured equivalents.This means we no longer need to juggle two dicts in UserDefaults, and we can instead check which keys are forced via a simple method call.
Additionally, the implementation was simplified in the following ways:
setConfigurationwhich applies the current configuration, and saves it in order to start up again without the GUI connected. The obvious caveat here is that if the GUI isn't running, configuration such asinternetResourceEnabledapplied by the administrator won't take effect. This is considered an edge case for the time being since no customers have asked for this. Additionally, admins can be advised to ensure the Firezone GUI is running on the system at all times to prevent this.Related: #4505