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 userSig: String?
|
||||||
private var cachedDeviceToken: String?
|
private var cachedDeviceToken: String?
|
||||||
private var lastInitializedAppKey: String?
|
private var lastInitializedAppKey: String?
|
||||||
|
private var isInitialized = false
|
||||||
|
private var initContinuations: [CheckedContinuation<Void, Never>] = []
|
||||||
|
|
||||||
private override init() {
|
private override init() {
|
||||||
super.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) {
|
public func initialize(config: SDKConfig) {
|
||||||
if let lastInitializedAppKey, lastInitializedAppKey == config.appKey {
|
if let lastInitializedAppKey, lastInitializedAppKey == config.appKey {
|
||||||
return
|
return
|
||||||
@ -25,6 +41,13 @@ public final class XuqmSDK: NSObject {
|
|||||||
self.lastInitializedAppKey = config.appKey
|
self.lastInitializedAppKey = config.appKey
|
||||||
self.tokenStore = TokenStore()
|
self.tokenStore = TokenStore()
|
||||||
ApiClient.shared.configure(with: config)
|
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 {
|
if config.autoRegisterPush {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
try? await PushSDK.shared.start()
|
try? await PushSDK.shared.start()
|
||||||
@ -39,6 +62,16 @@ public final class XuqmSDK: NSObject {
|
|||||||
return config
|
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 {
|
public func login(userId: String, userSig: String) async {
|
||||||
if currentUserId == userId && self.userSig == userSig {
|
if currentUserId == userId && self.userSig == userSig {
|
||||||
return
|
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 appKey: String
|
||||||
let appName: String?
|
let appName: String?
|
||||||
let companyName: String?
|
let companyName: String?
|
||||||
|
let packageName: String?
|
||||||
|
let iosBundleId: String?
|
||||||
|
let harmonyBundleName: String?
|
||||||
let baseUrl: String?
|
let baseUrl: String?
|
||||||
|
let serverUrl: String?
|
||||||
let issuedAt: String?
|
let issuedAt: String?
|
||||||
let expiresAt: String?
|
let expiresAt: String?
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,10 @@ public final class LicenseSDK: @unchecked Sendable {
|
|||||||
/// Check license validity. Returns cached result within 10-minute window.
|
/// Check license validity. Returns cached result within 10-minute window.
|
||||||
/// On network error, falls back to last cached OK status.
|
/// On network error, falls back to last cached OK status.
|
||||||
public func checkLicense(userInfo: LicenseUserInfo? = nil) async -> LicenseResult {
|
public func checkLicense(userInfo: LicenseUserInfo? = nil) async -> LicenseResult {
|
||||||
if !initialized { tryAutoInitialize() }
|
if !initialized {
|
||||||
|
await XuqmSDK.shared.awaitInitialization()
|
||||||
|
tryAutoInitialize()
|
||||||
|
}
|
||||||
guard initialized, let appKey else {
|
guard initialized, let appKey else {
|
||||||
return .error("LicenseSDK not initialized")
|
return .error("LicenseSDK not initialized")
|
||||||
}
|
}
|
||||||
@ -79,8 +82,11 @@ public final class LicenseSDK: @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getStatus() -> LicenseStatus {
|
public func getStatus() async -> LicenseStatus {
|
||||||
if !initialized { tryAutoInitialize() }
|
if !initialized {
|
||||||
|
await XuqmSDK.shared.awaitInitialization()
|
||||||
|
tryAutoInitialize()
|
||||||
|
}
|
||||||
switch store.status {
|
switch store.status {
|
||||||
case Self.statusOk: return .ok
|
case Self.statusOk: return .ok
|
||||||
case Self.statusDenied: return .denied
|
case Self.statusDenied: return .denied
|
||||||
@ -88,8 +94,12 @@ public final class LicenseSDK: @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getDeviceId() -> String? {
|
public func getDeviceId() async -> String? {
|
||||||
if !initialized && !tryAutoInitialize() { return nil }
|
if !initialized {
|
||||||
|
await XuqmSDK.shared.awaitInitialization()
|
||||||
|
tryAutoInitialize()
|
||||||
|
}
|
||||||
|
if !initialized { return nil }
|
||||||
return store.deviceId
|
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 {
|
guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
|
||||||
throw URLError(.badServerResponse)
|
throw URLError(.badServerResponse)
|
||||||
}
|
}
|
||||||
struct Wrapper<U: Decodable>: Decodable { let data: U? }
|
guard let wrapper = try? JSONDecoder().decode(LicenseResponseWrapper<T>.self, from: data), let result = wrapper.data else {
|
||||||
guard let wrapper = try? JSONDecoder().decode(Wrapper<T>.self, from: data), let result = wrapper.data else {
|
|
||||||
throw URLError(.cannotDecodeContentData)
|
throw URLError(.cannotDecodeContentData)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct LicenseResponseWrapper<U: Decodable>: Decodable {
|
||||||
|
let data: U?
|
||||||
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ public final class UpdateSDK {
|
|||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
public func checkAppUpdate(currentVersionCode: Int) async throws -> AppUpdateInfo {
|
public func checkAppUpdate(currentVersionCode: Int) async throws -> AppUpdateInfo {
|
||||||
|
await XuqmSDK.shared.awaitInitialization()
|
||||||
let config = XuqmSDK.shared.requireConfig()
|
let config = XuqmSDK.shared.requireConfig()
|
||||||
let userId = XuqmSDK.shared.currentUserId
|
let userId = XuqmSDK.shared.currentUserId
|
||||||
var queryItems = [
|
var queryItems = [
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户