// // Session.swift // Cyberlock // // Created by Jura on 8/29/19. // import Foundation typealias EmitterSessionRecordChangeType = (SessionRecord?) typealias EmitterVPNChangeType = (VPNService?) typealias EmitterVPNConnectionStateChangeType = (VPNConnectionState, VPNService?) typealias EmitterLocationChangeType = (GeoLocation?) enum SessionError: Error { case setupRequire } final class Session { typealias AuthenticationCompletionClosure = (_ result: Result) -> Void private let router = Environment.router private let key: String private var cancellationToken: CancellationToken? let locationEmitter = Emitter() let sessionEmitter = Emitter() let vpnEmitter = Emitter() let vpnConnectionStateEmitter = Emitter() private(set) var currentRecord: SessionRecord? { didSet { let record = self.currentRecord DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.sessionEmitter.invoke(record) } } } var location: GeoLocation? { didSet { DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.locationEmitter.invoke(self.location) } } } var currentVPN: VPNService? { didSet { if let vpn = self.currentVPN { // Observe state changing! vpn.stateEmitter.addReaction { [weak self, weak vpn] (state, service) -> ShouldContinueReceiveNotifications in guard let current = vpn else { return false } guard current.id == service.id else { return true } DispatchQueue.main.async { guard let self = self else { return } self.vpnConnectionStateEmitter.invoke((state, vpn)) } return self.isExist } } DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.vpnConnectionStateEmitter.invoke((self.currentVPN?.state ?? .disconnected, self.currentVPN)) self.vpnEmitter.invoke(self.currentVPN) } } } var token: TokenRecord? { return self.currentRecord?.token } var bestLocation: ServerRecord? { didSet { self.saveBestLocation() } } // MARK: - Static private static var instance: Session? @discardableResult static func shared(with key: String? = nil) -> Session { if let session = self.instance { return session } let instance = Session(with: key ?? "") self.instance = instance return instance } // MARK: - Init private init(with key: String) { self.key = key self.restoreBestLocation() } // MARK: - Methods func login(using username: String, password: String, then completion: @escaping AuthenticationCompletionClosure) { guard !self.cancellationToken.isExist else { return } let service = SessionService.login(username: username, password: password, key: self.key) self.cancellationToken = self.router.requestDecodable(Either.self, service: service) { [weak self] result in self?.cancellationToken = nil result .onSuccess { (either, _) in switch either { case let .firstType(record): completion(.success(record)) case let .secondType(error): completion(.failure(error)) } }.onFail { error in if error is DecodingError { completion(.failure(ErrorResponse(code: ErrorResponse.Constants.decoding, reason: "\(error)"))) } else { completion(.failure(ErrorResponse(code: ErrorResponse.Constants.undefined, reason: "\(error)"))) } } } } func refresh() { } @discardableResult private func restoreBestLocation() -> Bool { let keys = KeychainSwift() guard let data = keys.getData(PrivadoConstants.bestLocationKey) , let record = try? JSONDecoder().decode(ServerRecord.self, from: data) else { return false } self.bestLocation = record return true } @discardableResult private func saveBestLocation() -> Bool { let keys = KeychainSwift() if let record = self.bestLocation { guard let encoded = try? JSONEncoder().encode(record) else { return false } keys.set(encoded, forKey: PrivadoConstants.bestLocationKey) } else { keys.set(Data(), forKey: PrivadoConstants.bestLocationKey) } return true } @discardableResult func save(_ sessionRecord: SessionRecord?) -> Bool { let keys = KeychainSwift() if let record = sessionRecord { guard let encoded = try? JSONEncoder().encode(record) else { return false } keys.set(encoded, forKey: PrivadoConstants.sessionKey) } else { keys.set(Data(), forKey: PrivadoConstants.sessionKey) } self.currentRecord = sessionRecord return true } @discardableResult func restore() -> Bool { let keys = KeychainSwift() guard let data = keys.getData(PrivadoConstants.sessionKey) , let record = try? JSONDecoder().decode(SessionRecord.self, from: data) else { return false } guard record.isValid else { return false } self.currentRecord = record return true } }