Files
2021-09-21 21:36:53 +06:00

211 lines
6.3 KiB
Swift

//
// 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<SessionRecord, ErrorResponse>) -> Void
private let router = Environment.router
private let key: String
private var cancellationToken: CancellationToken?
let locationEmitter = Emitter<EmitterLocationChangeType>()
let sessionEmitter = Emitter<EmitterSessionRecordChangeType>()
let vpnEmitter = Emitter<EmitterVPNChangeType>()
let vpnConnectionStateEmitter = Emitter<EmitterVPNConnectionStateChangeType>()
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<SessionRecord, ErrorResponse>.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
}
}