Files

163 lines
5.1 KiB
Swift

//
// GeoLocationAssistant.swift
// PrivadoVPN
//
// Created by Juraldinio on 3/27/21.
// Copyright © 2021 Privado LLC. All rights reserved.
//
import Foundation
final class GeoLocationAssistant {
enum GeoLocationError: Error {
case empty
case killSwitch
case api
case progress
}
private enum Constants {
static let maxDelay: TimeInterval = 5
}
typealias GeoLocationEmitterTuple = (GeoLocationAssistant, Result<GeoLocation, Error>)
private let interactor: MainInteractorInput
private(set) var geoLocation: GeoLocation?
private(set) var killSwitchEnabled = false
private let vpnAssistant: VPNAssistant
private(set) var connectionState = VPNConnectionState.initialize
let geoLocationEmitter = Emitter<GeoLocationEmitterTuple>()
init(interactor: MainInteractorInput, vpnAssistant: VPNAssistant) {
self.interactor = interactor
self.vpnAssistant = vpnAssistant
self.killSwitchEnabled = UserSettings.shared.killSwitchEnabled
self.bind()
}
// MARK: - Private
private func bind() {
self.vpnAssistant.connectionStateEmitter.addReaction { [weak self] assistant, state -> ShouldContinueReceiveNotifications in
guard let self = self else { return false }
guard self.connectionState != state else { return true }
let oldState = self.connectionState
self.connectionState = state
// Geolocation
switch state {
case .connected:
self.refreshCurrentLocation(for: state)
case .disconnected where !assistant.hasRequiredServer:
if [.connecting, .connected, .disconnecting(true), .disconnecting(false)].contains(oldState) {
self.geoLocation = nil
self.geoLocationEmitter.invoke((self, Result.failure(GeoLocationError.progress)))
}
self.refreshCurrentLocation(for: state)
case .disconnecting,
.reconnect:
self.geoLocation = nil
default:
break
}
return true
}
UserSettings.shared.killSwitchEmitter.addReaction { [weak self] enabled in
guard let self = self else { return false }
let old = self.killSwitchEnabled
self.killSwitchEnabled = enabled
guard old != self.killSwitchEnabled
, [.connected, .disconnected].contains(self.connectionState) else {
return true
}
self.geoLocation = nil
self.refreshCurrentLocation(for: self.connectionState)
return true
}
}
private func refreshCurrentLocation(for state: VPNConnectionState, delay: TimeInterval = 0) {
/* if state == .disconnected
, self.killSwitchEnabled {
self.geoLocationEmitter.invoke((self, Result.failure(GeoLocationError.killSwitch)))
return
} */
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
guard let self = self, self.connectionState == state else { return }
self.interactor.fetchLocation { [weak self] result in
guard let self = self
, self.connectionState == state else {
return
}
self.handleRefreshLocation(result: result, for: state, delay: delay)
}
}
}
private func handleRefreshLocation(result: Result<GeoLocation, Error>, for state: VPNConnectionState, delay: TimeInterval) {
switch result {
case let .success(geoLocation):
guard self.geoLocation.isExist else {
self.geoLocation = geoLocation
self.geoLocationEmitter.invoke((self, Result.success(geoLocation)))
return
}
guard self.geoLocation != geoLocation
|| self.connectionState == .disconnected else {
if delay < Constants.maxDelay {
self.refreshCurrentLocation(for: state, delay: delay + 1)
} else {
self.geoLocation = nil
self.geoLocationEmitter.invoke((self, Result.failure(GeoLocationError.empty)))
}
return
}
self.geoLocation = geoLocation
self.geoLocationEmitter.invoke((self, Result.success(geoLocation)))
case .failure:
self.geoLocation = nil
if delay < Constants.maxDelay {
self.refreshCurrentLocation(for: state, delay: delay + 1)
} else {
self.geoLocationEmitter.invoke((self, Result.failure(GeoLocationError.api)))
}
}
}
}