163 lines
5.1 KiB
Swift
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)))
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|