Skip to content

Commit

Permalink
Socket Refresh (#7)
Browse files Browse the repository at this point in the history
Flipped 'Socket' / 'WebSocket' definitions.
  • Loading branch information
richardpiazza authored Feb 25, 2023
1 parent eb243be commit d15b52f
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import FoundationNetworking
#endif
#if canImport(ObjectiveC)

open class BaseURLSocket: NSObject, Socket {
open class AbsoluteURLWebSocket: NSObject, WebSocket {

private typealias ResumeHandler = (Result<Void, Error>) -> Void

Expand All @@ -17,7 +17,7 @@ open class BaseURLSocket: NSObject, Socket {
lazy var task = session.webSocketTask(with: urlRequest)

private var keepAliveTask: Task<Void, Never>?
private var messageSequence: PassthroughAsyncThrowingSequence<WebSocket.Message> = .init()
private var messageSequence: PassthroughAsyncThrowingSequence<Socket.Message> = .init()
private var resumeHandler: ResumeHandler?
private var pingContinuation: CheckedContinuation<Void, Error>?

Expand Down Expand Up @@ -80,12 +80,12 @@ open class BaseURLSocket: NSObject, Socket {
session.invalidateAndCancel()
}

public func send(_ message: WebSocket.Message) async throws {
public func send(_ message: Socket.Message) async throws {
let taskMessage = URLSessionWebSocketTask.Message(message)
try await task.send(taskMessage)
}

public func receive() -> AsyncThrowingStream<WebSocket.Message, Error> {
public func receive() -> AsyncThrowingStream<Socket.Message, Error> {
messageSequence = .init()
return messageSequence.stream
}
Expand Down Expand Up @@ -140,7 +140,7 @@ open class BaseURLSocket: NSObject, Socket {

stop()
case .success(let message):
let message = WebSocket.Message(message)
let message = Socket.Message(message)
messageSequence.yield(message)

// Oddity of the `URLSessionWebSocketTask` implementation. Requires re-assignment of the
Expand All @@ -152,11 +152,11 @@ open class BaseURLSocket: NSObject, Socket {
}
}

extension BaseURLSocket: URLSessionDelegate {
extension AbsoluteURLWebSocket: URLSessionDelegate {

}

extension BaseURLSocket: URLSessionTaskDelegate {
extension AbsoluteURLWebSocket: URLSessionTaskDelegate {
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let error = error else {
return
Expand All @@ -169,7 +169,7 @@ extension BaseURLSocket: URLSessionTaskDelegate {
}
}

extension BaseURLSocket: URLSessionWebSocketDelegate {
extension AbsoluteURLWebSocket: URLSessionWebSocketDelegate {
public func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
print("WebSocket Opened; Protocol: '\(`protocol` ?? "")'")
resumeHandler?(.success(()))
Expand All @@ -183,7 +183,7 @@ extension BaseURLSocket: URLSessionWebSocketDelegate {
}

public func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, closeReason: Data?) {
let code = WebSocket.CloseCode(closeCode)
let code = Socket.CloseCode(closeCode)
let reason = String(data: closeReason ?? Data(), encoding: .utf8) ?? ""

if code == .normalClosure {
Expand Down
98 changes: 93 additions & 5 deletions Sources/SessionPlus/Interface/Socket.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,96 @@
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

public protocol Socket {
func start() async throws
func stop()
func receive() -> AsyncThrowingStream<WebSocket.Message, Error>
func send(_ message: WebSocket.Message) async throws
/// Namespace for socket related types
public enum Socket {
/// A code that indicates why a WebSocket connection closed.
///
/// These follow the close codes defined in RFC 6455
public enum CloseCode: Int, Codable, CaseIterable {
/// A code that indicates the connection is still open.
case invalid = 0
/// A code that indicates normal connection closure.
case normalClosure = 1000
/// A code that indicates an endpoint is going away.
case goingAway = 1001
/// A code that indicates an endpoint terminated the connection due to a protocol error.
case protocolError = 1002
/// A code that indicates an endpoint terminated the connection after receiving a type of data it can’t accept.
case unsupportedData = 1003
/// A reserved code that indicates an endpoint expected a status code and didn’t receive one.
case noStatusReceived = 1005
/// A reserved code that indicates the connection closed without a close control frame.
case abnormalClosure = 1006
/// A code that indicates the server terminated the connection because it received data inconsistent with the message’s type.
case invalidFramePayloadData = 1007
/// A code that indicates an endpoint terminated the connection because it received a message that violates its policy.
case policyViolation = 1008
/// A code that indicates an endpoint is terminating the connection because it received a message too big for it to process.
case messageTooBig = 1009
/// A code that indicates the client terminated the connection because the server didn’t negotiate a required extension.
case mandatoryExtensionMissing = 1010
/// A code that indicates the server terminated the connection because it encountered an unexpected condition.
case internalServerError = 1011
/// A reserved code that indicates the connection closed due to the failure to perform a TLS handshake.
case tlsHandshakeFailure = 1015
}

public enum Message: Codable {
case data(Data)
case string(String)
}
}

extension Socket.CloseCode: CustomStringConvertible {
public var description: String {
switch self {
case .invalid: return "A code that indicates the connection is still open."
case .normalClosure: return "A code that indicates normal connection closure."
case .goingAway: return "A code that indicates an endpoint is going away."
case .protocolError: return "A code that indicates an endpoint terminated the connection due to a protocol error."
case .unsupportedData: return "A code that indicates an endpoint terminated the connection after receiving a type of data it can’t accept."
case .noStatusReceived: return "A reserved code that indicates an endpoint expected a status code and didn’t receive one."
case .abnormalClosure: return "A reserved code that indicates the connection closed without a close control frame."
case .invalidFramePayloadData: return "A code that indicates the server terminated the connection because it received data inconsistent with the message’s type."
case .policyViolation: return "A code that indicates an endpoint terminated the connection because it received a message that violates its policy."
case .messageTooBig: return "A code that indicates an endpoint is terminating the connection because it received a message too big for it to process."
case .mandatoryExtensionMissing: return "A code that indicates the client terminated the connection because the server didn’t negotiate a required extension."
case .internalServerError: return "A code that indicates the server terminated the connection because it encountered an unexpected condition."
case .tlsHandshakeFailure: return "A reserved code that indicates the connection closed due to the failure to perform a TLS handshake."
}
}
}

#if canImport(ObjectiveC)
public extension Socket.CloseCode {
init(_ closeCode: URLSessionWebSocketTask.CloseCode) {
self = Self.allCases.first(where: { $0.rawValue == closeCode.rawValue }) ?? .invalid
}
}

public extension Socket.Message {
init(_ message: URLSessionWebSocketTask.Message) {
switch message {
case .data(let value):
self = .data(value)
case .string( let value):
self = .string(value)
@unknown default:
self = .string("\(message)")
}
}
}

public extension URLSessionWebSocketTask.Message {
init(_ message: Socket.Message) {
switch message {
case .data(let value):
self = .data(value)
case .string(let value):
self = .string(value)
}
}
}
#endif
66 changes: 0 additions & 66 deletions Sources/SessionPlus/Interface/WebSocket+CloseCode.swift

This file was deleted.

37 changes: 0 additions & 37 deletions Sources/SessionPlus/Interface/WebSocket+Message.swift

This file was deleted.

18 changes: 16 additions & 2 deletions Sources/SessionPlus/Interface/WebSocket.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
import Foundation

/// Namespace for WebSocket related types
public enum WebSocket {
/// Communications protocol providing full-duplex communication channels over a single connection.
///
/// The WebSocket protocol enables interaction between a web browser (or other client application)
/// and a web server with lower overhead than half-duplex alternatives such as HTTP polling, facilitating
/// real-time data transfer from and to the server. This is made possible by providing a standardized
/// way for the server to send content to the client without being first requested by the client, and
/// allowing messages to be passed back and forth while keeping the connection open.
public protocol WebSocket {
/// Initialize the `WebSocket` connection
func start() async throws
/// Terminate the connection
func stop()
/// Send a `Socket.Message`
func send(_ message: Socket.Message) async throws
/// Receive an asynchronous stream of `Socket.Message`.
func receive() -> AsyncThrowingStream<Socket.Message, Error>
}

0 comments on commit d15b52f

Please sign in to comment.