import Foundation public struct ApiResponse: Decodable { public let code: Int public let status: String public let data: T? public let message: String } public struct EmptyResponse: Decodable, Sendable { public init() {} } public final class ApiClient: @unchecked Sendable { public static let shared = ApiClient() private var config: SDKConfig? private var session: URLSession = .shared private init() {} func configure(with config: SDKConfig) { self.config = config } public func request( path: String, method: String = "GET", queryItems: [URLQueryItem]? = nil, body: (some Encodable)? = nil as String? ) async throws -> T { guard let config else { throw URLError(.badURL) } let tokenStore = await XuqmSDK.shared.tokenStore var components = URLComponents(url: SDKEndpoints.apiBaseURL.appendingPathComponent(path), resolvingAgainstBaseURL: false)! if let qi = queryItems { components.queryItems = qi } var req = URLRequest(url: components.url!) req.httpMethod = method req.setValue("application/json", forHTTPHeaderField: "Content-Type") if let token = tokenStore?.get() { req.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") } if let b = body { req.httpBody = try JSONEncoder().encode(b) } if config.debug { print("[XuqmSDK][HTTP] request", method, req.url?.absoluteString ?? path) } let (data, response) = try await session.data(for: req) guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else { if config.debug, let http = response as? HTTPURLResponse { print("[XuqmSDK][HTTP] response", http.statusCode, req.url?.absoluteString ?? path) } throw URLError(.badServerResponse) } if config.debug { print("[XuqmSDK][HTTP] response", http.statusCode, req.url?.absoluteString ?? path) } let wrapper = try JSONDecoder().decode(ApiResponse.self, from: data) if let result = wrapper.data { return result } if T.self == EmptyResponse.self { return EmptyResponse() as! T } throw URLError(.cannotDecodeContentData) } }