Fix login

This commit is contained in:
Daniel Arantes Loverde
2026-02-21 18:51:39 -03:00
parent 0f5ad4c268
commit 3a3dc7217b
17 changed files with 1296 additions and 91 deletions

View File

@@ -0,0 +1,215 @@
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
@MainActor
final class OrderRealtimeTracker {
private var pollingTask: Task<Void, Never>? = nil
private var socketClient: OrderSocketClient? = nil
private var activeOrderId: String? = nil
var onOrderUpdated: ((PublicOrderResult) -> Void)?
func start(orderId: String, jwt: String?) {
stop()
activeOrderId = orderId
pollingTask = Task { @MainActor [weak self] in
guard let self else { return }
await self.runPollingLoop(orderId: orderId)
}
guard let jwt, jwt.isEmpty == false else { return }
let socket = OrderSocketClient()
socket.onOrderUpdate = { [weak self] update in
guard let self else { return }
guard update.id == orderId else { return }
Task { @MainActor [weak self] in
guard let self else { return }
await self.fetchLatest(orderId: orderId)
}
}
socket.connect(jwt: jwt)
socketClient = socket
}
func stop() {
pollingTask?.cancel()
pollingTask = nil
socketClient?.disconnect()
socketClient = nil
activeOrderId = nil
}
private func runPollingLoop(orderId: String) async {
var elapsedSeconds = 0
while Task.isCancelled == false {
if activeOrderId != orderId { return }
let fetched = await fetchLatest(orderId: orderId)
if fetched?.isFinalStatus == true {
return
}
let delay = pollingDelay(for: elapsedSeconds)
elapsedSeconds += delay
do {
try await Task.sleep(nanoseconds: UInt64(delay) * 1_000_000_000)
} catch {
return
}
}
}
private func pollingDelay(for elapsedSeconds: Int) -> Int {
if elapsedSeconds < 60 { return 3 }
if elapsedSeconds < 180 { return 5 }
return 10
}
@discardableResult
private func fetchLatest(orderId: String) async -> PublicOrderResult? {
do {
let response = try await ApiService().publicOrder(orderId: orderId)
guard response.error == false, let order = response.result else { return nil }
SessionStateStore.saveTrackedOrder(order)
onOrderUpdated?(order)
return order
} catch {
return nil
}
}
}
final class OrderSocketClient: @unchecked Sendable {
var onOrderUpdate: ((OrderRealtimeUpdate) -> Void)?
#if os(iOS) || os(macOS)
private var task: URLSessionWebSocketTask? = nil
private let session = URLSession(configuration: .default)
private var isConnected = false
private var pendingJWT: String? = nil
#endif
func connect(jwt: String) {
#if os(iOS) || os(macOS)
disconnect()
guard let url = makeSocketURL() else { return }
let wsTask = session.webSocketTask(with: url)
wsTask.resume()
task = wsTask
pendingJWT = jwt
receiveLoop()
#else
_ = jwt
#endif
}
func disconnect() {
#if os(iOS) || os(macOS)
isConnected = false
pendingJWT = nil
task?.cancel(with: .goingAway, reason: nil)
task = nil
#endif
}
#if os(iOS) || os(macOS)
private func receiveLoop() {
guard let task else { return }
task.receive { [weak self] result in
guard let self else { return }
switch result {
case .failure:
self.disconnect()
case .success(let message):
self.handleMessage(message)
self.receiveLoop()
}
}
}
private func handleMessage(_ message: URLSessionWebSocketTask.Message) {
let text: String
switch message {
case .string(let value):
text = value
case .data(let data):
text = String(data: data, encoding: .utf8) ?? ""
@unknown default:
return
}
guard text.isEmpty == false else { return }
if text == "2" {
task?.send(.string("3")) { _ in }
return
}
if text.hasPrefix("0"), let jwt = pendingJWT {
let authPacket = "40{\"token\":\"Bearer \(jwt)\"}"
task?.send(.string(authPacket)) { _ in }
pendingJWT = nil
return
}
if text.hasPrefix("40") {
isConnected = true
return
}
guard text.hasPrefix("42") else { return }
let eventPayload = String(text.dropFirst(2))
guard let data = eventPayload.data(using: .utf8) else { return }
if let rawArray = try? JSONSerialization.jsonObject(with: data) as? [Any],
rawArray.count >= 2,
let eventName = rawArray[0] as? String,
eventName == "order_update" {
let payloadAny = rawArray[1]
guard JSONSerialization.isValidJSONObject(payloadAny),
let payloadData = try? JSONSerialization.data(withJSONObject: payloadAny),
let update = try? JSONDecoder().decode(OrderRealtimeUpdate.self, from: payloadData) else {
return
}
onOrderUpdate?(update)
return
}
// Compat: alguns servidores podem encapsular o evento como objeto.
if let rawObject = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let eventName = (rawObject["event"] as? String)?.lowercased(),
eventName == "order_update",
let payloadAny = rawObject["data"],
JSONSerialization.isValidJSONObject(payloadAny),
let payloadData = try? JSONSerialization.data(withJSONObject: payloadAny),
let update = try? JSONDecoder().decode(OrderRealtimeUpdate.self, from: payloadData) {
onOrderUpdate?(update)
}
}
private func makeSocketURL() -> URL? {
guard var components = URLComponents(url: ApiConfig.baseURL, resolvingAgainstBaseURL: false) else {
return nil
}
if components.scheme == "https" {
components.scheme = "wss"
} else {
components.scheme = "ws"
}
components.path = "/socket.io/"
components.queryItems = [
URLQueryItem(name: "EIO", value: "4"),
URLQueryItem(name: "transport", value: "websocket")
]
return components.url
}
#endif
}