import Foundation private let backendBaseURL = "https://chatai-realtime-proxy-production.up.railway.app" private let deviceIdKey = "marksDeviceId" private let tokenKey = "marksJWT" private let tokenExpiryKey = "marksJWTExpiry" enum MarksAuth { static var baseURL: String { backendBaseURL } /// Returns a valid JWT, registering if missing or within 1 hour of expiry. static func validToken() async throws -> String { if let token = cached(), !token.isEmpty { return token } return try await register() } private static func deviceId() -> String { if let existing = UserDefaults.standard.string(forKey: deviceIdKey) { return existing } let new = UUID().uuidString UserDefaults.standard.set(new, forKey: deviceIdKey) return new } private static func cached() -> String? { guard let token = UserDefaults.standard.string(forKey: tokenKey), let expiry = UserDefaults.standard.object(forKey: tokenExpiryKey) as? Date, expiry > Date().addingTimeInterval(3600) else { return nil } return token } private static func register() async throws -> String { guard let url = URL(string: "\(backendBaseURL)/api/auth/register") else { throw URLError(.badURL) } let id = deviceId() var req = URLRequest(url: url) req.httpMethod = "POST" req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.httpBody = try JSONSerialization.data(withJSONObject: ["device_id": id]) let (data, response) = try await URLSession.shared.data(for: req) let status = (response as? HTTPURLResponse)?.statusCode ?? 0 guard (200...299).contains(status) else { throw APIError.badStatus(status) } struct Resp: Decodable { let access_token: String; let expires_in: Int } let resp = try JSONDecoder().decode(Resp.self, from: data) UserDefaults.standard.set(resp.access_token, forKey: tokenKey) UserDefaults.standard.set(Date().addingTimeInterval(TimeInterval(resp.expires_in)), forKey: tokenExpiryKey) Log.auth.info("Registered device \(id.prefix(8), privacy: .public)…") return resp.access_token } }