Files
2021-11-15 22:33:17 +03:00

132 lines
4.2 KiB
Swift

//
// SerenityService.swift
// PrivadoVPN
//
// Created by Juraldinio on 3/30/21.
// Copyright © 2021 Privado LLC. All rights reserved.
//
import Foundation
final class SerenityService: BayeuxClientOutput {
enum State {
case notReceiving
case preparing
case receving
}
struct Credentials: Equatable {
let token: String
let username: String
}
private var bayeuxClient: BayeuxClient<PrivadoNotification>?
private var lastVpnState: VPNConnectionState?
private(set) var credentials: Credentials?
let stateEmitter = Emitter<State>()
let notificationsEmitter = Emitter<[PrivadoNotification]>()
// MARK: - Interface
func connect(with credentials: Credentials) { // establishNewConnection
guard self.credentials != credentials else { return }
self.reset()
guard let client = self.createBayeuxClient(using: credentials) else { return }
self.credentials = credentials
self.bayeuxClient = client
client.connect()
}
func reviseClient(with credentials: Credentials) {
guard credentials != self.credentials
, let client = self.createBayeuxClient(using: credentials) else { return }
self.reset()
self.credentials = credentials
self.bayeuxClient = client
client.connect()
}
func reset() {
self.credentials = nil
guard let bayeuxClient = self.bayeuxClient else { return }
bayeuxClient.disconnect()
self.bayeuxClient = nil
}
// MARK: - BayeuxClientOutput
func didReceiveNotifications<Notification>(_ notifications: [Notification]) {
let notification: [PrivadoNotification] = notifications
.compactMap { $0 as? PrivadoNotification }
self.notificationsEmitter.invoke(notification)
}
// MARK: - Private
private func createBayeuxClient(using credentials: Credentials) -> BayeuxClient<PrivadoNotification>? {
guard let serenityFayeUrl = URL(string: PrivadoConstants.Serenity.serverPath)
, let subscriptionChannel = Self.subscriptionChannel(for: credentials) else {
return nil
}
let handshakeCredentials = HandshakeSystemChannel.Credentials(username: credentials.username, token: credentials.token)
let client = BayeuxClient<PrivadoNotification>(url: serenityFayeUrl,
handshakeCredentials: handshakeCredentials,
subscriptionChannel: subscriptionChannel,
output: self,
autoReconnect: true)
client.stateEmitter.addReaction { [weak self] state -> ShouldContinueReceiveNotifications in
print("BayeuxClient: \(state)")
switch state {
case .connected:
self?.stateEmitter.invoke(.receving)
case .handshake,
.preparing,
.connect:
self?.stateEmitter.invoke(.preparing)
case .error,
.disconnected:
self?.stateEmitter.invoke(.notReceiving)
}
return self.isExist
}
return client
}
// MARK: - Static
private static func subscriptionChannel(for credentials: Credentials) -> String? {
guard let keyData = PrivadoConstants.Serenity.Crypto.cypherKey.data(using: .utf8)
, let ivData = PrivadoConstants.Serenity.Crypto.iv.data(using: .utf8)
, let encryptedUsernameData = CryptoAES(key: keyData, iv: ivData).encrypt(message: credentials.username) else {
return nil
}
var resultData = ivData
resultData.append(encryptedUsernameData)
return "/" + resultData
.base64EncodedString()
.replacingOccurrences(of: "=", with: "")
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
}
}