// The owner's collector (stream-audit §12), as a player reaches it: a session's bundle to // `v1/sessions` when it ends, and its Event-mode CMCD batches to `v1/cmcd` while it plays. import Foundation public final class WaiCollector: @unchecked Sendable { public let base: URL private let session: URLSession public init(base: URL, session: URLSession = URLSession(configuration: .ephemeral)) { self.base = base self.session = session } /// Post a bundle; the collector's status and answer. public func postSession(_ bundle: Data, completion: @escaping @Sendable (Int, Data?) -> Void = { _, _ in }) { post("v1/sessions", type: "text/plain;charset=UTF-8", body: bundle, completion: completion) } /// Post an `application/cmcd` batch. Telemetry is advisory: a failure is dropped /// (stream-audit §7.2). public func postCMCD(_ batch: String) { guard !batch.isEmpty else { return } post("v1/cmcd", type: "application/cmcd", body: Data(batch.utf8)) { _, _ in } } private func post(_ path: String, type: String, body: Data, completion: @escaping @Sendable (Int, Data?) -> Void) { var r = URLRequest(url: base.appendingPathComponent(path)) r.httpMethod = "POST" r.setValue(type, forHTTPHeaderField: "Content-Type") r.httpBody = body session.dataTask(with: r) { d, resp, _ in completion((resp as? HTTPURLResponse)?.statusCode ?? 0, d) }.resume() } }