1→import Foundation 2→import Combine 3→ 4→/// Adaptive polling service that provides near-real-time updates 5→/// Uses smart intervals: faster when active, slower when idle 6→@MainActor 7→final class RealtimeService: ObservableObject { 8→ static let shared = RealtimeService() 9→ 10→ // MARK: - Published State 11→ @Published private(set) var isConnected: Bool = false 12→ @Published private(set) var lastUpdate: Date? 13→ @Published private(set) var connectionState: ConnectionState = .disconnected 14→ @Published private(set) var eventsReceived: Int = 0 15→ 16→ // MARK: - Event Publishers 17→ let roiUpdated = PassthroughSubject() 18→ let runCreated = PassthroughSubject() 19→ let runCompleted = PassthroughSubject() 20→ let budgetAlert = PassthroughSubject() 21→ 22→ // MARK: - Configuration 23→ private var baseInterval: TimeInterval = 5.0 // Base polling interval 24→ private var idleInterval: TimeInterval = 30.0 // Interval when idle 25→ private var activeInterval: TimeInterval = 3.0 // Interval when active 26→ private var currentInterval: TimeInterval = 5.0 27→ 28→ // MARK: - Private State 29→ private var pollingTask: Task? 30→ private var lastKnownRuns: [String] = [] 31→ private var lastROI: ROISummary? 32→ private var idleCounter: Int = 0 33→ private let maxIdleCount = 10 // After 10 polls with no changes, slow down 34→ 35→ private let apiClient = APIClient.shared 36→ private let ccService = CCTrackingService() 37→ 38→ enum ConnectionState: String { 39→ case disconnected = "Disconnected" 40→ case connecting = "Connecting..." 41→ case connected = "Connected" 42→ case reconnecting = "Reconnecting..." 43→ case error = "Error" 44→ } 45→ 46→ // MARK: - Lifecycle 47→ 48→ func start() { 49→ guard pollingTask == nil else { return } 50→ connectionState = .connecting 51→ 52→ pollingTask = Task { [weak self] in 53→ await self?.pollingLoop() 54→ } 55→ } 56→ 57→ func stop() { 58→ pollingTask?.cancel() 59→ pollingTask = nil 60→ connectionState = .disconnected 61→ isConnected = false 62→ } 63→ 64→ // MARK: - Polling Loop 65→ 66→ private func pollingLoop() async { 67→ while !Task.isCancelled { 68→ do { 69→ // Fetch latest data 70→ let (hasChanges, roi, runs) = try await fetchUpdates() 71→ 72→ // Update connection state 73→ isConnected = true 74→ connectionState = .connected 75→ lastUpdate = Date() 76→ 77→ // Process changes 78→ if hasChanges { 79→ idleCounter = 0 80→ currentInterval = activeInterval 81→ eventsReceived += 1 82→ 83→ // Emit events for new runs 84→ if let runs = runs { 85→ for run in runs { 86→ if !lastKnownRuns.contains(run.id) { 87→ if run.status == .completed { 88→ runCompleted.send(run) 89→ } else { 90→ runCreated.send(run) 91→ } 92→ } 93→ } 94→ lastKnownRuns = runs.map { $0.id } 95→ } 96→ 97→ // Emit ROI update 98→ if let roi = roi, roi != lastROI { 99→ roiUpdated.send(roi) 100→ 101→ // Check budget alerts 102→ checkBudgetAlerts(roi) 103→ } 104→ lastROI = roi 105→ } else { 106→ idleCounter += 1 107→ if idleCounter >= maxIdleCount { 108→ currentInterval = idleInterval 109→ } 110→ } 111→ 112→ // Wait for next poll 113→ try await Task.sleep(for: .seconds(currentInterval)) 114→ 115→ } catch { 116→ // Handle connection errors 117→ isConnected = false 118→ connectionState = .error 119→ currentInterval = idleInterval // Slow down on errors 120→ 121→ // Wait before retry 122→ try? await Task.sleep(for: .seconds(10)) 123→ connectionState = .reconnecting 124→ } 125→ } 126→ } 127→ 128→ private func fetchUpdates() async throws -> (hasChanges: Bool, roi: ROISummary?, runs: [CCRun]?) { 129→ // Fetch ROI summary and recent runs in parallel 130→ async let roiTask = ccService.getROISummary() 131→ async let runsTask = ccService.getRecentRuns(limit: 10) 132→ 133→ let (roi, runs) = try await (roiTask, runsTask) 134→ 135→ // Detect changes 136→ let roiChanged = roi != lastROI 137→ let runsChanged = runs.map { $0.id } != lastKnownRuns 138→ 139→ return (roiChanged || runsChanged, roi, runs) 140→ } 141→ 142→ // MARK: - Budget Alerts 143→ 144→ private func checkBudgetAlerts(_ roi: ROISummary) { 145→ // We need budget config for proper alerts - fetch it separately 146→ Task { 147→ if let budget = try? await ccService.getBudgetConfig() { 148→ let dailyUsage = roi.totalCost / max(budget.dailyLimitUsd, 0.01) 149→ await MainActor.run { 150→ processBudgetUsage(dailyUsage) 151→ } 152→ } 153→ } 154→ } 155→ 156→ private func processBudgetUsage(_ dailyUsage: Double) { 157→ // Alert at 80% and 100% budget usage 158→ 159→ if dailyUsage >= 1.0 { 160→ budgetAlert.send(BudgetAlert( 161→ level: .critical, 162→ message: "Daily budget exceeded!", 163→ percentage: dailyUsage * 100 164→ )) 165→ } else if dailyUsage >= 0.8 { 166→ budgetAlert.send(BudgetAlert( 167→ level: .warning, 168→ message: "Approaching daily budget limit", 169→ percentage: dailyUsage * 100 170→ )) 171→ } 172→ } 173→ 174→ // MARK: - Manual Actions 175→ 176→ func forceRefresh() async { 177→ currentInterval = activeInterval 178→ idleCounter = 0 179→ 180→ // Trigger immediate fetch 181→ do { 182→ let (_, roi, runs) = try await fetchUpdates() 183→ if let roi = roi { roiUpdated.send(roi) } 184→ if let runs = runs { lastKnownRuns = runs.map { $0.id } } 185→ lastUpdate = Date() 186→ } catch { 187→ print("Force refresh failed: \(error)") 188→ } 189→ } 190→} 191→ 192→// MARK: - Supporting Types 193→ 194→struct BudgetAlert: Identifiable { 195→ let id = UUID() 196→ let level: AlertLevel 197→ let message: String 198→ let percentage: Double 199→ let timestamp = Date() 200→ 201→ enum AlertLevel { 202→ case info 203→ case warning 204→ case critical 205→ } 206→} 207→ Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.