@@ -29,7 +29,16 @@ protocol SystemExtensionAsyncRecorder: Sendable {
2929extension CoderVPNService : SystemExtensionAsyncRecorder {
3030func recordSystemExtensionState( _ state: SystemExtensionState ) async {
3131 sysExtnState= state
32+ if state== . uninstalled{
33+ installSystemExtension ( )
34+ }
3235if state== . installed{
36+ do {
37+ try await getTunnelManager ( )
38+ neState= . disabled
39+ } catch {
40+ neState= . unconfigured
41+ }
3342 // system extension was successfully installed, so we don't need the delegate any more
3443 systemExtnDelegate= nil
3544}
@@ -64,7 +73,21 @@ extension CoderVPNService: SystemExtensionAsyncRecorder {
6473return extensionBundle
6574}
6675
67- func installSystemExtension( ) {
76+ func checkSystemExtensionStatus( ) {
77+ logger. info ( " checking SystemExtension status " )
78+ guard let bundleID= extensionBundle. bundleIdentifierelse {
79+ logger. error ( " Bundle has no identifier " )
80+ return
81+ }
82+ let request = OSSystemExtensionRequest . propertiesRequest ( forExtensionWithIdentifier: bundleID, queue: . main)
83+ let delegate = SystemExtensionDelegate ( asyncDelegate: self )
84+ request. delegate= delegate
85+ systemExtnDelegate= delegate
86+ OSSystemExtensionManager . shared. submitRequest ( request)
87+ logger. info ( " submitted SystemExtension properties request with bundleID: \( bundleID) " )
88+ }
89+
90+ private func installSystemExtension( ) {
6891 logger. info ( " activating SystemExtension " )
6992guard let bundleID= extensionBundle. bundleIdentifierelse {
7093 logger. error ( " Bundle has no identifier " )
@@ -74,11 +97,9 @@ extension CoderVPNService: SystemExtensionAsyncRecorder {
7497 forExtensionWithIdentifier: bundleID,
7598 queue: . main
7699)
77- let delegate = SystemExtensionDelegate ( asyncDelegate: self )
78- systemExtnDelegate= delegate
79- request. delegate= delegate
100+ request. delegate= systemExtnDelegate
80101OSSystemExtensionManager . shared. submitRequest ( request)
81- logger. info ( " submitted SystemExtension request with bundleID: \( bundleID) " )
102+ logger. info ( " submitted SystemExtensionactivate request with bundleID: \( bundleID) " )
82103}
83104}
84105
@@ -88,6 +109,8 @@ class SystemExtensionDelegate<AsyncDelegate: SystemExtensionAsyncRecorder>:
88109NSObject , OSSystemExtensionRequestDelegate
89110{
90111private var logger = Logger ( subsystem: Bundle . main. bundleIdentifier!, category: " vpn-installer " )
112+ // TODO: Refactor this to use a continuation, so the result of a request can be
113+ // 'await'd for the determined state
91114private var asyncDelegate : AsyncDelegate
92115
93116init ( asyncDelegate: AsyncDelegate ) {
@@ -138,4 +161,38 @@ class SystemExtensionDelegate<AsyncDelegate: SystemExtensionAsyncRecorder>:
138161 logger. info ( " Replacing \( request. identifier) v \( existing. bundleShortVersion) with v \( `extension`. bundleShortVersion) " )
139162return . replace
140163}
164+
165+ public func request(
166+ _: OSSystemExtensionRequest ,
167+ foundProperties properties: [ OSSystemExtensionProperties ]
168+ ) {
169+ // In debug builds we always replace the SE to test
170+ // changes made without bumping the version
171+ #if DEBUG
172+ Task { [ asyncDelegate] in
173+ await asyncDelegate. recordSystemExtensionState ( . uninstalled)
174+ }
175+ return
176+ #else
177+ let version = Bundle . main. object ( forInfoDictionaryKey: " CFBundleVersion " ) as? String
178+ let shortVersion = Bundle . main. object ( forInfoDictionaryKey: " CFBundleShortVersionString " ) as? String
179+
180+ let versionMatches = properties. contains { sysexin
181+ sysex. isEnabled
182+ && sysex. bundleVersion== version
183+ && sysex. bundleShortVersion== shortVersion
184+ }
185+ if versionMatches{
186+ Task { [ asyncDelegate] in
187+ await asyncDelegate. recordSystemExtensionState ( . installed)
188+ }
189+ return
190+ }
191+
192+ // Either uninstalled or needs replacing
193+ Task { [ asyncDelegate] in
194+ await asyncDelegate. recordSystemExtensionState ( . uninstalled)
195+ }
196+ #endif
197+ }
141198}