Skip to content

Commit 60006e6

Browse files
KM-13372 Add async version of install function and add logs to help understanding where it fails
1 parent 60c6090 commit 60006e6

8 files changed

Lines changed: 160 additions & 126 deletions

File tree

LocalPackages/PIALibrary/Sources/PIALibrary/ClientError.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public enum ClientError: Error, Equatable {
5252
/// The specified VPN profile protocol is unavailable.
5353
case vpnProfileUnavailable
5454

55+
/// The specified VPN type is not supported or has no profile configuration.
56+
case unsupportedVPNType
57+
5558
/// Error while checking the dip token renewal.
5659
case dipTokenRenewalError
5760

LocalPackages/PIALibrary/Sources/PIALibrary/Mock/MockVPNProvider.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ public class MockVPNProvider: VPNProvider, ConfigurationAccess, DatabaseAccess {
7777
callback?(nil)
7878
}
7979

80+
/// :nodoc:
81+
public func install(force forceInstall: Bool) async throws {
82+
Macros.postNotification(.PIAVPNDidInstall)
83+
}
84+
8085
/// :nodoc:
8186
public func uninstall(_ callback: SuccessLibraryCallback?) {
8287
callback?(nil)

LocalPackages/PIALibrary/Sources/PIALibrary/VPN/DefaultVPNProvider.swift

Lines changed: 115 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import NetworkExtension
2727
fileprivate let log = PIALogger.logger(for: DefaultVPNProvider.self)
2828

2929
@available(tvOS 17.0, *)
30-
open class DefaultVPNProvider: VPNProvider, ConfigurationAccess, DatabaseAccess, PreferencesAccess, ProvidersAccess, WebServicesAccess {
30+
open class DefaultVPNProvider: ConfigurationAccess, DatabaseAccess, PreferencesAccess, ProvidersAccess, WebServicesAccess {
3131

3232
private static let forcedStatuses: [VPNStatus] = [
3333
.connected,
@@ -48,27 +48,6 @@ open class DefaultVPNProvider: VPNProvider, ConfigurationAccess, DatabaseAccess,
4848
}
4949
}
5050

51-
// MARK: VPNProvider
52-
53-
public var availableVPNTypes: [String] {
54-
return accessedConfiguration.availableVPNTypes()
55-
}
56-
57-
public var currentVPNType: String {
58-
return accessedPreferences.vpnType
59-
}
60-
61-
public var vpnStatus: VPNStatus {
62-
return accessedDatabase.transient.vpnStatus
63-
}
64-
65-
public var profileServer: Server? {
66-
guard let identifier = activeProfile?.serverIdentifier else {
67-
return nil
68-
}
69-
return accessedProviders.serverProvider.find(withIdentifier: identifier)
70-
}
71-
7251
var publicIP: String? {
7352
return accessedDatabase.plain.publicIP
7453
}
@@ -90,6 +69,96 @@ open class DefaultVPNProvider: VPNProvider, ConfigurationAccess, DatabaseAccess,
9069
}
9170
}
9271

72+
private func isLegacyProfile() -> Bool {
73+
return DefaultVPNProvider.legacyProtocols.contains(accessedPreferences.vpnType)
74+
}
75+
76+
@discardableResult private func activeProfileRemovingInactive() -> VPNProfile? {
77+
let activeVPNType = accessedPreferences.vpnType
78+
let activeProfile: VPNProfile? = accessedConfiguration.profile(forVPNType: activeVPNType)
79+
80+
for vpnType in availableVPNTypes {
81+
let profile = accessedConfiguration.profile(forVPNType: vpnType)!
82+
guard (vpnType == activeVPNType) else {
83+
if let activeProfile = activeProfile {
84+
if !((profile.vpnType == IPSecProfile.vpnType || profile.vpnType == IKEv2Profile.vpnType) &&
85+
(activeProfile.vpnType == IPSecProfile.vpnType || activeProfile.vpnType == IKEv2Profile.vpnType)) {
86+
//only remove the profile if is not Ipsec or IKEv2, if are one of them, override instead
87+
profile.disconnect(nil)
88+
profile.remove(nil)
89+
}
90+
}
91+
continue
92+
}
93+
}
94+
return activeProfile
95+
}
96+
97+
private func vpnClientConfiguration(for profile: VPNProfile? = nil) -> VPNConfiguration? {
98+
guard let currentUser = accessedProviders.accountProvider.currentUser else {
99+
log.error("vpnClientConfiguration: No current user available")
100+
return nil
101+
}
102+
103+
guard let currentPasswordReference = accessedProviders.accountProvider.currentPasswordReference else {
104+
log.error("vpnClientConfiguration: No current password reference available")
105+
return nil
106+
}
107+
108+
guard let profile = profile ?? activeProfile else {
109+
log.error("vpnClientConfiguration: No VPN profile available")
110+
return nil
111+
}
112+
113+
guard let targetServer = try? accessedProviders.serverProvider.targetServer else {
114+
log.error("vpnClientConfiguration: No target server available")
115+
return nil
116+
}
117+
118+
let customConfiguration = accessedPreferences.vpnCustomConfiguration(for: profile.vpnType)
119+
120+
return VPNConfiguration(
121+
name: accessedConfiguration.vpnProfileName,
122+
username: currentUser.credentials.username,
123+
passwordReference: currentPasswordReference,
124+
server: targetServer,
125+
isOnDemand: accessedPreferences.isPersistentConnection,
126+
disconnectsOnSleep: accessedPreferences.vpnDisconnectsOnSleep,
127+
customConfiguration: customConfiguration,
128+
leakProtection: accessedPreferences.leakProtection,
129+
allowLocalDeviceAccess: accessedPreferences.allowLocalDeviceAccess
130+
)
131+
}
132+
133+
// MARK: WebServicesConsumer
134+
135+
var webServices: WebServices {
136+
return customWebServices ?? accessedWebServices
137+
}
138+
}
139+
140+
// MARK: - VPNProvider extension
141+
142+
extension DefaultVPNProvider: VPNProvider {
143+
public var availableVPNTypes: [String] {
144+
return accessedConfiguration.availableVPNTypes()
145+
}
146+
147+
public var currentVPNType: String {
148+
return accessedPreferences.vpnType
149+
}
150+
151+
public var vpnStatus: VPNStatus {
152+
return accessedDatabase.transient.vpnStatus
153+
}
154+
155+
public var profileServer: Server? {
156+
guard let identifier = activeProfile?.serverIdentifier else {
157+
return nil
158+
}
159+
return accessedProviders.serverProvider.find(withIdentifier: identifier)
160+
}
161+
93162
public func prepare() throws {
94163

95164
var profile = activeProfileRemovingInactive()
@@ -139,42 +208,45 @@ open class DefaultVPNProvider: VPNProvider, ConfigurationAccess, DatabaseAccess,
139208

140209
public func install(force forceInstall: Bool, _ callback: SuccessLibraryCallback?) {
141210
guard accessedProviders.accountProvider.isLoggedIn else {
211+
log.error("VPN install failed: User is not logged in (unauthorized)")
142212
callback?(ClientError.unauthorized)
143213
return
144214
}
145215

146216
let newVPNType = accessedPreferences.vpnType
147217
guard let profile = accessedConfiguration.profile(forVPNType: newVPNType) else {
148-
callback?(ClientError.vpnProfileUnavailable)
218+
log.error("VPN install failed: No profile configuration found for VPN type: \(newVPNType)")
219+
callback?(ClientError.unsupportedVPNType)
149220
return
150221
}
151222

152223
var previousProfile: VPNProfile?
153-
if (newVPNType != activeProfile?.vpnType) {
224+
if newVPNType != activeProfile?.vpnType {
154225
previousProfile = activeProfile
155226
}
156227

157228
let forcedStatuses = DefaultVPNProvider.forcedStatuses.contains(accessedDatabase.transient.vpnStatus)
158-
let installBlock: SuccessLibraryCallback = { (error) in
229+
let installBlock: SuccessLibraryCallback = { error in
159230
guard let configuration = self.vpnClientConfiguration(for: profile) else {
160231
callback?(ClientError.vpnProfileUnavailable)
161232
return
162233
}
163-
profile.save(withConfiguration: configuration, force: forcedStatuses) { (error) in
164-
if let error = error {
234+
profile.save(withConfiguration: configuration, force: forcedStatuses) { error in
235+
if let error {
236+
log.error("VPN install failed: Profile save failed with error: \(error.localizedDescription)")
165237
callback?(error)
166238
return
167239
}
168240
self.activeProfile = profile
169241

170-
if let previousProfile = previousProfile,
242+
if let previousProfile,
171243
!((profile.vpnType == IPSecProfile.vpnType || profile.vpnType == IKEv2Profile.vpnType) &&
172244
(previousProfile.vpnType == IPSecProfile.vpnType || previousProfile.vpnType == IKEv2Profile.vpnType)) {
173245
//only remove the profile if is not Ipsec or IKEv2, if are one of them, override instead
174-
previousProfile.remove({ _ in
246+
previousProfile.remove { _ in
175247
Macros.postNotification(.PIAVPNDidInstall)
176248
callback?(nil)
177-
})
249+
}
178250
} else {
179251
if previousProfile != nil { // dont connect after install
180252
self.connect(nil)
@@ -196,7 +268,19 @@ open class DefaultVPNProvider: VPNProvider, ConfigurationAccess, DatabaseAccess,
196268
}
197269
}
198270
}
199-
271+
272+
public func install(force forceInstall: Bool) async throws {
273+
return try await withCheckedThrowingContinuation { continuation in
274+
install(force: forceInstall) { error in
275+
if let error = error {
276+
continuation.resume(throwing: error)
277+
} else {
278+
continuation.resume()
279+
}
280+
}
281+
}
282+
}
283+
200284
public func disable(_ callback: SuccessLibraryCallback?) {
201285
guard let activeProfile = activeProfile else {
202286
callback?(ClientError.vpnProfileUnavailable)
@@ -359,75 +443,7 @@ open class DefaultVPNProvider: VPNProvider, ConfigurationAccess, DatabaseAccess,
359443
callback?(usage, nil)
360444
}
361445
}
362-
363-
private func isLegacyProfile() -> Bool {
364-
return DefaultVPNProvider.legacyProtocols.contains(accessedPreferences.vpnType)
365-
}
366-
367-
@discardableResult private func activeProfileRemovingInactive() -> VPNProfile? {
368-
let activeVPNType = accessedPreferences.vpnType
369-
let activeProfile: VPNProfile? = accessedConfiguration.profile(forVPNType: activeVPNType)
370-
371-
for vpnType in availableVPNTypes {
372-
let profile = accessedConfiguration.profile(forVPNType: vpnType)!
373-
guard (vpnType == activeVPNType) else {
374-
if let activeProfile = activeProfile {
375-
if !((profile.vpnType == IPSecProfile.vpnType || profile.vpnType == IKEv2Profile.vpnType) &&
376-
(activeProfile.vpnType == IPSecProfile.vpnType || activeProfile.vpnType == IKEv2Profile.vpnType)) {
377-
//only remove the profile if is not Ipsec or IKEv2, if are one of them, override instead
378-
profile.disconnect(nil)
379-
profile.remove(nil)
380-
}
381-
}
382-
continue
383-
}
384-
}
385-
return activeProfile
386-
}
387-
388-
private func vpnClientConfiguration(for profile: VPNProfile? = nil) -> VPNConfiguration? {
389-
guard let currentUser = accessedProviders.accountProvider.currentUser else {
390-
log.error("vpnClientConfiguration: No current user available")
391-
return nil
392-
}
393-
394-
guard let currentPasswordReference = accessedProviders.accountProvider.currentPasswordReference else {
395-
log.error("vpnClientConfiguration: No current password reference available")
396-
return nil
397-
}
398-
399-
guard let profile = profile ?? activeProfile else {
400-
log.error("vpnClientConfiguration: No VPN profile available")
401-
return nil
402-
}
403-
404-
guard let targetServer = try? accessedProviders.serverProvider.targetServer else {
405-
log.error("vpnClientConfiguration: No target server available")
406-
return nil
407-
}
408-
409-
let customConfiguration = accessedPreferences.vpnCustomConfiguration(for: profile.vpnType)
410446

411-
return VPNConfiguration(
412-
name: accessedConfiguration.vpnProfileName,
413-
username: currentUser.credentials.username,
414-
passwordReference: currentPasswordReference,
415-
server: targetServer,
416-
isOnDemand: accessedPreferences.isPersistentConnection,
417-
disconnectsOnSleep: accessedPreferences.vpnDisconnectsOnSleep,
418-
customConfiguration: customConfiguration,
419-
leakProtection: accessedPreferences.leakProtection,
420-
allowLocalDeviceAccess: accessedPreferences.allowLocalDeviceAccess
421-
)
422-
}
423-
424-
// MARK: WebServicesConsumer
425-
426-
var webServices: WebServices {
427-
return customWebServices ?? accessedWebServices
428-
}
429-
430-
// MARK: Migration
431447
public func needsMigrationToGEN4() -> Bool {
432448
if isVPNConnected {
433449
let manager = NEVPNManager.shared()

LocalPackages/PIALibrary/Sources/PIALibrary/VPN/VPNProvider.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ public protocol VPNProvider: AnyObject {
5454
*/
5555
func install(force forceInstall: Bool, _ callback: SuccessLibraryCallback?)
5656

57+
/**
58+
Installs the profile as per `currentVPNType`.
59+
60+
- Parameter forceInstall: Force the install of the profile.
61+
- Throws: Error if installation fails.
62+
*/
63+
func install(force forceInstall: Bool) async throws
64+
5765
/**
5866
Disables the current profile.
5967

@@ -129,7 +137,7 @@ public protocol VPNProvider: AnyObject {
129137
}
130138

131139
public extension VPNProvider {
132-
public func reconnect(after delay: Int?, forceDisconnect: Bool = false, _ callback: SuccessLibraryCallback?) {
140+
func reconnect(after delay: Int?, forceDisconnect: Bool = false, _ callback: SuccessLibraryCallback?) {
133141
return reconnect(after: delay, forceDisconnect: forceDisconnect, callback)
134142
}
135143
}

PIA VPN-tvOS/VPNConfigurationInstalling/Data/InstallVpnConfigurationProvider.swift

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,12 @@ class InstallVpnConfigurationProvider: InstallVPNConfigurationUseCaseType {
1818
self.vpnConfigurationAvailability = vpnConfigurationAvailability
1919
}
2020

21-
2221
func callAsFunction() async throws {
23-
return try await withCheckedThrowingContinuation { continuation in
24-
vpnProvider.install(force: true) { [self] error in
25-
if error != nil {
26-
continuation.resume(throwing: InstallVPNConfigurationError.userCanceled)
27-
return
28-
}
29-
30-
vpnConfigurationAvailability.set(value: true)
31-
continuation.resume()
32-
}
22+
do {
23+
try await vpnProvider.install(force: true)
24+
vpnConfigurationAvailability.set(value: true)
25+
} catch {
26+
throw InstallVPNConfigurationError.userCanceled
3327
}
3428
}
3529
}

PIA VPN-tvOS/VPNConfigurationInstalling/Data/VpnConfigurationProviderType.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import Foundation
1010
import PIALibrary
1111

1212
protocol VpnConfigurationProviderType {
13-
func install(force forceInstall: Bool, _ callback: SuccessLibraryCallback?)
14-
13+
func install(force forceInstall: Bool) async throws
1514
func uninstall(_ callback: SuccessLibraryCallback?)
1615
}
1716

@@ -21,11 +20,11 @@ class VpnConfigurationProvider: VpnConfigurationProviderType {
2120
init(vpnProvider: VPNProvider) {
2221
self.vpnProvider = vpnProvider
2322
}
24-
25-
func install(force forceInstall: Bool, _ callback: PIALibrary.SuccessLibraryCallback?) {
26-
vpnProvider.install(force: forceInstall, callback)
23+
24+
func install(force forceInstall: Bool) async throws {
25+
try await vpnProvider.install(force: forceInstall)
2726
}
28-
27+
2928
func uninstall(_ callback: SuccessLibraryCallback?) {
3029
self.vpnProvider.uninstall(callback)
3130
}

PIA VPN-tvOSTests/VPNConfigurationInstalling/Mocks/VpnConfigurationProviderTypeMock.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ class VpnConfigurationProviderTypeMock: VpnConfigurationProviderType {
2121
func install(force forceInstall: Bool, _ callback: SuccessLibraryCallback?) {
2222
callback?(error)
2323
}
24-
24+
25+
func install(force forceInstall: Bool) async throws {
26+
if let error = error {
27+
throw error
28+
}
29+
}
30+
2531
func uninstall(_ callback: PIALibrary.SuccessLibraryCallback?) {
2632
callback?(error)
2733
}

0 commit comments

Comments
 (0)