import Foundation protocol AppControlServicing { func commands() -> AsyncStream } struct MockBackendControlService: AppControlServicing { static let controlFileEnvironmentKey = "SOCIALIO_CONTROL_FILE" static let pollingIntervalEnvironmentKey = "SOCIALIO_CONTROL_POLL_MS" private let environment: [String: String] init(environment: [String: String] = ProcessInfo.processInfo.environment) { self.environment = environment } func commands() -> AsyncStream { guard let controlFilePath = environment[Self.controlFileEnvironmentKey]? .trimmingCharacters(in: .whitespacesAndNewlines), !controlFilePath.isEmpty else { return AsyncStream { continuation in continuation.finish() } } let controlFileURL = URL(fileURLWithPath: controlFilePath) let pollingInterval = pollingIntervalDuration return AsyncStream { continuation in let task = Task.detached(priority: .background) { var lastAppliedPayload: String? while !Task.isCancelled { if let payload = try? String(contentsOf: controlFileURL, encoding: .utf8) { let trimmedPayload = payload.trimmingCharacters(in: .whitespacesAndNewlines) if !trimmedPayload.isEmpty, trimmedPayload != lastAppliedPayload, let command = AppNavigationCommand.parse(trimmedPayload) { lastAppliedPayload = trimmedPayload continuation.yield(command) } } try? await Task.sleep(for: pollingInterval) } continuation.finish() } continuation.onTermination = { _ in task.cancel() } } } private var pollingIntervalDuration: Duration { guard let rawValue = environment[Self.pollingIntervalEnvironmentKey], let milliseconds = Int(rawValue), milliseconds > 0 else { return .milliseconds(600) } return .milliseconds(milliseconds) } }