// // Session.swift // PrivadoVPN // // Created by Juraldinio on 12/16/20. // Copyright © 2020 Privado LLC. All rights reserved. // import Foundation final class Session { enum SessionError: Error { case absent // case setupRequire } typealias EmitterSessionRecordChangeType = (SessionRecord?) typealias EmitterVPNChangeType = (VPNService?) typealias EmitterVPNConnectionStateChangeType = (VPNConnectionState, VPNService?) // MARK: - Properties static private var session: Session? /// Change VPN emitter. let vpnEmitter = Emitter() /// Change VPN state emitter. let vpnConnectionStateEmitter = Emitter() /// Change session record emitter. let sessionEmitter = Emitter() private let keychain: KeychainSwift /// Current session record. 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) } } } /// Current VPN Service. private(set) var currentVPN: VPNService? /// Cancellation token. private var cancellationToken: CancellationToken? // MARK: - Static @discardableResult static func initialize(with key: String, keychain: KeychainSwift) -> Session { if let session = Self.session { return session } let session = Session(with: key, keychain: keychain) Self.session = session return session } @discardableResult static func instance() throws -> Session { guard let session = self.session else { throw SessionError.absent } return session } // MARK: - Init private init(with key: String, keychain: KeychainSwift) { self.keychain = keychain } // MARK: - Interface @discardableResult func save(_ sessionRecord: SessionRecord?) -> Bool { if let record = sessionRecord { guard let encoded = try? JSONEncoder().encode(record) else { return false } CometLogger.shared.event(name: PrivadoConstants.Event.source, attributes: ["Record": "Save"], secured: nil) self.keychain.set(encoded, forKey: PrivadoConstants.sessionKey) } else { CometLogger.shared.event(name: PrivadoConstants.Event.source, attributes: ["Record": "Clear"], secured: nil) self.keychain.set(Data(), forKey: PrivadoConstants.sessionKey) } self.currentRecord = sessionRecord return true } @discardableResult func restore() -> Bool { guard let data = self.keychain.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 } func switchVPNService(_ sevice: VPNService) { self.currentVPN = sevice // Observe state changing! let vpn = sevice sevice.stateEmitter.addReaction { [weak self, weak vpn] (state, service) -> ShouldContinueReceiveNotifications in guard let self = self , let current = vpn else { return false } guard current.id == service.id else { return true } DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.vpnConnectionStateEmitter.invoke((state, current)) } return true } DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.vpnEmitter.invoke(self.currentVPN) self.vpnConnectionStateEmitter.invoke((self.currentVPN?.state ?? .disconnected, self.currentVPN)) } } } /* func current(location: GeoLocation?) { guard let location = location else { return } Session.shared().location = location } /////////// let locationEmitter = Emitter() var location: GeoLocation? { didSet { DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.locationEmitter.invoke(self.location) } } } */