93 lines
3.2 KiB
Swift
93 lines
3.2 KiB
Swift
import Foundation
|
|
#if canImport(FoundationNetworking)
|
|
import FoundationNetworking
|
|
#endif
|
|
|
|
public actor WebSocketClient {
|
|
private var task: URLSessionWebSocketTask?
|
|
private let session: URLSession
|
|
private let serverURL: URL
|
|
private let token: String
|
|
private var isConnected = false
|
|
|
|
public init(serverURL: URL, token: String) {
|
|
self.serverURL = serverURL
|
|
self.token = token
|
|
self.session = URLSession(configuration: .default)
|
|
}
|
|
|
|
public func connect() {
|
|
var components = URLComponents(url: serverURL, resolvingAgainstBaseURL: false)!
|
|
components.scheme = serverURL.scheme == "https" ? "wss" : "ws"
|
|
components.path = "/socket"
|
|
components.queryItems = [.init(name: "api_key", value: token)]
|
|
guard let url = components.url else { return }
|
|
task = session.webSocketTask(with: url)
|
|
task?.resume()
|
|
isConnected = true
|
|
receiveMessage()
|
|
}
|
|
|
|
public func disconnect() {
|
|
task?.cancel(with: .goingAway, reason: nil)
|
|
isConnected = false
|
|
}
|
|
|
|
private func receiveMessage() {
|
|
task?.receive { [weak self] result in
|
|
Task { [weak self] in
|
|
switch result {
|
|
case .success(let message):
|
|
await self?.handleMessage(message)
|
|
await self?.receiveMessage()
|
|
case .failure:
|
|
await self?.reconnect()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func handleMessage(_ message: URLSessionWebSocketTask.Message) {
|
|
switch message {
|
|
case .string(let text):
|
|
guard let data = text.data(using: .utf8),
|
|
let event = try? JSONDecoder().decode(WebSocketEvent.self, from: data) else { return }
|
|
Task { @MainActor in
|
|
NotificationCenter.default.post(
|
|
name: .init(event.messageType),
|
|
object: event
|
|
)
|
|
}
|
|
case .data:
|
|
break
|
|
@unknown default:
|
|
break
|
|
}
|
|
}
|
|
|
|
private func reconnect() async {
|
|
guard isConnected else { return }
|
|
try? await Task.sleep(nanoseconds: 5_000_000_000)
|
|
connect()
|
|
}
|
|
}
|
|
|
|
public struct WebSocketEvent: Decodable {
|
|
public let messageType: String
|
|
public let data: [String: AnyDecodable]?
|
|
}
|
|
|
|
public struct AnyDecodable: Decodable {
|
|
public var value: Any
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.singleValueContainer()
|
|
if let intVal = try? container.decode(Int.self) { value = intVal }
|
|
else if let doubleVal = try? container.decode(Double.self) { value = doubleVal }
|
|
else if let boolVal = try? container.decode(Bool.self) { value = boolVal }
|
|
else if let stringVal = try? container.decode(String.self) { value = stringVal }
|
|
else if let arrayVal = try? container.decode([AnyDecodable].self) { value = arrayVal.map(\.value) }
|
|
else if let dictVal = try? container.decode([String: AnyDecodable].self) { value = dictVal.mapValues(\.value) }
|
|
else { throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "AnyDecodable error")) }
|
|
}
|
|
}
|