diff --git a/Sources/XuqmSDK/Core/XuqmSDK.swift b/Sources/XuqmSDK/Core/XuqmSDK.swift index f1c3e97..edc015f 100644 --- a/Sources/XuqmSDK/Core/XuqmSDK.swift +++ b/Sources/XuqmSDK/Core/XuqmSDK.swift @@ -12,11 +12,27 @@ public final class XuqmSDK: NSObject { private var userSig: String? private var cachedDeviceToken: String? private var lastInitializedAppKey: String? + private var isInitialized = false + private var initContinuations: [CheckedContinuation] = [] 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 @@ -25,6 +41,13 @@ public final class XuqmSDK: NSObject { 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() @@ -39,6 +62,16 @@ public final class XuqmSDK: NSObject { 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 @@ -87,5 +120,9 @@ public final class XuqmSDK: NSObject { } } } - +} + +public enum XuqmSDKError: Error { + case noLicenseFile + case packageMismatch(license: String, local: String) } diff --git a/Sources/XuqmSDK/License/LicenseModels.swift b/Sources/XuqmSDK/License/LicenseModels.swift index 0db7507..6b4108c 100644 --- a/Sources/XuqmSDK/License/LicenseModels.swift +++ b/Sources/XuqmSDK/License/LicenseModels.swift @@ -4,7 +4,11 @@ struct LicenseFile: Codable { let appKey: String let appName: String? let companyName: String? + let packageName: String? + let iosBundleId: String? + let harmonyBundleName: String? let baseUrl: String? + let serverUrl: String? let issuedAt: String? let expiresAt: String? } diff --git a/Sources/XuqmSDK/License/LicenseSDK.swift b/Sources/XuqmSDK/License/LicenseSDK.swift index f4a3c88..88a73d1 100644 --- a/Sources/XuqmSDK/License/LicenseSDK.swift +++ b/Sources/XuqmSDK/License/LicenseSDK.swift @@ -32,7 +32,10 @@ public final class LicenseSDK: @unchecked Sendable { /// Check license validity. Returns cached result within 10-minute window. /// On network error, falls back to last cached OK status. public func checkLicense(userInfo: LicenseUserInfo? = nil) async -> LicenseResult { - if !initialized { tryAutoInitialize() } + if !initialized { + await XuqmSDK.shared.awaitInitialization() + tryAutoInitialize() + } guard initialized, let appKey else { return .error("LicenseSDK not initialized") } @@ -79,8 +82,11 @@ public final class LicenseSDK: @unchecked Sendable { } } - public func getStatus() -> LicenseStatus { - if !initialized { tryAutoInitialize() } + public func getStatus() async -> LicenseStatus { + if !initialized { + await XuqmSDK.shared.awaitInitialization() + tryAutoInitialize() + } switch store.status { case Self.statusOk: return .ok case Self.statusDenied: return .denied @@ -88,8 +94,12 @@ public final class LicenseSDK: @unchecked Sendable { } } - public func getDeviceId() -> String? { - if !initialized && !tryAutoInitialize() { return nil } + public func getDeviceId() async -> String? { + if !initialized { + await XuqmSDK.shared.awaitInitialization() + tryAutoInitialize() + } + if !initialized { return nil } return store.deviceId } @@ -122,10 +132,13 @@ public final class LicenseSDK: @unchecked Sendable { guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else { throw URLError(.badServerResponse) } - struct Wrapper: Decodable { let data: U? } - guard let wrapper = try? JSONDecoder().decode(Wrapper.self, from: data), let result = wrapper.data else { + guard let wrapper = try? JSONDecoder().decode(LicenseResponseWrapper.self, from: data), let result = wrapper.data else { throw URLError(.cannotDecodeContentData) } return result } } + +private struct LicenseResponseWrapper: Decodable { + let data: U? +} diff --git a/Sources/XuqmSDK/Update/UpdateSDK.swift b/Sources/XuqmSDK/Update/UpdateSDK.swift index aabc66d..255b46a 100644 --- a/Sources/XuqmSDK/Update/UpdateSDK.swift +++ b/Sources/XuqmSDK/Update/UpdateSDK.swift @@ -19,6 +19,7 @@ public final class UpdateSDK { private init() {} public func checkAppUpdate(currentVersionCode: Int) async throws -> AppUpdateInfo { + await XuqmSDK.shared.awaitInitialization() let config = XuqmSDK.shared.requireConfig() let userId = XuqmSDK.shared.currentUserId var queryItems = [