Files

165 lines
4.6 KiB
Swift

//
// 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<EmitterVPNChangeType>()
/// Change VPN state emitter.
let vpnConnectionStateEmitter = Emitter<EmitterVPNConnectionStateChangeType>()
/// Change session record emitter.
let sessionEmitter = Emitter<EmitterSessionRecordChangeType>()
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<EmitterLocationChangeType>()
var location: GeoLocation? {
didSet {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.locationEmitter.invoke(self.location)
}
}
}
*/