import Foundation @MainActor public final class XuqmSDK: NSObject { public static let shared = XuqmSDK() private(set) var config: SDKConfig? private(set) var tokenStore: TokenStore? public private(set) var currentUserId: String? public var onUserSigExpired: (() -> Void)? private var userSig: String? private var userSigTimer: Timer? private var cachedDeviceToken: String? private override init() { super.init() } public func initialize(config: SDKConfig) { self.config = config self.tokenStore = TokenStore() ApiClient.shared.configure(with: config) } public func requireConfig() -> SDKConfig { guard let config else { fatalError("XuqmSDK not initialized. Call XuqmSDK.shared.initialize() first.") } return config } public func login(userId: String, userSig: String) async { self.currentUserId = userId self.userSig = userSig startUserSigExpirationTimer(userSig: userSig) do { try await ImSDK.shared.loginWithUserSig(userId, userSig) } catch { // IM login failed; silently ignored per facade pattern } if let cachedDeviceToken { do { try await PushSDK.shared.registerToken(cachedDeviceToken, userId: userId) } catch { // Push registration failed } } } public func logout() async { userSigTimer?.invalidate() userSigTimer = nil ImSDK.shared.disconnect() if let userId = currentUserId { do { try await PushSDK.shared.unregisterToken(userId: userId) } catch { // Push unregistration failed } } tokenStore?.clear() currentUserId = nil userSig = nil cachedDeviceToken = nil } public func registerDeviceToken(_ deviceToken: Data) { let token = deviceToken.map { String(format: "%02x", $0) }.joined() self.cachedDeviceToken = token Task { @MainActor in if let userId = self.currentUserId { try? await PushSDK.shared.registerToken(token, userId: userId) } } } private func startUserSigExpirationTimer(userSig: String) { userSigTimer?.invalidate() guard let expDate = extractExpirationDate(from: userSig) else { return } let interval = expDate.timeIntervalSinceNow - 300 // 5 minutes before expiry guard interval > 0 else { onUserSigExpired?() return } userSigTimer = Timer.scheduledTimer( timeInterval: interval, target: self, selector: #selector(userSigDidExpire), userInfo: nil, repeats: false ) } @objc private func userSigDidExpire() { onUserSigExpired?() } private func extractExpirationDate(from userSig: String) -> Date? { let parts = userSig.split(separator: ".") guard parts.count >= 2 else { return nil } var base64 = String(parts[1]) .replacingOccurrences(of: "-", with: "+") .replacingOccurrences(of: "_", with: "/") let padding = 4 - base64.count % 4 if padding != 4 { base64 += String(repeating: "=", count: padding) } guard let data = Data(base64Encoded: base64), let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let exp = json["exp"] as? TimeInterval else { return nil } return Date(timeIntervalSince1970: exp) } }