136 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			136 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
import Foundation
 | 
						|
#if canImport(SocketIO)
 | 
						|
import SocketIO
 | 
						|
#endif
 | 
						|
 | 
						|
final class SocketService {
 | 
						|
    static let shared = SocketService()
 | 
						|
 | 
						|
    private let syncQueue = DispatchQueue(label: "org.yobble.socket.service")
 | 
						|
    private var currentToken: String?
 | 
						|
    private var currentAuthPayload: [String: Any] = [:]
 | 
						|
 | 
						|
    #if canImport(SocketIO)
 | 
						|
    private var manager: SocketManager?
 | 
						|
    private var socket: SocketIOClient?
 | 
						|
    #endif
 | 
						|
 | 
						|
    private init() {}
 | 
						|
 | 
						|
    func connectForCurrentUser() {
 | 
						|
        syncQueue.async { [weak self] in
 | 
						|
            guard let self else { return }
 | 
						|
            guard let token = self.resolveCurrentAccessToken() else {
 | 
						|
                if AppConfig.DEBUG { print("[SocketService] No access token available, disconnecting") }
 | 
						|
                self.currentToken = nil
 | 
						|
                self.disconnectInternal()
 | 
						|
                return
 | 
						|
            }
 | 
						|
 | 
						|
            self.connectInternal(with: token)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    func connect(withToken token: String) {
 | 
						|
        syncQueue.async { [weak self] in
 | 
						|
            self?.connectInternal(with: token)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    func disconnect() {
 | 
						|
        syncQueue.async { [weak self] in
 | 
						|
            guard let self else { return }
 | 
						|
            self.currentToken = nil
 | 
						|
            self.disconnectInternal()
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private func resolveCurrentAccessToken() -> String? {
 | 
						|
        guard
 | 
						|
            let login = UserDefaults.standard.string(forKey: "currentUser"),
 | 
						|
            !login.isEmpty
 | 
						|
        else {
 | 
						|
            return nil
 | 
						|
        }
 | 
						|
 | 
						|
        return KeychainService.shared.get(forKey: "access_token", service: login)
 | 
						|
    }
 | 
						|
 | 
						|
    private func connectInternal(with token: String) {
 | 
						|
        #if canImport(SocketIO)
 | 
						|
        if token == currentToken,
 | 
						|
           let socket,
 | 
						|
           socket.status == .connected || socket.status == .connecting {
 | 
						|
            if AppConfig.DEBUG { print("[SocketService] Already connected with current token") }
 | 
						|
            return
 | 
						|
        }
 | 
						|
 | 
						|
        currentToken = token
 | 
						|
        currentAuthPayload = ["token": token]
 | 
						|
        setupSocket(with: token)
 | 
						|
        socket?.connect(withPayload: currentAuthPayload)
 | 
						|
        #else
 | 
						|
        if AppConfig.DEBUG {
 | 
						|
            print("[SocketService] SocketIO framework not available; skipping connection")
 | 
						|
        }
 | 
						|
        #endif
 | 
						|
    }
 | 
						|
 | 
						|
    #if canImport(SocketIO)
 | 
						|
    private func setupSocket(with token: String) {
 | 
						|
        guard let baseURL = URL(string: AppConfig.API_SERVER) else {
 | 
						|
            if AppConfig.DEBUG { print("[SocketService] Invalid socket URL: \(AppConfig.API_SERVER)") }
 | 
						|
            return
 | 
						|
        }
 | 
						|
 | 
						|
        disconnectInternal()
 | 
						|
 | 
						|
        let configuration: SocketIOClientConfiguration = [
 | 
						|
            .log(AppConfig.DEBUG),
 | 
						|
            .compress,
 | 
						|
            .secure(AppConfig.PROTOCOL.lowercased() == "https"),
 | 
						|
            .path(AppConfig.SOCKET_PATH),
 | 
						|
            .reconnects(true),
 | 
						|
            .reconnectWait(2),
 | 
						|
            .forceWebsockets(true),
 | 
						|
            .extraHeaders(["Authorization": "Bearer \(token)"]),
 | 
						|
            .connectParams(["token": token])
 | 
						|
        ]
 | 
						|
 | 
						|
        let manager = SocketManager(socketURL: baseURL, config: configuration)
 | 
						|
        manager.handleQueue = syncQueue
 | 
						|
        let socket = manager.defaultSocket
 | 
						|
 | 
						|
        if AppConfig.DEBUG {
 | 
						|
            socket.onAny { event in
 | 
						|
                print("[SocketService] onAny event=\(event.event) data=\(event.items ?? [])")
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        socket.on(clientEvent: .connect) { _, _ in
 | 
						|
            if AppConfig.DEBUG { print("[SocketService] Connected") }
 | 
						|
        }
 | 
						|
 | 
						|
        socket.on(clientEvent: .disconnect) { data, _ in
 | 
						|
            if AppConfig.DEBUG { print("[SocketService] Disconnected: \(data)") }
 | 
						|
        }
 | 
						|
 | 
						|
        socket.on(clientEvent: .error) { data, _ in
 | 
						|
            if AppConfig.DEBUG { print("[SocketService] Error: \(data)") }
 | 
						|
        }
 | 
						|
 | 
						|
        self.manager = manager
 | 
						|
        self.socket = socket
 | 
						|
    }
 | 
						|
 | 
						|
    private func disconnectInternal() {
 | 
						|
        socket?.disconnect()
 | 
						|
        manager?.disconnect()
 | 
						|
        socket = nil
 | 
						|
        manager = nil
 | 
						|
    }
 | 
						|
    #else
 | 
						|
    private func disconnectInternal() { }
 | 
						|
    #endif
 | 
						|
}
 |