XuqmGroup-iOSSDK/Sources/XuqmSDK/Core/XuqmSDK.swift
XuqmGroup c9aff03a63 sdk: auto-init from license file, init continuation waiting
- XuqmSDK: autoInitialize() validates iosBundleId; awaitInitialization() with CheckedContinuation
- LicenseSDK: await XuqmSDK initialization before license operations
- UpdateSDK: await XuqmSDK init before checkAppUpdate
- LicenseModels: add packageName, iosBundleId, harmonyBundleName, serverUrl fields

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:56:52 +08:00

129 行
3.9 KiB
Swift

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?
private var userSig: String?
private var cachedDeviceToken: String?
private var lastInitializedAppKey: String?
private var isInitialized = false
private var initContinuations: [CheckedContinuation<Void, Never>] = []
private override init() {
super.init()
}
/// Auto-initialize from the embedded license file (Bundle.main/xuqm/license.xuqm).
/// Validates the bundle ID / package name against the license file.
public func autoInitialize(debug: Bool = false) throws {
guard let file = LicenseFileReader.read() else {
throw XuqmSDKError.noLicenseFile
}
let bundleId = Bundle.main.bundleIdentifier ?? ""
if let licenseBundle = file.iosBundleId, !licenseBundle.isEmpty, licenseBundle != bundleId {
throw XuqmSDKError.packageMismatch(license: licenseBundle, local: bundleId)
}
let cfg = SDKConfig(appKey: file.appKey, debug: debug)
initialize(config: cfg)
}
public func initialize(config: SDKConfig) {
if let lastInitializedAppKey, lastInitializedAppKey == config.appKey {
return
}
self.config = config
self.lastInitializedAppKey = config.appKey
self.tokenStore = TokenStore()
ApiClient.shared.configure(with: config)
isInitialized = true
// Resume any waiting continuations
let continuations = initContinuations
initContinuations.removeAll()
for cont in continuations {
cont.resume()
}
if config.autoRegisterPush {
Task { @MainActor in
try? await PushSDK.shared.start()
}
}
}
public func requireConfig() -> SDKConfig {
guard let config else {
fatalError("XuqmSDK not initialized. Call XuqmSDK.shared.initialize() first.")
}
return config
}
public var initialized: Bool { isInitialized }
/// Wait for initialization to complete.
public func awaitInitialization() async {
guard !isInitialized else { return }
await withCheckedContinuation { cont in
initContinuations.append(cont)
}
}
public func login(userId: String, userSig: String) async {
if currentUserId == userId && self.userSig == userSig {
return
}
self.currentUserId = userId
self.userSig = userSig
do {
try await ImSDK.shared.login(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 {
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)
}
}
}
}
public enum XuqmSDKError: Error {
case noLicenseFile
case packageMismatch(license: String, local: String)
}