// // 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? private var lastVpnState: VPNConnectionState? private(set) var credentials: Credentials? let stateEmitter = Emitter() 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(_ notifications: [Notification]) { let notification: [PrivadoNotification] = notifications .compactMap { $0 as? PrivadoNotification } self.notificationsEmitter.invoke(notification) } // MARK: - Private private func createBayeuxClient(using credentials: Credentials) -> BayeuxClient? { 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(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: "_") } }