import Foundation import UIKit private let backendBaseURL = "https://chatai-realtime-proxy-production.up.railway.app" 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 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 deviceId = UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString var req = URLRequest(url: url) req.httpMethod = "POST" req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.httpBody = try JSONSerialization.data(withJSONObject: ["device_id": deviceId]) 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) let expiry = Date().addingTimeInterval(TimeInterval(resp.expires_in)) UserDefaults.standard.set(expiry, forKey: tokenExpiryKey) print("[Auth] Registered device \(deviceId.prefix(8))…") return resp.access_token } }