mirror of
https://github.com/xtool-org/xtool.git
synced 2026-02-04 11:53:30 +01:00
114 lines
3.4 KiB
Swift
114 lines
3.4 KiB
Swift
//
|
|
// URLSession+HTTPClientProtocol.swift
|
|
//
|
|
//
|
|
// Created by Kabir Oberai on 05/05/21.
|
|
//
|
|
|
|
#if !os(Linux)
|
|
import Foundation
|
|
import ConcurrencyExtras
|
|
import OpenAPIRuntime
|
|
import OpenAPIURLSession
|
|
import Dependencies
|
|
|
|
public struct UnknownHTTPError: Error {}
|
|
|
|
extension HTTPClientDependencyKey: DependencyKey {
|
|
public static let liveValue: HTTPClientProtocol = Client()
|
|
}
|
|
|
|
private struct Client: HTTPClientProtocol {
|
|
final class ClientDelegate: NSObject, URLSessionWebSocketDelegate {
|
|
let webSocketCallbacks = LockIsolated<[
|
|
URLSessionWebSocketTask: @Sendable (URLSessionWebSocketTask.CloseCode?) -> Void
|
|
]>([:])
|
|
|
|
func urlSession(
|
|
_ session: URLSession,
|
|
webSocketTask: URLSessionWebSocketTask,
|
|
didOpenWithProtocol protocol: String?
|
|
) {
|
|
webSocketCallbacks.withValue { $0.removeValue(forKey: webSocketTask) }?(nil)
|
|
}
|
|
|
|
func urlSession(
|
|
_ session: URLSession,
|
|
webSocketTask: URLSessionWebSocketTask,
|
|
didCloseWith closeCode: URLSessionWebSocketTask.CloseCode,
|
|
reason: Data?
|
|
) {
|
|
webSocketCallbacks.withValue { $0.removeValue(forKey: webSocketTask) }?(closeCode)
|
|
}
|
|
}
|
|
|
|
private let clientDelegate = ClientDelegate()
|
|
private let session: URLSession
|
|
|
|
init() {
|
|
// no need for cert handling since the Apple Root CA is already
|
|
// installed on any devices which support the Security APIs which
|
|
// would have been required to add the custom root anyway
|
|
self.session = URLSession(configuration: .ephemeral, delegate: clientDelegate, delegateQueue: .main)
|
|
}
|
|
|
|
var asOpenAPITransport: any ClientTransport {
|
|
URLSessionTransport(configuration: .init(session: session))
|
|
}
|
|
|
|
public func makeWebSocket(url: URL) async throws -> any WebSocketSession {
|
|
let task = session.webSocketTask(with: url)
|
|
let (event, eventContinuation) = AsyncStream<URLSessionWebSocketTask.CloseCode?>.makeStream()
|
|
clientDelegate.webSocketCallbacks.withValue {
|
|
$0[task] = {
|
|
eventContinuation.yield($0)
|
|
eventContinuation.finish()
|
|
}
|
|
}
|
|
task.resume()
|
|
let code = await event.first { _ in true } ?? nil
|
|
if let code { throw Errors.webSocketClosed(code) }
|
|
return WebSocketSessionWrapper(task: task)
|
|
}
|
|
|
|
enum Errors: Error {
|
|
case webSocketClosed(URLSessionWebSocketTask.CloseCode)
|
|
}
|
|
}
|
|
|
|
private final class WebSocketSessionWrapper: WebSocketSession {
|
|
let task: URLSessionWebSocketTask
|
|
|
|
init(task: URLSessionWebSocketTask) {
|
|
self.task = task
|
|
}
|
|
|
|
func receive() async throws -> WebSocketMessage {
|
|
switch try await task.receive() {
|
|
case .data(let data): .data(data)
|
|
case .string(let text): .text(text)
|
|
@unknown default: .data(Data())
|
|
}
|
|
}
|
|
|
|
func send(_ message: WebSocketMessage) async throws {
|
|
let message: URLSessionWebSocketTask.Message = switch message {
|
|
case .data(let data): .data(data)
|
|
case .text(let text): .string(text)
|
|
}
|
|
try await task.send(message)
|
|
}
|
|
|
|
func close() {
|
|
task.cancel(with: .normalClosure, reason: nil)
|
|
}
|
|
}
|
|
|
|
extension Optional {
|
|
fileprivate mutating func inoutMap<E>(_ transform: (inout Wrapped) throws(E) -> Void) throws(E) {
|
|
if self != nil { try transform(&self!) }
|
|
}
|
|
}
|
|
|
|
#endif
|