WebSocket.swift (3099B)
1 // 2 // WebSocket.swift 3 // damus 4 // 5 // Created by Bryan Montz on 4/13/23. 6 // 7 8 import Combine 9 import Foundation 10 11 enum WebSocketEvent { 12 case connected 13 case message(URLSessionWebSocketTask.Message) 14 case disconnected(URLSessionWebSocketTask.CloseCode, String?) 15 case error(Error) 16 17 var description: String? { 18 switch self { 19 case .connected: 20 return "Connected" 21 case .message: 22 return nil // adding this to the RelayLog was too noisy 23 case .disconnected(let close_code, let reason): 24 return "Disconnected: Close code: \(close_code), reason: \(reason ?? "unknown")" 25 case .error(let error): 26 return "Error: \(error)" 27 } 28 } 29 } 30 31 final class WebSocket: NSObject, URLSessionWebSocketDelegate { 32 33 private let url: URL 34 private let session: URLSession 35 private lazy var webSocketTask: URLSessionWebSocketTask = { 36 let task = session.webSocketTask(with: url) 37 task.delegate = self 38 return task 39 }() 40 41 let subject = PassthroughSubject<WebSocketEvent, Never>() 42 43 init(_ url: URL, session: URLSession = .shared) { 44 self.url = url 45 self.session = session 46 } 47 48 func ping(receiveHandler: @escaping (Error?) -> Void) { 49 self.webSocketTask.sendPing(pongReceiveHandler: receiveHandler) 50 } 51 52 func connect() { 53 resume() 54 } 55 56 func disconnect(closeCode: URLSessionWebSocketTask.CloseCode = .normalClosure, reason: Data? = nil) { 57 webSocketTask.cancel(with: closeCode, reason: reason) 58 59 // reset after disconnecting to be ready for reconnecting 60 let task = session.webSocketTask(with: url) 61 task.delegate = self 62 webSocketTask = task 63 64 let reason_str: String? 65 if let reason { 66 reason_str = String(data: reason, encoding: .utf8) 67 } else { 68 reason_str = nil 69 } 70 subject.send(.disconnected(closeCode, reason_str)) 71 } 72 73 func send(_ message: URLSessionWebSocketTask.Message) { 74 webSocketTask.send(message) { [weak self] error in 75 if let error { 76 self?.subject.send(.error(error)) 77 } 78 } 79 } 80 81 private func resume() { 82 webSocketTask.receive { [weak self] result in 83 switch result { 84 case .success(let message): 85 self?.subject.send(.message(message)) 86 self?.resume() 87 case .failure(let error): 88 self?.subject.send(.error(error)) 89 } 90 } 91 92 webSocketTask.resume() 93 } 94 95 // MARK: - URLSessionWebSocketDelegate 96 97 func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol theProtocol: String?) { 98 subject.send(.connected) 99 } 100 101 func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) { 102 disconnect(closeCode: closeCode, reason: reason) 103 } 104 }