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>
这个提交包含在:
XuqmGroup 2026-05-22 17:56:52 +08:00
父节点 b1e5c52edf
当前提交 c9aff03a63
共有 4 个文件被更改,包括 63 次插入8 次删除

查看文件

@ -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<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
@ -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)
}

查看文件

@ -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?
}

查看文件

@ -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<U: Decodable>: Decodable { let data: U? }
guard let wrapper = try? JSONDecoder().decode(Wrapper<T>.self, from: data), let result = wrapper.data else {
guard let wrapper = try? JSONDecoder().decode(LicenseResponseWrapper<T>.self, from: data), let result = wrapper.data else {
throw URLError(.cannotDecodeContentData)
}
return result
}
}
private struct LicenseResponseWrapper<U: Decodable>: Decodable {
let data: U?
}

查看文件

@ -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 = [