import Foundation
/// A completed HTTP exchange.
public struct HTTPResponse: Sendable {
public var status: Int
public var headers: [String: String]
public var body: Data
public init(status: Int, headers: [String: String] = [:], body: Data = Data()) {
self.status = status
self.headers = headers
self.body = body
}
public var isSuccess: Bool { (200..<300).contains(status) }
}
/// The HTTP layer, injectable so tests run offline.
public protocol HTTPTransport: Sendable {
func send(_ request: URLRequest) async throws -> HTTPResponse
}
/// `URLSession`-backed transport. Network failures surface as `P2PError.transport`.
public struct URLSessionHTTPTransport: HTTPTransport {
let session: URLSession
public init(session: URLSession = .shared) { self.session = session }
public func send(_ request: URLRequest) async throws -> HTTPResponse {
do {
let (data, response) = try await session.data(for: request)
guard let http = response as? HTTPURLResponse else { throw P2PError.transport("non-HTTP response") }
var headers: [String: String] = [:]
for (k, v) in http.allHeaderFields { if let k = k as? String, let v = v as? String { headers[k.lowercased()] = v } }
return HTTPResponse(status: http.statusCode, headers: headers, body: data)
} catch let e as P2PError {
throw e
} catch let e as URLError where e.code == .timedOut {
throw P2PError.timeout
} catch {
throw P2PError.transport(error.localizedDescription)
}
}
}
public extension URLRequest {
/// Builds a request with the SDK's common headers.
static func p2p(_ url: URL, method: String = "GET", origin: String?, timeout: TimeInterval) -> URLRequest {
var r = URLRequest(url: url, timeoutInterval: timeout)
r.httpMethod = method
r.setValue("application/json", forHTTPHeaderField: "Accept")
if let origin { r.setValue(origin, forHTTPHeaderField: "Origin") }
return r
}
}