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>
这个提交包含在:
父节点
b1e5c52edf
当前提交
c9aff03a63
@ -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 = [
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户