mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
126 lines
4.7 KiB
Swift
126 lines
4.7 KiB
Swift
import Foundation
|
|
import Observation
|
|
|
|
/// Manages the server connection state and configuration.
|
|
///
|
|
/// ConnectionManager handles saving and loading server configurations,
|
|
/// tracking connection state, and providing a central point for
|
|
/// connection-related operations.
|
|
@Observable
|
|
@MainActor
|
|
final class ConnectionManager {
|
|
static let shared = ConnectionManager()
|
|
|
|
// MARK: - Constants
|
|
|
|
private enum Constants {
|
|
static let connectionRestorationWindow: TimeInterval = 3_600 // 1 hour
|
|
static let savedServerConfigKey = "savedServerConfig"
|
|
static let connectionStateKey = "connectionState"
|
|
static let lastConnectionTimeKey = "lastConnectionTime"
|
|
}
|
|
|
|
var isConnected: Bool = false {
|
|
didSet {
|
|
guard oldValue != isConnected else { return }
|
|
storage.set(isConnected, forKey: Constants.connectionStateKey)
|
|
}
|
|
}
|
|
|
|
var serverConfig: ServerConfig?
|
|
var lastConnectionTime: Date?
|
|
private(set) var authenticationService: AuthenticationService?
|
|
private let storage: PersistentStorage
|
|
|
|
private init(storage: PersistentStorage = UserDefaultsStorage()) {
|
|
self.storage = storage
|
|
loadSavedConnection()
|
|
restoreConnectionState()
|
|
}
|
|
|
|
#if DEBUG
|
|
/// Test-only factory method for creating instances with mock storage
|
|
/// - Parameter storage: Mock storage for testing
|
|
/// - Returns: A new ConnectionManager instance for testing
|
|
static func createForTesting(storage: PersistentStorage) -> ConnectionManager {
|
|
ConnectionManager(storage: storage)
|
|
}
|
|
#endif
|
|
|
|
private func loadSavedConnection() {
|
|
if let data = storage.data(forKey: Constants.savedServerConfigKey),
|
|
let config = try? JSONDecoder().decode(ServerConfig.self, from: data)
|
|
{
|
|
self.serverConfig = config
|
|
|
|
// Set up authentication service for restored connection
|
|
authenticationService = AuthenticationService(
|
|
apiClient: APIClient.shared,
|
|
serverConfig: config
|
|
)
|
|
|
|
// Configure API client and WebSocket client with auth service
|
|
if let authService = authenticationService {
|
|
APIClient.shared.setAuthenticationService(authService)
|
|
BufferWebSocketClient.shared.setAuthenticationService(authService)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func restoreConnectionState() {
|
|
// Restore connection state if app was terminated while connected
|
|
let wasConnected = storage.bool(forKey: Constants.connectionStateKey)
|
|
if let lastConnectionData = storage.object(forKey: Constants.lastConnectionTimeKey) as? Date {
|
|
lastConnectionTime = lastConnectionData
|
|
|
|
// Only restore connection if it was within the last hour
|
|
let timeSinceLastConnection = Date().timeIntervalSince(lastConnectionData)
|
|
if wasConnected && timeSinceLastConnection < Constants.connectionRestorationWindow && serverConfig != nil {
|
|
// Attempt to restore connection
|
|
isConnected = true
|
|
} else {
|
|
// Clear stale connection state
|
|
isConnected = false
|
|
}
|
|
}
|
|
}
|
|
|
|
func saveConnection(_ config: ServerConfig) {
|
|
if let data = try? JSONEncoder().encode(config) {
|
|
// Create and configure authentication service BEFORE saving config
|
|
// This prevents race conditions where other components try to use
|
|
// the API client before authentication is properly configured
|
|
authenticationService = AuthenticationService(
|
|
apiClient: APIClient.shared,
|
|
serverConfig: config
|
|
)
|
|
|
|
// Configure API client and WebSocket client with auth service
|
|
if let authService = authenticationService {
|
|
APIClient.shared.setAuthenticationService(authService)
|
|
BufferWebSocketClient.shared.setAuthenticationService(authService)
|
|
}
|
|
|
|
// Now save the config and timestamp after auth is set up
|
|
storage.set(data, forKey: Constants.savedServerConfigKey)
|
|
self.serverConfig = config
|
|
|
|
// Save connection timestamp
|
|
lastConnectionTime = Date()
|
|
storage.set(lastConnectionTime, forKey: Constants.lastConnectionTimeKey)
|
|
}
|
|
}
|
|
|
|
func disconnect() async {
|
|
isConnected = false
|
|
storage.removeObject(forKey: Constants.connectionStateKey)
|
|
storage.removeObject(forKey: Constants.lastConnectionTimeKey)
|
|
|
|
await authenticationService?.logout()
|
|
authenticationService = nil
|
|
}
|
|
|
|
var currentServerConfig: ServerConfig? {
|
|
serverConfig
|
|
}
|
|
}
|