Skip to content

Commit

Permalink
PIA-1173: Add Bootstrapper and monitoring for vpn and auth status
Browse files Browse the repository at this point in the history
  • Loading branch information
kp-said-rehouni committed Jan 21, 2024
1 parent 11c9e89 commit 68e240a
Show file tree
Hide file tree
Showing 15 changed files with 676 additions and 20 deletions.
105 changes: 105 additions & 0 deletions PIA VPN-tvOS/Bootstraper/BootstraperFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//
// BootstraperFactory.swift
// PIA VPN-tvOS
//
// Created by Said Rehouni on 18/1/24.
// Copyright © 2024 Private Internet Access Inc. All rights reserved.
//

import Foundation
import PIALibrary
import SwiftyBeaver

class BootstraperFactory {
static func makeBootstrapper() -> BootstraperType {
Bootstrapper(setupDebugginConsole: setupDebugginConsole,
loadDataBase: loadDataBase,
cleanCurrentAccount: cleanCurrentAccount,
migrateNMT: migrateNMT,
setupLatestRegionList: setupLatestRegionList,
setupConfiguration: setupConfiguration,
setupPreferences: setupPreferences,
acceptDataSharing: acceptDataSharing,
dependencyBootstrap: Client.bootstrap,
renewalDIPToken: renewalDIPToken,
setupExceptionHandler: setupExceptionHandler)
}

private static func setupDebugginConsole() {
let console = ConsoleDestination()
#if PIA_DEV
console.minLevel = .debug
#else
console.minLevel = .info
#endif
SwiftyBeaver.addDestination(console)
}

private static func migrateNMT() {
AppPreferences.shared.migrateNMT()
}

private static func loadDataBase() {
Client.database = Client.Database(group: AppConstants.appGroup)
}

private static func setupPreferences() {
let defaults = Client.preferences.defaults
defaults.isPersistentConnection = true
defaults.mace = false
defaults.vpnType = IKEv2Profile.vpnType
}

private static func cleanCurrentAccount() {
// Check if should clean the account after delete the app and install again
if Client.providers.accountProvider.shouldCleanAccount {
//If first install, we need to ensure we don't have data from previous sessions in the Secure Keychain
Client.providers.accountProvider.cleanDatabase()
}
}

private static func setupLatestRegionList() {
// TODO: Fix Build Phase script to download the latest Regions list on building
/*
guard let bundledRegionsURL = AppConstants.RegionsGEN4.bundleURL else {
fatalError("Could not find bundled regions file")
}
do {
let bundledServersJSON = try Data(contentsOf: bundledRegionsURL)
Client.configuration.bundledServersJSON = bundledServersJSON
} catch let e {
fatalError("Could not parse bundled regions file: \(e)")
}
*/
}

private static func renewalDIPToken() {
// Check the DIP token for renewal
if AppPreferences.shared.checksDipExpirationRequest, let dipToken = Client.providers.serverProvider.dipTokens?.first {
Client.providers.serverProvider.handleDIPTokenExpiration(dipToken: dipToken, nil)
}
}

private static func setupConfiguration() {
Client.configuration.enablesConnectivityUpdates = true
Client.configuration.enablesServerUpdates = true
Client.configuration.enablesServerPings = true
Client.configuration.webTimeout = AppConfiguration.ClientConfiguration.webTimeout
Client.configuration.vpnProfileName = AppConfiguration.VPN.profileName
}

private static func acceptDataSharing() {
if Client.preferences.shareServiceQualityData {
ServiceQualityManager.shared.start()
} else {
ServiceQualityManager.shared.stop()
}
}

private static func setupExceptionHandler() {
NSSetUncaughtExceptionHandler { exception in
Client.preferences.lastKnownException = "$exception,\n\(exception.callStackSymbols.joined(separator: "\n"))"
}
}
}
55 changes: 55 additions & 0 deletions PIA VPN-tvOS/Bootstrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// Bootstrapper.swift
// PIA VPN-tvOS
//
// Created by Said Rehouni on 17/1/24.
// Copyright © 2024 Private Internet Access Inc. All rights reserved.
//

import Foundation

protocol BootstraperType {
func callAsFunction()
}

class Bootstrapper: BootstraperType {
var setupDebugginConsole: (() -> Void)
var loadDataBase: (() -> Void)
var cleanCurrentAccount: (() -> Void)
var migrateNMT: (() -> Void)
var setupLatestRegionList: (() -> Void)
var setupConfiguration: (() -> Void)
var setupPreferences: (() -> Void)
var acceptDataSharing: (() -> Void)
var dependencyBootstrap: (() -> Void)
var renewalDIPToken: (() -> Void)
var setupExceptionHandler: (() -> Void)

init(setupDebugginConsole: @escaping () -> Void, loadDataBase: @escaping () -> Void, cleanCurrentAccount: @escaping () -> Void, migrateNMT: @escaping () -> Void, setupLatestRegionList: @escaping () -> Void, setupConfiguration: @escaping () -> Void, setupPreferences: @escaping () -> Void, acceptDataSharing: @escaping () -> Void, dependencyBootstrap: @escaping () -> Void, renewalDIPToken: @escaping () -> Void, setupExceptionHandler: @escaping () -> Void) {
self.setupDebugginConsole = setupDebugginConsole
self.loadDataBase = loadDataBase
self.cleanCurrentAccount = cleanCurrentAccount
self.migrateNMT = migrateNMT
self.setupLatestRegionList = setupLatestRegionList
self.setupConfiguration = setupConfiguration
self.setupPreferences = setupPreferences
self.acceptDataSharing = acceptDataSharing
self.dependencyBootstrap = dependencyBootstrap
self.renewalDIPToken = renewalDIPToken
self.setupExceptionHandler = setupExceptionHandler
}

func callAsFunction() {
setupDebugginConsole()
loadDataBase()
cleanCurrentAccount()
migrateNMT()
setupLatestRegionList()
setupConfiguration()
setupPreferences()
acceptDataSharing()
dependencyBootstrap()
renewalDIPToken()
setupExceptionHandler()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class RootContainerFactory {
fatalError("Incorrect account provider type")
}
return RootContainerViewModel(accountProvider: defaultAccountProvider,
vpnConfigurationAvailability: VPNConfigurationAvailability())
vpnConfigurationAvailability: VPNConfigurationAvailability(),
bootstrap: BootstraperFactory.makeBootstrapper(),
userAuthenticationStatusMonitor: StateMonitorsFactory.makeUserAuthenticationStatusMonitor())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import Foundation
import SwiftUI
import PIALibrary
import Combine

class RootContainerViewModel: ObservableObject {
enum State {
Expand All @@ -17,12 +18,17 @@ class RootContainerViewModel: ObservableObject {
private let accountProvider: AccountProviderType
private let notificationCenter: NotificationCenterType
private let vpnConfigurationAvailability: VPNConfigurationAvailabilityType
private let bootstrap: BootstraperType
private let userAuthenticationStatusMonitor: UserAuthenticationStatusMonitorType
private var cancellables = Set<AnyCancellable>()

init(accountProvider: AccountProviderType, notificationCenter: NotificationCenterType = NotificationCenter.default, vpnConfigurationAvailability: VPNConfigurationAvailabilityType) {
init(accountProvider: AccountProviderType, notificationCenter: NotificationCenterType = NotificationCenter.default, vpnConfigurationAvailability: VPNConfigurationAvailabilityType, bootstrap: BootstraperType, userAuthenticationStatusMonitor: UserAuthenticationStatusMonitorType) {

self.accountProvider = accountProvider
self.notificationCenter = notificationCenter
self.vpnConfigurationAvailability = vpnConfigurationAvailability
self.bootstrap = bootstrap
self.userAuthenticationStatusMonitor = userAuthenticationStatusMonitor
updateState()
subscribeToAccountUpdates()
}
Expand All @@ -32,9 +38,7 @@ class RootContainerViewModel: ObservableObject {
}

func phaseDidBecomeActive() {
// Bootstrap PIALibrary preferences and settings
// TODO: DI this object
Bootstrapper.shared.bootstrap()
bootstrap()
isBootstrapped = true
updateState()
}
Expand All @@ -61,20 +65,12 @@ class RootContainerViewModel: ObservableObject {

}

// NotificationCenter subscriptions
// Combine subscriptions

extension RootContainerViewModel {
private func subscribeToAccountUpdates() {
notificationCenter.addObserver(self, selector: #selector(handleAccountDidLogin), name: .PIAAccountDidLogin, object: nil)

notificationCenter.addObserver(self, selector: #selector(handleAccountDidLogout), name: .PIAAccountDidLogout, object: nil)
}

@objc func handleAccountDidLogin() {
updateState()
}

@objc func handleAccountDidLogout() {
updateState()
userAuthenticationStatusMonitor.getStatus().sink { status in
self.updateState()
}.store(in: &cancellables)
}
}
26 changes: 26 additions & 0 deletions PIA VPN-tvOS/Shared/StateMonitors/StateMonitorsFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// StateMonitorsFactory.swift
// PIA VPN-tvOS
//
// Created by Said Rehouni on 19/1/24.
// Copyright © 2024 Private Internet Access Inc. All rights reserved.
//

import Foundation
import PIALibrary

class StateMonitorsFactory {
static func makeUserAuthenticationStatusMonitor() -> UserAuthenticationStatusMonitorType {
UserAuthenticationStatusMonitor(currentStatus: Client.providers.accountProvider.isLoggedIn ? .loggedIn : .loggedOut,
notificationCenter: NotificationCenter.default)
}

static func makeVPNStatusMonitor() -> VPNStatusMonitorType {
guard let defaultVPNProvider = Client.providers.vpnProvider as? DefaultVPNProvider else {
fatalError("Incorrect vpn provider type")
}

return VPNStatusMonitor(vpnStatusProvider: defaultVPNProvider,
notificationCenter: NotificationCenter.default)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// UserAuthenticationStatusMonitor.swift
// PIA VPN-tvOS
//
// Created by Said Rehouni on 18/1/24.
// Copyright © 2024 Private Internet Access Inc. All rights reserved.
//

import Foundation
import Combine

protocol UserAuthenticationStatusMonitorType {
func getStatus() -> AnyPublisher<UserAuthenticationStatus, Never>
}

enum UserAuthenticationStatus {
case loggedIn
case loggedOut
}

class UserAuthenticationStatusMonitor: UserAuthenticationStatusMonitorType {
private var status: CurrentValueSubject<UserAuthenticationStatus, Never>
private let notificationCenter: NotificationCenterType

init(currentStatus: UserAuthenticationStatus, notificationCenter: NotificationCenterType) {
self.notificationCenter = notificationCenter
self.status = CurrentValueSubject<UserAuthenticationStatus, Never>(currentStatus)

addObservers()
}

private func addObservers() {
notificationCenter.addObserver(self, selector: #selector(handleAccountDidLogin), name: .PIAAccountDidLogin, object: nil)

notificationCenter.addObserver(self, selector: #selector(handleAccountDidLogout), name: .PIAAccountDidLogout, object: nil)
}

@objc func handleAccountDidLogin() {
if status.value != .loggedIn {
status.send(.loggedIn)
}
}

@objc func handleAccountDidLogout() {
if status.value != .loggedOut {
status.send(.loggedOut)
}
}

func getStatus() -> AnyPublisher<UserAuthenticationStatus, Never> {
return status.eraseToAnyPublisher()
}

deinit {
notificationCenter.removeObserver(self)
}
}
48 changes: 48 additions & 0 deletions PIA VPN-tvOS/Shared/StateMonitors/VPNStatusMonitor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// VPNStatusMonitor.swift
// PIA VPN-tvOS
//
// Created by Said Rehouni on 18/1/24.
// Copyright © 2024 Private Internet Access Inc. All rights reserved.
//

import Foundation
import Combine
import PIALibrary

protocol VPNStatusMonitorType {
func getStatus() -> AnyPublisher<VPNStatus, Never>
}

class VPNStatusMonitor: VPNStatusMonitorType {
private var status: CurrentValueSubject<VPNStatus, Never>
private let vpnStatusProvider: VPNStatusProviderType
private let notificationCenter: NotificationCenterType

init(vpnStatusProvider: VPNStatusProviderType, notificationCenter: NotificationCenterType) {
self.vpnStatusProvider = vpnStatusProvider
self.notificationCenter = notificationCenter
self.status = CurrentValueSubject<VPNStatus, Never>(vpnStatusProvider.vpnStatus)
}

private func addObservers() {
notificationCenter.addObserver(self,
selector: #selector(vpnStatusDidChange(notification:)),
name: .PIADaemonsDidUpdateVPNStatus,
object: nil)
}

@objc func vpnStatusDidChange(notification: Notification) {
if vpnStatusProvider.vpnStatus != status.value {
status.send(vpnStatusProvider.vpnStatus)
}
}

func getStatus() -> AnyPublisher<VPNStatus, Never> {
return status.eraseToAnyPublisher()
}

deinit {
notificationCenter.removeObserver(self)
}
}
6 changes: 6 additions & 0 deletions PIA VPN-tvOS/Shared/Utils/PIALibrary+Protocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ protocol ServerProviderType {

extension DefaultServerProvider: ServerProviderType {
}

protocol VPNStatusProviderType {
var vpnStatus: VPNStatus { get }
}

extension DefaultVPNProvider: VPNStatusProviderType {}
18 changes: 18 additions & 0 deletions PIA VPN-tvOSTests/RootContainer/Mocks/BootstraperMock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// BootstraperMock.swift
// PIA VPN-tvOSTests
//
// Created by Said Rehouni on 20/1/24.
// Copyright © 2024 Private Internet Access Inc. All rights reserved.
//

import Foundation
@testable import PIA_VPN_tvOS

class BootstraperMock: BootstraperType {
var callAsFunctionTimesCalled = 0

func callAsFunction() {
callAsFunctionTimesCalled += 1
}
}
Loading

0 comments on commit 68e240a

Please sign in to comment.