// // ScenicQueueSocketClient.swift // suixinkan // import Foundation /// 排队 WebSocket 客户端,订阅打卡点队列刷新与叫号事件。 final class ScenicQueueSocketClient { var onMessage: ((ScenicQueueSocketMessage) -> Void)? private let environment: APIEnvironment private let session: URLSession private let decoder = JSONDecoder() private var task: URLSessionWebSocketTask? private var connectedSpotId: Int64? init(environment: APIEnvironment = .current, session: URLSession = .shared) { self.environment = environment self.session = session } /// 建立连接并订阅指定打卡点。 func connect(socketToken: String, scenicId: Int64, scenicSpotId: Int64) { guard !socketToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return } disconnect() connectedSpotId = scenicSpotId let webSocketTask = session.webSocketTask(with: environment.webSocketURL) task = webSocketTask webSocketTask.resume() sendSubscribePayloads(scenicSpotId: scenicSpotId) receiveLoop() } /// 断开连接。 func disconnect() { task?.cancel(with: .normalClosure, reason: nil) task = nil connectedSpotId = nil } private func sendSubscribePayloads(scenicSpotId: Int64) { [ #"{"type":306,"params":{"scenic_spot_id":\#(scenicSpotId)}}"#, #"{"type":307,"params":{"scenic_spot_id":\#(scenicSpotId)}}"#, ].forEach { payload in task?.send(.string(payload)) { _ in } } } private func receiveLoop() { task?.receive { [weak self] result in guard let self else { return } switch result { case .success(let message): if case .string(let text) = message { self.handle(text) } self.receiveLoop() case .failure: self.task = nil } } } private func handle(_ text: String) { let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) guard trimmed.hasPrefix("{"), let data = trimmed.data(using: .utf8) else { return } guard let message = try? decoder.decode(ScenicQueueSocketMessage.self, from: data) else { return } guard message.isQueueEvent else { return } onMessage?(message) } }