| | |
| | | /// especially around adoption of the typed throws feature in Swift 6. Please report any missing features or |
| | | /// bugs to https://github.com/Alamofire/Alamofire/issues. |
| | | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) |
| | | @_spi(WebSocket) public final class WebSocketRequest: Request { |
| | | @_spi(WebSocket) public final class WebSocketRequest: Request, @unchecked Sendable { |
| | | enum IncomingEvent { |
| | | case connected(protocol: String?) |
| | | case receivedMessage(URLSessionWebSocketTask.Message) |
| | |
| | | case completed(Completion) |
| | | } |
| | | |
| | | public struct Event<Success, Failure: Error> { |
| | | public enum Kind { |
| | | public struct Event<Success: Sendable, Failure: Error>: Sendable { |
| | | public enum Kind: Sendable { |
| | | case connected(protocol: String?) |
| | | case receivedMessage(Success) |
| | | case serializerFailed(Failure) |
| | |
| | | socket?.cancel() |
| | | } |
| | | |
| | | public func sendPing(respondingOn queue: DispatchQueue = .main, onResponse: @escaping (PingResponse) -> Void) { |
| | | public func sendPing(respondingOn queue: DispatchQueue = .main, onResponse: @escaping @Sendable (PingResponse) -> Void) { |
| | | socket?.sendPing(respondingOn: queue, onResponse: onResponse) |
| | | } |
| | | } |
| | | |
| | | public struct Completion { |
| | | public struct Completion: Sendable { |
| | | /// Last `URLRequest` issued by the instance. |
| | | public let request: URLRequest? |
| | | /// Last `HTTPURLResponse` received by the instance. |
| | |
| | | } |
| | | |
| | | /// Response to a sent ping. |
| | | public enum PingResponse { |
| | | public struct Pong { |
| | | public enum PingResponse: Sendable { |
| | | public struct Pong: Sendable { |
| | | let start: Date |
| | | let end: Date |
| | | let latency: TimeInterval |
| | |
| | | /// Received a pong with the associated state. |
| | | case pong(Pong) |
| | | /// Received an error. |
| | | case error(Error) |
| | | case error(any Error) |
| | | /// Did not send the ping, the request is cancelled or suspended. |
| | | case unsent |
| | | } |
| | |
| | | struct SocketMutableState { |
| | | var enqueuedSends: [(message: URLSessionWebSocketTask.Message, |
| | | queue: DispatchQueue, |
| | | completionHandler: (Result<Void, Error>) -> Void)] = [] |
| | | completionHandler: @Sendable (Result<Void, any Error>) -> Void)] = [] |
| | | var handlers: [(queue: DispatchQueue, handler: (_ event: IncomingEvent) -> Void)] = [] |
| | | var pingTimerItem: DispatchWorkItem? |
| | | } |
| | |
| | | task as? URLSessionWebSocketTask |
| | | } |
| | | |
| | | public let convertible: URLRequestConvertible |
| | | public let convertible: any URLRequestConvertible |
| | | public let configuration: Configuration |
| | | |
| | | init(id: UUID = UUID(), |
| | | convertible: URLRequestConvertible, |
| | | convertible: any URLRequestConvertible, |
| | | configuration: Configuration, |
| | | underlyingQueue: DispatchQueue, |
| | | serializationQueue: DispatchQueue, |
| | | eventMonitor: EventMonitor?, |
| | | interceptor: RequestInterceptor?, |
| | | delegate: RequestDelegate) { |
| | | eventMonitor: (any EventMonitor)?, |
| | | interceptor: (any RequestInterceptor)?, |
| | | delegate: any RequestDelegate) { |
| | | self.convertible = convertible |
| | | self.configuration = configuration |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | public func sendPing(respondingOn queue: DispatchQueue = .main, onResponse: @escaping (PingResponse) -> Void) { |
| | | @preconcurrency |
| | | public func sendPing(respondingOn queue: DispatchQueue = .main, onResponse: @escaping @Sendable (PingResponse) -> Void) { |
| | | guard isResumed else { |
| | | queue.async { onResponse(.unsent) } |
| | | return |
| | |
| | | } |
| | | |
| | | let item = DispatchWorkItem { [weak self] in |
| | | guard let self, self.isResumed else { return } |
| | | guard let self, isResumed else { return } |
| | | |
| | | self.sendPing(respondingOn: self.underlyingQueue) { response in |
| | | sendPing(respondingOn: underlyingQueue) { response in |
| | | guard case .pong = response else { return } |
| | | |
| | | self.startAutomaticPing(every: pingInterval) |
| | |
| | | } |
| | | } |
| | | |
| | | #if swift(>=5.8) |
| | | @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) |
| | | func startAutomaticPing(every duration: Duration) { |
| | | let interval = TimeInterval(duration.components.seconds) + (Double(duration.components.attoseconds) / 1e18) |
| | | startAutomaticPing(every: interval) |
| | | } |
| | | #endif |
| | | |
| | | func cancelAutomaticPing() { |
| | | socketMutableState.write { mutableState in |
| | |
| | | } |
| | | } |
| | | |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func streamSerializer<Serializer>( |
| | | _ serializer: Serializer, |
| | | on queue: DispatchQueue = .main, |
| | | handler: @escaping (_ event: Event<Serializer.Output, Serializer.Failure>) -> Void |
| | | ) -> Self where Serializer: WebSocketMessageSerializer, Serializer.Failure == Error { |
| | | handler: @escaping @Sendable (_ event: Event<Serializer.Output, Serializer.Failure>) -> Void |
| | | ) -> Self where Serializer: WebSocketMessageSerializer, Serializer.Failure == any Error { |
| | | forIncomingEvent(on: queue) { incomingEvent in |
| | | let event: Event<Serializer.Output, Serializer.Failure> |
| | | switch incomingEvent { |
| | |
| | | } |
| | | } |
| | | |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func streamDecodableEvents<Value>( |
| | | _ type: Value.Type = Value.self, |
| | | on queue: DispatchQueue = .main, |
| | | using decoder: DataDecoder = JSONDecoder(), |
| | | handler: @escaping (_ event: Event<Value, Error>) -> Void |
| | | using decoder: any DataDecoder = JSONDecoder(), |
| | | handler: @escaping @Sendable (_ event: Event<Value, any Error>) -> Void |
| | | ) -> Self where Value: Decodable { |
| | | streamSerializer(DecodableWebSocketMessageDecoder<Value>(decoder: decoder), on: queue, handler: handler) |
| | | } |
| | | |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func streamDecodable<Value>( |
| | | _ type: Value.Type = Value.self, |
| | | on queue: DispatchQueue = .main, |
| | | using decoder: DataDecoder = JSONDecoder(), |
| | | handler: @escaping (_ value: Value) -> Void |
| | | ) -> Self where Value: Decodable { |
| | | using decoder: any DataDecoder = JSONDecoder(), |
| | | handler: @escaping @Sendable (_ value: Value) -> Void |
| | | ) -> Self where Value: Decodable & Sendable { |
| | | streamDecodableEvents(Value.self, on: queue) { event in |
| | | event.message.map(handler) |
| | | } |
| | | } |
| | | |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func streamMessageEvents( |
| | | on queue: DispatchQueue = .main, |
| | | handler: @escaping (_ event: Event<URLSessionWebSocketTask.Message, Never>) -> Void |
| | | handler: @escaping @Sendable (_ event: Event<URLSessionWebSocketTask.Message, Never>) -> Void |
| | | ) -> Self { |
| | | forIncomingEvent(on: queue) { incomingEvent in |
| | | let event: Event<URLSessionWebSocketTask.Message, Never> |
| | | switch incomingEvent { |
| | | let event: Event<URLSessionWebSocketTask.Message, Never> = switch incomingEvent { |
| | | case let .connected(`protocol`): |
| | | event = .init(socket: self, kind: .connected(protocol: `protocol`)) |
| | | .init(socket: self, kind: .connected(protocol: `protocol`)) |
| | | case let .receivedMessage(message): |
| | | event = .init(socket: self, kind: .receivedMessage(message)) |
| | | .init(socket: self, kind: .receivedMessage(message)) |
| | | case let .disconnected(closeCode, reason): |
| | | event = .init(socket: self, kind: .disconnected(closeCode: closeCode, reason: reason)) |
| | | .init(socket: self, kind: .disconnected(closeCode: closeCode, reason: reason)) |
| | | case let .completed(completion): |
| | | event = .init(socket: self, kind: .completed(completion)) |
| | | .init(socket: self, kind: .completed(completion)) |
| | | } |
| | | |
| | | queue.async { handler(event) } |
| | | } |
| | | } |
| | | |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func streamMessages( |
| | | on queue: DispatchQueue = .main, |
| | | handler: @escaping (_ message: URLSessionWebSocketTask.Message) -> Void |
| | | handler: @escaping @Sendable (_ message: URLSessionWebSocketTask.Message) -> Void |
| | | ) -> Self { |
| | | streamMessageEvents(on: queue) { event in |
| | | event.message.map(handler) |
| | | } |
| | | } |
| | | |
| | | func forIncomingEvent(on queue: DispatchQueue, handler: @escaping (IncomingEvent) -> Void) -> Self { |
| | | func forIncomingEvent(on queue: DispatchQueue, handler: @escaping @Sendable (IncomingEvent) -> Void) -> Self { |
| | | socketMutableState.write { state in |
| | | state.handlers.append((queue: queue, handler: { incomingEvent in |
| | | self.serializationQueue.async { |
| | |
| | | return self |
| | | } |
| | | |
| | | @preconcurrency |
| | | public func send(_ message: URLSessionWebSocketTask.Message, |
| | | queue: DispatchQueue = .main, |
| | | completionHandler: @escaping (Result<Void, Error>) -> Void) { |
| | | completionHandler: @escaping @Sendable (Result<Void, any Error>) -> Void) { |
| | | guard !(isCancelled || isFinished) else { return } |
| | | |
| | | guard let socket else { |
| | |
| | | } |
| | | |
| | | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) |
| | | public protocol WebSocketMessageSerializer<Output, Failure> { |
| | | associatedtype Output |
| | | associatedtype Failure: Error = Error |
| | | public protocol WebSocketMessageSerializer<Output, Failure>: Sendable { |
| | | associatedtype Output: Sendable |
| | | associatedtype Failure: Error = any Error |
| | | |
| | | func decode(_ message: URLSessionWebSocketTask.Message) throws -> Output |
| | | } |
| | |
| | | } |
| | | |
| | | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) |
| | | public struct DecodableWebSocketMessageDecoder<Value: Decodable>: WebSocketMessageSerializer { |
| | | public struct DecodableWebSocketMessageDecoder<Value: Decodable & Sendable>: WebSocketMessageSerializer { |
| | | public enum Error: Swift.Error { |
| | | case decoding(Swift.Error) |
| | | case decoding(any Swift.Error) |
| | | case unknownMessage(description: String) |
| | | } |
| | | |
| | | public let decoder: DataDecoder |
| | | public let decoder: any DataDecoder |
| | | |
| | | public init(decoder: DataDecoder) { |
| | | public init(decoder: any DataDecoder) { |
| | | self.decoder = decoder |
| | | } |
| | | |