// // 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) 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() 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, 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))) } } } }