702 lines
26 KiB
Swift
702 lines
26 KiB
Swift
//
|
|
// MainPresenter.swift
|
|
// PrivadoVPN
|
|
//
|
|
// Created by Juraldinio on 1/27/21.
|
|
// Copyright © 2021 Privado LLC. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
protocol MainModuleOutput: HeaderControllerModuleOutput {
|
|
var session: Session? { get }
|
|
var keychain: KeychainSwift { get }
|
|
var scheduler: Emitter<[NotificationLocal]> { get }
|
|
|
|
func navigate(to route: Route)
|
|
}
|
|
|
|
protocol MainPresenterInput {
|
|
var availableServerRecords: [ServerRecord] { get }
|
|
var bestRecord: ServerRecord? { get }
|
|
func refreshCustomer()
|
|
func switchServerRecord(current record: ServerRecord)
|
|
func connectServer()
|
|
func disconnectServer()
|
|
}
|
|
|
|
final class MainPresenter: ServerPanelOutput, KilSwitchPanelOutput, TrafficPanelOutput {
|
|
|
|
typealias FetchServersCompletion = () -> Void
|
|
|
|
private enum Constants {
|
|
|
|
enum Localized {
|
|
|
|
static let loadingInfo = "main.flow.loading.info"
|
|
static let pingServers = "main.flow.loading.ping"
|
|
//
|
|
static let modalTrafficTitle = "modal.traffic.title"
|
|
static let modalTrafficDescription = "modal.traffic.description"
|
|
static let modalTrafficButton = "modal.traffic.button"
|
|
//
|
|
static let modalCustomerTitle = "modal.customer.title"
|
|
static let modalCustomerDescription = "modal.customer.description"
|
|
static let modalCustomerButton = "modal.customer.button"
|
|
//
|
|
static let modalServerOverqutaTitle = "modal.overquota.server.title"
|
|
static let modalServerOverqutaButton = "modal.overquota.server.button"
|
|
//
|
|
static let serverUnavailable = "main.flow.serverUnavailable"
|
|
//
|
|
static let accountSuspendedTitle = "modal.suspended.title"
|
|
static let accountSuspendedDescrition = "modal.suspended.description"
|
|
static let accountSuspendedButton = "modal.suspended.button"
|
|
}
|
|
|
|
enum Actions {
|
|
static let customerRefresh = "privadovpn://customer"
|
|
}
|
|
|
|
enum Image {
|
|
static let overquta = "connection.lock"
|
|
}
|
|
|
|
enum Time {
|
|
#if PRODUCTION
|
|
static let serverListUpdateInterval: Int = 15
|
|
#else
|
|
static let serverListUpdateInterval: Int = 3
|
|
#endif
|
|
}
|
|
}
|
|
|
|
private let interactor: MainInteractorInput
|
|
private weak var output: MainModuleOutput?
|
|
weak var viewInput: MainControllerInput?
|
|
|
|
private var context: MainRecordsContext
|
|
private let type: Route.Main
|
|
private var serverRecords = [ServerRecord]()
|
|
private(set) var availableServerRecords = [ServerRecord]() // MainPresenterInput
|
|
private(set) var bestRecord: ServerRecord?
|
|
let updateEmitter = Emitter<Void>() // ConnectionPanelOutput, ServerPanelOutput
|
|
private var lastServerListUpdate = Date()
|
|
|
|
private var serverFetcherConveyor: ServerFetcherConveyor?
|
|
|
|
private let geoAssistant: GeoLocationAssistant
|
|
private let vpnAssistant: VPNAssistant
|
|
|
|
private var customerModule = CustomerModuleBuilder.shared()
|
|
|
|
private var notificationPanel: NotificationPanelViewInput?
|
|
|
|
// MARK: - Init
|
|
|
|
init(interactor: MainInteractorInput, output: MainModuleOutput, type: Route.Main) {
|
|
self.interactor = interactor
|
|
self.output = output
|
|
self.context = MainRecordsContext()
|
|
self.type = type
|
|
|
|
self.vpnAssistant = VPNAssistant(session: output.session, keychain: output.keychain)
|
|
self.geoAssistant = GeoLocationAssistant(interactor: interactor, vpnAssistant: self.vpnAssistant)
|
|
self.bind(using: output)
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private func bind(using output: MainModuleOutput) {
|
|
|
|
self.vpnAssistant.connectionStateEmitter.addReaction { [weak self] _, _ -> ShouldContinueReceiveNotifications in
|
|
self?.updateEmitter.invoke(())
|
|
return self.isExist
|
|
}
|
|
|
|
self.geoAssistant.geoLocationEmitter.addReaction { [weak self] _, result -> ShouldContinueReceiveNotifications in
|
|
|
|
guard let self = self else { return false }
|
|
|
|
guard let location = result.optional else { return true }
|
|
|
|
let oldLocation = self.context.geoLocation
|
|
self.context.geoLocation = location
|
|
|
|
if oldLocation != self.context.geoLocation {
|
|
self.updateEmitter.invoke(())
|
|
}
|
|
|
|
if self.vpnAssistant.connectionState == .disconnected && self.needToReloadServerlist() {
|
|
self.fetchServersInfo()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
output.scheduler.addReaction { [weak self] notifications -> ShouldContinueReceiveNotifications in
|
|
|
|
guard let self = self else { return false }
|
|
|
|
guard let record = notifications.sorted(by: { $0.date > $1.date }).first else { return true }
|
|
|
|
guard record.isVPNActive else {
|
|
|
|
self.showModal(with: record.title,
|
|
description: record.body,
|
|
button: NSLocalizedString(Constants.Localized.modalTrafficButton, comment: ""),
|
|
url: URL(string: record.loginUrlPath ?? ""))
|
|
self.disconnectServer()
|
|
|
|
return true
|
|
}
|
|
|
|
guard record.actions.isEmpty else {
|
|
|
|
let action = record.actions.compactMap { NotificationLocal.Action.init(rawValue: $0) }.first
|
|
|
|
switch action {
|
|
case .passReset:
|
|
if !record.subject.isEmpty && !record.body.isEmpty {
|
|
self.showModalNotification(with: record.body, closable: false)
|
|
}
|
|
self.disconnectServer()
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
|
self.navigate(to: .login(type: .signoutPrefill))
|
|
}
|
|
case .conlimit:
|
|
guard let ip = record.localIp else { return true }
|
|
if !record.subject.isEmpty && !record.body.isEmpty {
|
|
self.showModalNotification(with: record.body, closable: false)
|
|
}
|
|
if !InterfaceAddress.instance.getAllAddresses().contains(ip) {
|
|
self.disconnectServer()
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
let traffic = TrafficPanelData(total: record.trafficTotalMb, left: record.trafficLeftMb)
|
|
self.trafficEmitter.invoke(traffic)
|
|
|
|
if let notificationPanel = self.notificationPanel {
|
|
self.update(notificationPanel: notificationPanel, with: record)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
self.customerModule.customerEmitter.addReaction { [weak self] customer -> ShouldContinueReceiveNotifications in
|
|
|
|
guard let self = self else { return false }
|
|
guard let customer = customer else {
|
|
|
|
self.showModal(with: NSLocalizedString(Constants.Localized.modalCustomerTitle, comment: ""),
|
|
description: NSLocalizedString(Constants.Localized.modalCustomerDescription, comment: ""),
|
|
button: NSLocalizedString(Constants.Localized.modalCustomerButton, comment: ""),
|
|
url: URL(string: Constants.Actions.customerRefresh))
|
|
|
|
return true
|
|
}
|
|
|
|
self.update(customer: customer)
|
|
|
|
switch self.type {
|
|
case .verifyAutologin:
|
|
openApplicationRoute(.tutorial)
|
|
default:
|
|
break
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
}
|
|
|
|
// swiftlint:disable function_body_length
|
|
private func fetchServersInfo(_ completion: FetchServersCompletion? = nil) {
|
|
|
|
guard !self.serverFetcherConveyor.isExist
|
|
, let customer = self.customerModule.customer else { return }
|
|
|
|
let lastServer = self.context.selectedRecord
|
|
|
|
self.serverRecords = []
|
|
self.context = MainRecordsContext.empty()
|
|
|
|
let conveyor = ServerFetcherConveyor(interactor: self.interactor)
|
|
|
|
let viewInput = self.viewInput
|
|
// Progress
|
|
conveyor.pingProgress = { progress in
|
|
DispatchQueue.main.async {
|
|
// let title = try? NSLocalizedString(Constants.LocalizedString.pingServers, comment: "")
|
|
// .replace("#PERCENTS#", replacement: "\(Int((progress * 100).rounded(.down)))")
|
|
viewInput?.loading(progress: true)
|
|
}
|
|
}
|
|
|
|
// State change
|
|
conveyor.stateChangeClosure = { state, success in
|
|
switch state {
|
|
case .fetch where success == false:
|
|
DispatchQueue.main.async {
|
|
viewInput?.loading(progress: false)
|
|
viewInput?.activate(components: [.notification])
|
|
}
|
|
case .convert where success == true:
|
|
DispatchQueue.main.async {
|
|
// let title = try? NSLocalizedString(Constants.LocalizedString.pingServers, comment: "").replace("#PERCENTS#", replacement: "0")
|
|
viewInput?.loading(progress: true)
|
|
}
|
|
default: break
|
|
}
|
|
}
|
|
|
|
self.serverFetcherConveyor = conveyor
|
|
|
|
// Completion
|
|
conveyor
|
|
.run()
|
|
.onResult { [weak self] result in
|
|
|
|
guard let self = self else { return }
|
|
|
|
guard let operationsResult = result.optional else {
|
|
DispatchQueue.main.async {
|
|
self.viewInput?.loading(progress: false)
|
|
self.viewInput?.activate(components: [.notification])
|
|
}
|
|
return
|
|
}
|
|
|
|
self.serverRecords = operationsResult.serverRecords ?? []
|
|
self.context = operationsResult.context
|
|
self.serverFetcherConveyor = nil
|
|
|
|
// For Overquota plan we must change all behaviour.
|
|
let plan = customer.plan ?? .unrecognized(value: "")
|
|
if self.updateServerList(using: plan, preferred: self.interactor.preferredServer)
|
|
, let record = self.context.selectedRecord {
|
|
|
|
if let server = lastServer {
|
|
if self.availableServerRecords.contains(where: { $0.city == server.city }) {
|
|
self.context.selectedRecord = server
|
|
self.vpnAssistant.selected(server: server.server)
|
|
if let completion = completion {
|
|
completion()
|
|
}
|
|
} else {
|
|
self.notificationPanel?.notificationMessage(.error(message: NSLocalizedString(Constants.Localized.serverUnavailable, comment: ""), icon: nil))
|
|
self.showModalNotification(with: NSLocalizedString(Constants.Localized.serverUnavailable, comment: ""), closable: true)
|
|
self.vpnAssistant.selected(server: record.server)
|
|
}
|
|
self.lastServerListUpdate = Date()
|
|
return
|
|
} else {
|
|
self.vpnAssistant.selected(server: record.server)
|
|
|
|
if self.interactor.preferredServer.isExist {
|
|
self.reconnect(to: record.server)
|
|
}
|
|
}
|
|
}
|
|
|
|
self.lastServerListUpdate = Date()
|
|
|
|
let components = self.activationComponents(for: plan)
|
|
DispatchQueue.main.async {
|
|
self.viewInput?.activate(components: components)
|
|
|
|
}
|
|
|
|
openApplicationRoute(.killSwitch(type: .refresh))
|
|
|
|
}
|
|
|
|
/*DispatchQueue.main.async {
|
|
let route = Route.modal(settings: .closable(title: "Oh no! You ran out of data", description: "You can gat unlimited", buttonTitle: "Get more data", action: nil))
|
|
self.output?.navigate(to: route)
|
|
}*/
|
|
}
|
|
// swiftlint:enable function_body_length
|
|
|
|
private func update(customer: Customer) {
|
|
|
|
guard let isVPNActive = customer.isVPNActive else {
|
|
// We are premium?!
|
|
self.viewInput?.loading(progress: true)
|
|
self.fetchServersInfo()
|
|
return
|
|
}
|
|
|
|
guard customer.plan == .overquota || customer.isPremium || isVPNActive else {
|
|
|
|
self.showModal(with: NSLocalizedString(Constants.Localized.modalTrafficTitle, comment: ""),
|
|
description: NSLocalizedString(Constants.Localized.modalTrafficDescription, comment: ""),
|
|
button: NSLocalizedString(Constants.Localized.modalTrafficButton, comment: ""),
|
|
url: URL(string: customer.loginUrl ?? ""))
|
|
|
|
self.disconnectServer()
|
|
return
|
|
}
|
|
|
|
self.viewInput?.loading(progress: true)
|
|
self.fetchServersInfo()
|
|
}
|
|
|
|
private func update(notificationPanel: NotificationPanelViewInput, with notification: NotificationLocal) {
|
|
|
|
let message: NotificationPanelMessage
|
|
if let linkPath = notification.loginUrlPath
|
|
, let url = URL(string: linkPath) {
|
|
message = .link(message: notification.title, link: "", url: url)
|
|
} else {
|
|
message = .info(message: notification.title)
|
|
}
|
|
|
|
notificationPanel.notificationMessage(message)
|
|
}
|
|
|
|
private func showModal(with title: String, description: String, button: String, url: URL?) {
|
|
let settings: Route.ModalSettings = .modal(title: title,
|
|
description: description,
|
|
buttonTitle: button,
|
|
action: url)
|
|
let modal = Route.modal(settings: settings)
|
|
self.output?.navigate(to: modal)
|
|
}
|
|
|
|
private func showModalNotification(with title: String, closable: Bool) {
|
|
|
|
let settings: Route.ModalSettings = .modalNotification(closable: closable, title: title)
|
|
let modal = Route.modal(settings: settings)
|
|
self.output?.navigate(to: modal)
|
|
}
|
|
|
|
private func updateServerList(using plan: Customer.Plan, preferred: PreferredServerType?) -> Bool {
|
|
switch plan {
|
|
case .unrecognized:
|
|
self.availableServerRecords = []
|
|
self.context.bestRecord = nil
|
|
self.bestRecord = nil
|
|
return false
|
|
default:
|
|
self.availableServerRecords = self.serverRecords
|
|
.sorted(by: { $0.group > $1.group })
|
|
.reduce(into: []) { acc, record in
|
|
|
|
if acc.first(where: { $0.group == record.group && $0.countryCode == record.countryCode && $0.city == record.city }).isExist { return }
|
|
|
|
let records = acc.filter({ $0.countryCode == record.countryCode && $0.city == record.city })
|
|
if records.isEmpty {
|
|
switch plan {
|
|
case .premium where record.group == .premium:
|
|
acc.append(record)
|
|
return
|
|
case .freemium:
|
|
acc.append(record)
|
|
return
|
|
case .overquota where record.group == .overquota:
|
|
acc.append(record)
|
|
return
|
|
default: break
|
|
}
|
|
}
|
|
|
|
if case .freemium = plan, (!records.contains(where: { $0.isFreemium }) || record.isPremium) {
|
|
acc.append(record)
|
|
} else if case .overquota = plan, !records.contains(where: { $0.isOverquota }) {
|
|
acc.append(record)
|
|
} else {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
let searchServers: [ServerRecord]
|
|
switch plan {
|
|
case .freemium: searchServers = self.availableServerRecords.filter { $0.isFreemium }
|
|
case .overquota: searchServers = self.availableServerRecords.filter { $0.isOverquota }
|
|
default: searchServers = self.availableServerRecords
|
|
}
|
|
|
|
let bestRecord: ServerRecord?
|
|
if let location = self.context.geoLocation
|
|
, let latitude = Double(location.lat)
|
|
, let longitude = Double(location.long) {
|
|
bestRecord = BestRecord.calculate(for: searchServers, latitude: latitude, longitude: longitude)?.record
|
|
} else {
|
|
bestRecord = BestRecord.calculate(for: searchServers, latitude: 0.0, longitude: 0.0)?.record
|
|
}
|
|
|
|
let record: ServerRecord?
|
|
if preferred == .last
|
|
, let server = interactor.restoreServer()
|
|
, let found = searchServers.first(where: { $0.server.name == server.name}) {
|
|
record = found
|
|
} else if preferred == .random {
|
|
|
|
let randomIndex = Int.random(in: 0..<(searchServers.count))
|
|
if let searched = searchServers[safe: randomIndex] {
|
|
record = searched
|
|
} else {
|
|
record = bestRecord
|
|
}
|
|
} else {
|
|
record = bestRecord
|
|
}
|
|
|
|
self.context.bestRecord = bestRecord
|
|
self.bestRecord = bestRecord
|
|
self.context.selectedRecord = record ?? bestRecord
|
|
|
|
return true
|
|
}
|
|
|
|
private func activationComponents(for plan: Customer.Plan) -> [MainControllerComponent] {
|
|
switch plan {
|
|
case .unrecognized: return []
|
|
case .freemium: return [.notification, .connection, .servers, .killswitch, .remaining]
|
|
case .overquota: return [.notification, .connection, .servers, .killswitch, .remaining]
|
|
case .premium: return [.notification, .connection, .servers, .killswitch]
|
|
}
|
|
}
|
|
|
|
private func needToReloadServerlist() -> Bool {
|
|
guard let minutes = Calendar.current.dateComponents([.minute], from: self.lastServerListUpdate, to: Date()).minute else { return false }
|
|
return minutes >= Constants.Time.serverListUpdateInterval
|
|
}
|
|
|
|
func isCustomerSuspended() -> Bool {
|
|
if let customer = self.customerModule.customer {
|
|
switch customer.plan {
|
|
case .premium:
|
|
if let isActive = customer.isVPNActive, !isActive {
|
|
let url = customer.loginUrl.flatMap { URL(string: $0) }
|
|
self.showSuspendedModal(with: url)
|
|
return true
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
} else {
|
|
if let record = self.session?.currentRecord {
|
|
// 2 means it's usenet suspended
|
|
if record.accountType == 2 {
|
|
self.showSuspendedModal(with: nil)
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
private func showSuspendedModal(with url: URL?) {
|
|
let settings = Route.ModalSettings.closable(title: NSLocalizedString(Constants.Localized.accountSuspendedTitle, comment: ""),
|
|
description: NSLocalizedString(Constants.Localized.accountSuspendedDescrition, comment: ""),
|
|
buttonTitle: NSLocalizedString(Constants.Localized.accountSuspendedButton, comment: ""),
|
|
action: url)
|
|
self.output?.navigate(to: .modal(settings: settings))
|
|
}
|
|
|
|
private func reconnect(to server: Server) {
|
|
guard !self.isCustomerSuspended() else { return }
|
|
self.vpnAssistant.reconnect(to: server)
|
|
}
|
|
|
|
// MARK: - ServerPanelOutput
|
|
|
|
func update(for panel: ServerPanelInput) {
|
|
|
|
guard let record = self.context.selectedRecord else {
|
|
panel.update(state: .undefined)
|
|
return
|
|
}
|
|
|
|
let plan = self.customerModule.customer?.plan ?? .freemium
|
|
let state: ServerPanelState = plan == .overquota
|
|
? .overqoute(country: record.countryCode, city: record.city, flag: record.countryCode)
|
|
: .selected(country: record.countryCode, city: record.city, flag: record.countryCode)
|
|
|
|
panel.update(state: state)
|
|
}
|
|
|
|
func selectRequired(from panel: ServerPanelInput) {
|
|
|
|
guard let customer = self.customerModule.customer else {
|
|
self.output?.navigate(to: .servers)
|
|
return
|
|
}
|
|
|
|
let route: Route
|
|
if customer.plan == .overquota {
|
|
let settings = Route.ModalSettings.image(named: Constants.Image.overquta,
|
|
title: NSLocalizedString(Constants.Localized.modalServerOverqutaTitle, comment: ""),
|
|
buttonTitle: NSLocalizedString(Constants.Localized.modalServerOverqutaButton, comment: ""),
|
|
action: URL(string: customer.loginUrl ?? ""))
|
|
route = .modal(settings: settings)
|
|
} else {
|
|
route = .servers
|
|
}
|
|
|
|
self.output?.navigate(to: route)
|
|
}
|
|
|
|
// MARK: - KilSwitchPanelOutput
|
|
|
|
// MARK: - TrafficPanelOutput
|
|
|
|
|
|
let trafficEmitter = Emitter<TrafficPanelData>()
|
|
|
|
var session: Session? { self.output?.session }
|
|
var customerInput: CustomerModuleInput { self.customerModule }
|
|
func navigate(to url: URL) { self.output?.navigate(to: .navigate(url: url)) }
|
|
}
|
|
|
|
// MARK: - MainPresenterInput
|
|
|
|
extension MainPresenter: MainPresenterInput {
|
|
|
|
func refreshCustomer() {
|
|
|
|
guard let customer = self.customerModule.customer else {
|
|
self.customerModule.requireCustomerInfo()
|
|
self.viewInput?.loading(progress: false)
|
|
return
|
|
}
|
|
|
|
self.update(customer: customer)
|
|
}
|
|
|
|
func switchServerRecord(current record: ServerRecord) {
|
|
|
|
guard self.vpnAssistant.isDifferent(server: record.server) else { return }
|
|
|
|
self.context.selectedRecord = record
|
|
|
|
self.interactor.storeServer(record.server)
|
|
|
|
if self.vpnAssistant.connectionState != .disconnected {
|
|
self.reconnect(to: record.server)
|
|
} else {
|
|
self.vpnAssistant.selected(server: record.server)
|
|
self.vpnAssistant.connect()
|
|
}
|
|
|
|
self.updateEmitter.invoke(())
|
|
|
|
}
|
|
}
|
|
|
|
// MARK: - MainControllerOutput
|
|
|
|
extension MainPresenter: MainControllerOutput {
|
|
|
|
func viewIsReady() {
|
|
self.refreshCustomer()
|
|
self.viewInput?.loading(progress: true)
|
|
}
|
|
}
|
|
|
|
// MARK: - NotificationPanelOutput
|
|
|
|
extension MainPresenter: NotificationPanelOutput {
|
|
|
|
func ready(panel: NotificationPanelViewInput) {
|
|
self.notificationPanel = panel
|
|
panel.notificationHide()
|
|
|
|
guard let record = self.output?.scheduler.value?.last , !record.subject.isEmpty else { return }
|
|
self.update(notificationPanel: panel, with: record)
|
|
}
|
|
}
|
|
|
|
// MARK: - ConnectionPanelOutput
|
|
|
|
extension MainPresenter: ConnectionPanelOutput {
|
|
|
|
func update(for panel: ConnectionPanelInput) {
|
|
|
|
let connectionState = self.vpnAssistant.connectionState
|
|
guard let location = self.context.geoLocation else {
|
|
|
|
switch connectionState {
|
|
case .connecting:
|
|
panel.update(state: .connecting)
|
|
case .disconnecting(true),
|
|
.disconnecting(false):
|
|
panel.update(state: .disconnecting)
|
|
case .reconnect:
|
|
panel.update(state: .reconnecting)
|
|
default:
|
|
let state: ConnectionPanelState = [.prepare, .connecting, .connected].contains(where: { $0 == connectionState })
|
|
? .connecting
|
|
: .disconnecting
|
|
panel.update(state: state)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
switch connectionState {
|
|
case .connected:
|
|
panel.update(state: .connected(ip: location.ip, secured: true))
|
|
case .connecting:
|
|
panel.update(state: .connecting)
|
|
case .disconnecting:
|
|
panel.update(state: .disconnecting)
|
|
case .disconnected:
|
|
panel.update(state: .disconnected(ip: location.ip, city: location.city ?? "", country: location.country ?? ""))
|
|
case .reconnect:
|
|
panel.update(state: .reconnecting)
|
|
default: break
|
|
}
|
|
}
|
|
|
|
func connectServer() {
|
|
|
|
guard !self.isCustomerSuspended() else { return }
|
|
|
|
switch self.vpnAssistant.connectionState {
|
|
case .disconnected:
|
|
CometLogger.shared.event(name: PrivadoConstants.Event.user,
|
|
attributes: [PrivadoConstants.Event.Attributes.connect: true],
|
|
secured: nil)
|
|
self.context.clearGeo()
|
|
|
|
if self.needToReloadServerlist() {
|
|
self.fetchServersInfo {
|
|
self.vpnAssistant.connect()
|
|
}
|
|
return
|
|
}
|
|
self.vpnAssistant.connect()
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
func disconnectServer() {
|
|
switch self.vpnAssistant.connectionState {
|
|
case .connected:
|
|
CometLogger.shared.event(name: PrivadoConstants.Event.user,
|
|
attributes: [PrivadoConstants.Event.Attributes.connect: false],
|
|
secured: nil)
|
|
self.context.clearGeo()
|
|
self.vpnAssistant.disconnect()
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
func navigate(to route: Route) {
|
|
self.output?.navigate(to: route)
|
|
}
|
|
|
|
}
|