XuqmGroup-iOSSDK/Sources/XuqmCoreSDK/XuqmSDK.swift
XuqmGroup e016adbb71 chore(ci): 更新 Jenkins 配置以支持多模块版本管理
- 为 Android、Flutter、iOS 和 RN SDK 的 Jenkinsfile 添加模块化版本控制
- 引入版本升级策略选择(major/minor/patch)和自定义版本号功能
- 实现多模块独立版本管理和选择性构建发布
- 更新 iOS SDK Package.swift 以支持独立模块化库
- 修改 iOS SDK podspec 文件以适应新的标签命名约定
- 优化 Jenkins 构建流程以支持按需选择特定模块进行构建和发布
- 修复 iOS 测试中的可选类型转换问题以提高代码健壮性
2026-05-23 01:20:57 +08:00

167 行
5.2 KiB
Swift

import Foundation
public struct LicenseFileData: Sendable {
public let appKey: String
public let appName: String?
public let companyName: String?
public let packageName: String?
public let iosBundleId: String?
public let harmonyBundleName: String?
public let baseUrl: String?
public let serverUrl: String?
public let issuedAt: String?
public let expiresAt: String?
public init(
appKey: String,
appName: String? = nil,
companyName: String? = nil,
packageName: String? = nil,
iosBundleId: String? = nil,
harmonyBundleName: String? = nil,
baseUrl: String? = nil,
serverUrl: String? = nil,
issuedAt: String? = nil,
expiresAt: String? = nil
) {
self.appKey = appKey
self.appName = appName
self.companyName = companyName
self.packageName = packageName
self.iosBundleId = iosBundleId
self.harmonyBundleName = harmonyBundleName
self.baseUrl = baseUrl
self.serverUrl = serverUrl
self.issuedAt = issuedAt
self.expiresAt = expiresAt
}
}
@MainActor
public final class XuqmSDK: NSObject {
public static let shared = XuqmSDK()
public private(set) var config: SDKConfig?
public private(set) var tokenStore: TokenStore?
public private(set) var currentUserId: String?
private var userSig: String?
public private(set) var cachedDeviceToken: String?
private var lastInitializedAppKey: String?
private var isInitialized = false
private var initContinuations: [CheckedContinuation<Void, Never>] = []
// Module hooks set by the convenience XuqmSDK module to wire feature modules
public var onInitialize: (@MainActor @Sendable (SDKConfig) -> Void)?
public var onLogin: (@MainActor @Sendable (String, String) async throws -> Void)?
public var onLogout: (@MainActor @Sendable (String) async throws -> Void)?
public var onDeviceToken: (@MainActor @Sendable (String, String) async throws -> Void)?
public var licenseReader: (@Sendable () throws -> LicenseFileData?)?
private override init() {
super.init()
}
/// Auto-initialize from the embedded license file using the registered licenseReader hook.
public func autoInitialize(debug: Bool = false) throws {
guard let reader = licenseReader else {
throw XuqmSDKError.noLicenseFile
}
guard let file = try reader() 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
let continuations = initContinuations
initContinuations.removeAll()
for cont in continuations {
cont.resume()
}
onInitialize?(config)
}
public func requireConfig() -> SDKConfig {
guard let config else {
fatalError("XuqmSDK not initialized. Call XuqmSDK.shared.initialize() first.")
}
return config
}
public var initialized: Bool { isInitialized }
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 onLogin?(userId, userSig)
} catch {
// Module login failed; silently ignored per facade pattern
}
if let cachedDeviceToken {
do {
try await onDeviceToken?(cachedDeviceToken, userId)
} catch {
// Device token registration failed
}
}
}
public func logout() async {
if let userId = currentUserId {
do {
try await onLogout?(userId)
} catch {
// Module logout 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 self.onDeviceToken?(token, userId)
}
}
}
}
public enum XuqmSDKError: Error {
case noLicenseFile
case packageMismatch(license: String, local: String)
}