// Utils/Constants.swift // --------------------- // Central place for app-wide constants. import Foundation // MARK: - Server selection /// The two known JQC servers the inspector can connect to. nonisolated enum ServerOption: String, CaseIterable, Sendable { case primary = "http://127.0.0.1:5055" case secondary = "https://jqc1.ltservicesinc.com" var displayName: String { switch self { case .primary: return "jqc (Primary)" case .secondary: return "jqc1 (Secondary)" } } } /// Runtime-mutable server selection backed by UserDefaults. /// Read `ServerConfig.current` anywhere you would have used `Constants.baseURL`. nonisolated enum ServerConfig { private static let defaultsKey = "com.jqc.selectedServer" /// The currently selected base URL. Reads UserDefaults on every call so /// actor-isolated callers (e.g. APIClient) always get the latest value /// without needing @MainActor access. nonisolated static var current: String { get { let raw = UserDefaults.standard.string(forKey: defaultsKey) ?? "" return ServerOption(rawValue: raw)?.rawValue ?? ServerOption.primary.rawValue } } /// Persist the chosen server. Call from @MainActor UI code only. @MainActor static func select(_ option: ServerOption) { UserDefaults.standard.set(option.rawValue, forKey: defaultsKey) } /// The current selection as a `ServerOption` (for UI binding). @MainActor static var selectedOption: ServerOption { let raw = UserDefaults.standard.string(forKey: defaultsKey) ?? "" return ServerOption(rawValue: raw) ?? .primary } /// Resolve a photo URL, preferring an absolute server-provided URL /// (presigned R2 on the s3 backend, absolute-static on local) and falling /// back to building one from the relative 'uploads/...' key for older /// servers that don't send the *_url fields. nonisolated static func mediaURL(absolute: String?, path: String) -> URL? { if let a = absolute, !a.isEmpty { return URL(string: a) } guard !path.isEmpty else { return nil } return URL(string: current + "/static/" + path) } } // MARK: - App-wide constants // Explicitly not @MainActor — these constants must be readable from // any actor context including APIClient and KeychainHelper. nonisolated enum Constants { nonisolated enum Keychain { static let accessToken = "com.jqc.accessToken" static let refreshToken = "com.jqc.refreshToken" static let userId = "com.jqc.userId" static let userRole = "com.jqc.userRole" static let username = "com.jqc.username" static let displayName = "com.jqc.displayName" static let deviceId = "com.jqc.deviceId" } static let tokenRefreshBufferMinutes: Double = 5 }