chore(ci): 更新 Jenkins 配置以支持多模块版本管理

- 为 Android、Flutter、iOS 和 RN SDK 的 Jenkinsfile 添加模块化版本控制
- 引入版本升级策略选择(major/minor/patch)和自定义版本号功能
- 实现多模块独立版本管理和选择性构建发布
- 更新 iOS SDK Package.swift 以支持独立模块化库
- 修改 iOS SDK podspec 文件以适应新的标签命名约定
- 优化 Jenkins 构建流程以支持按需选择特定模块进行构建和发布
- 修复 iOS 测试中的可选类型转换问题以提高代码健壮性
这个提交包含在:
XuqmGroup 2026-05-23 01:20:57 +08:00
父节点 c9aff03a63
当前提交 e016adbb71
共有 29 个文件被更改,包括 546 次插入93 次删除

206
Jenkinsfile vendored
查看文件

@ -2,7 +2,26 @@ pipeline {
agent any
parameters {
string(name: 'VERSION', defaultValue: '', description: '发布版本号(留空自动递增)')
// ── 版本升级策略 ─────────────────────────────────────────────────────
choice(name: 'VERSION_BUMP', defaultValue: 'patch',
description: '版本升级策略: major(1.0.0→2.0.0), minor(1.0.0→1.1.0), patch(1.0.0→1.0.1)')
booleanParam(name: 'CUSTOM_VERSION', defaultValue: false,
description: '勾选后使用下方自定义版本号忽略VERSION_BUMP')
// ── 模块自定义版本号仅CUSTOM_VERSION=true时生效───────────────────
string(name: 'CORE_VERSION', defaultValue: '', description: 'XuqmCoreSDK 自定义版本号仅CUSTOM_VERSION=true时生效')
string(name: 'IM_VERSION', defaultValue: '', description: 'XuqmImSDK 自定义版本号')
string(name: 'PUSH_VERSION', defaultValue: '', description: 'XuqmPushSDK 自定义版本号')
string(name: 'UPDATE_VERSION', defaultValue: '', description: 'XuqmUpdateSDK 自定义版本号')
string(name: 'LICENSE_VERSION', defaultValue: '', description: 'XuqmLicenseSDK 自定义版本号')
string(name: 'FILE_VERSION', defaultValue: '', description: 'XuqmFileSDK 自定义版本号')
string(name: 'WEBVIEW_VERSION', defaultValue: '', description: 'XuqmWebViewSDK 自定义版本号')
// ── 模块选择 ────────────────────────────────────────────────────────
text(name: 'MODULES', defaultValue: 'core\nim\npush\nupdate\nlicense\nfile\nwebview',
description: '要发布的模块,每行一个。可选: core im push update license file webview')
// ── 构建选项 ────────────────────────────────────────────────────────
booleanParam(name: 'RUN_TESTS', defaultValue: true, description: '是否运行单元测试')
booleanParam(name: 'PUBLISH_SPM', defaultValue: true, description: '发布 SPM 版本(打 Git Tag')
booleanParam(name: 'PUBLISH_PODS', defaultValue: false, description: '发布到 CocoaPods 私有 Spec Repo')
@ -30,11 +49,91 @@ pipeline {
])
script {
env.GIT_COMMIT_SHORT = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
if (params.VERSION?.trim()) {
env.PUBLISH_VERSION = params.VERSION.trim()
sh "sed -i '' 's/s.version.*= .*/s.version = \"${env.PUBLISH_VERSION}\"/' XuqmSDK.podspec"
// ── 解析选中的模块 ──────────────────────────────────────
def allValid = ['core', 'im', 'push', 'update', 'license', 'file', 'webview']
def requested = params.MODULES
.tokenize('\n, ')
.collect { it.trim().toLowerCase() }
.findAll { it }
def invalid = requested - allValid
if (invalid) {
error "Unknown module(s): ${invalid}. Valid values: ${allValid}"
}
env.SELECTED_MODULES = requested.join(',')
echo "Selected modules: ${env.SELECTED_MODULES}"
// ── 模块 → podspec/tag 映射 ────────────────────────────
def moduleMap = [
'core': [podspec: 'XuqmCoreSDK', tagPrefix: 'core'],
'im': [podspec: 'XuqmImSDK', tagPrefix: 'im'],
'push': [podspec: 'XuqmPushSDK', tagPrefix: 'push'],
'update': [podspec: 'XuqmUpdateSDK', tagPrefix: 'update'],
'license': [podspec: 'XuqmLicenseSDK', tagPrefix: 'license'],
'file': [podspec: 'XuqmFileSDK', tagPrefix: 'file'],
'webview': [podspec: 'XuqmWebViewSDK', tagPrefix: 'webview'],
]
env.MODULE_MAP = groovy.json.JsonOutput.toJson(moduleMap)
}
}
}
stage('Resolve Versions') {
steps {
script {
def moduleMap = new groovy.json.JsonSlurper().parseText(env.MODULE_MAP)
def modules = env.SELECTED_MODULES.split(',')
def customVersions = [
'core': params.CORE_VERSION?.trim(),
'im': params.IM_VERSION?.trim(),
'push': params.PUSH_VERSION?.trim(),
'update': params.UPDATE_VERSION?.trim(),
'license': params.LICENSE_VERSION?.trim(),
'file': params.FILE_VERSION?.trim(),
'webview': params.WEBVIEW_VERSION?.trim(),
]
for (mod in modules) {
def podspecFile = "${moduleMap[mod].podspec}.podspec"
def currentVer = sh(
script: "grep \"s.version\" ${podspecFile} | head -1 | sed \"s/.*= *\\\"\\(.*\\)\\\".*/\\1/\"",
returnStdout: true
).trim()
def newVer = ''
if (params.CUSTOM_VERSION && customVersions[mod]) {
// 使用自定义版本号
newVer = customVersions[mod]
} else if (!params.CUSTOM_VERSION) {
// 根据 VERSION_BUMP 策略自动升级版本号
def parts = currentVer.tokenize('.')
while (parts.size() < 3) { parts.add('0') }
switch (params.VERSION_BUMP) {
case 'major':
parts[0] = (parts[0].toInteger() + 1).toString()
parts[1] = '0'
parts[2] = '0'
break
case 'minor':
parts[1] = (parts[1].toInteger() + 1).toString()
parts[2] = '0'
break
case 'patch':
default:
parts[2] = (parts[2].toInteger() + 1).toString()
break
}
newVer = parts.join('.')
}
if (newVer && newVer != currentVer) {
echo "Bumping ${moduleMap[mod].podspec}: ${currentVer} → ${newVer}"
sh "sed -i '' 's/s.version.*= .*/s.version = \"${newVer}\"/' ${podspecFile}"
env["VERSION_${mod}"] = newVer
} else {
env.PUBLISH_VERSION = sh(script: 'grep "s.version" XuqmSDK.podspec | head -1 | sed "s/.*= *\\"\\(.*\\)\\".*/\\1/"', returnStdout: true).trim()
echo "Keeping ${moduleMap[mod].podspec} at ${currentVer}"
env["VERSION_${mod}"] = currentVer
}
}
}
}
@ -43,10 +142,15 @@ pipeline {
stage('Build Info') {
steps {
script {
echo "🚀 构建分支: ${params.BRANCH}"
echo "🏷️ 发布版本: ${env.PUBLISH_VERSION}"
echo "🔨 Git Commit: ${env.GIT_COMMIT_SHORT}"
echo "⚠️ 注意:此任务应在 Jenkins2 (https://xuqinmin12.eicp.vip/) 上执行"
def modules = env.SELECTED_MODULES.split(',')
def moduleMap = new groovy.json.JsonSlurper().parseText(env.MODULE_MAP)
echo "Git Commit : ${env.GIT_COMMIT_SHORT}"
echo "Bump Strategy: ${params.CUSTOM_VERSION ? 'Custom' : params.VERSION_BUMP}"
echo "Modules (${modules.size()}):"
for (mod in modules) {
def ver = env["VERSION_${mod}"]
echo " ${mod.padRight(10)} → ${moduleMap[mod].podspec} v${ver}"
}
}
}
}
@ -72,37 +176,76 @@ pipeline {
stage('Pod Spec Lint') {
when { expression { return params.PUBLISH_PODS } }
steps {
sh 'pod spec lint XuqmSDK.podspec --allow-warnings'
script {
def moduleMap = new groovy.json.JsonSlurper().parseText(env.MODULE_MAP)
def modules = env.SELECTED_MODULES.split(',')
// 按依赖顺序 lint先 core,再其他模块
def ordered = ['core'] + modules.findAll { it != 'core' }
for (mod in ordered) {
def podspec = "${moduleMap[mod].podspec}.podspec"
if (fileExists(podspec)) {
echo "Linting ${podspec}..."
sh "pod spec lint ${podspec} --allow-warnings"
}
}
}
}
}
stage('Publish SPM (Git Tag)') {
when {
expression { return params.PUBLISH_SPM }
}
stage('Publish SPM (Git Tags)') {
when { expression { return params.PUBLISH_SPM } }
steps {
withCredentials([usernamePassword(credentialsId: 'gitlab-credentials', passwordVariable: 'GIT_PASS', usernameVariable: 'GIT_USER')]) {
script {
def moduleMap = new groovy.json.JsonSlurper().parseText(env.MODULE_MAP)
def modules = env.SELECTED_MODULES.split(',')
def tagsToPush = []
for (mod in modules) {
def ver = env["VERSION_${mod}"]
if (ver) {
def tag = "${moduleMap[mod].tagPrefix}/${ver}"
tagsToPush.push(tag)
}
}
if (tagsToPush) {
sh """
git config user.email "jenkins@xuqm.com"
git config user.name "Jenkins CI"
git add XuqmSDK.podspec || true
git diff --cached --quiet || git commit -m "ci: bump version to ${env.PUBLISH_VERSION}"
git tag -f "${env.PUBLISH_VERSION}"
git push https://${GIT_USER}:${GIT_PASS}@xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git HEAD:main --tags
git add *.podspec || true
git diff --cached --quiet || git commit -m "ci: bump versions [${env.SELECTED_MODULES}]"
"""
for (tag in tagsToPush) {
sh "git tag -f '${tag}'"
}
sh """
git push https://${GIT_USER}:${GIT_PASS}@xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git HEAD:main --tags -f
"""
echo "Published tags: ${tagsToPush.join(', ')}"
}
}
}
}
}
stage('Publish CocoaPods') {
when {
expression { return params.PUBLISH_PODS }
}
when { expression { return params.PUBLISH_PODS } }
steps {
sh """
pod repo add xuqm-specs ${SPEC_REPO} 2>/dev/null || true
pod repo push xuqm-specs XuqmSDK.podspec --allow-warnings
"""
script {
def moduleMap = new groovy.json.JsonSlurper().parseText(env.MODULE_MAP)
def modules = env.SELECTED_MODULES.split(',')
sh "pod repo add xuqm-specs ${SPEC_REPO} 2>/dev/null || true"
// 按依赖顺序发布:先 core,再其他模块
def ordered = ['core'] + modules.findAll { it != 'core' }
for (mod in ordered) {
def podspec = "${moduleMap[mod].podspec}.podspec"
if (fileExists(podspec)) {
echo "Publishing ${podspec}..."
sh "pod repo push xuqm-specs ${podspec} --allow-warnings"
}
}
}
}
}
}
@ -110,17 +253,14 @@ pipeline {
post {
success {
script {
echo "✅ iOS SDK v${env.PUBLISH_VERSION} 构建成功 (Commit: ${env.GIT_COMMIT_SHORT})"
if (params.PUBLISH_SPM) {
echo "📦 SPM 版本已发布: ${env.PUBLISH_VERSION}"
}
if (params.PUBLISH_PODS) {
echo "📦 CocoaPods 版本已发布到私有 Spec Repo"
}
def modules = env.SELECTED_MODULES?.split(',') ?: []
def moduleMap = new groovy.json.JsonSlurper().parseText(env.MODULE_MAP)
def summary = modules.collect { "${it}=${env["VERSION_${it}"]}" }.join(', ')
echo "iOS SDK published — ${summary} (Commit: ${env.GIT_COMMIT_SHORT})"
}
}
failure {
echo "❌ iOS SDK 构建失败,请检查日志"
echo "iOS SDK build failed — please check the logs"
}
}
}

查看文件

@ -5,21 +5,67 @@ let package = Package(
name: "XuqmSDK",
platforms: [.iOS(.v16), .macOS(.v13)],
products: [
.library(name: "XuqmSDK", targets: ["XuqmSDK"]),
// Individual modules use these for per-module consumption
.library(name: "XuqmCoreSDK", targets: ["XuqmCoreSDK"]),
.library(name: "XuqmImSDK", targets: ["XuqmImSDK"]),
.library(name: "XuqmPushSDK", targets: ["XuqmPushSDK"]),
.library(name: "XuqmUpdateSDK", targets: ["XuqmUpdateSDK"]),
.library(name: "XuqmLicenseSDK", targets: ["XuqmLicenseSDK"]),
.library(name: "XuqmFileSDK", targets: ["XuqmFileSDK"]),
.library(name: "XuqmWebViewSDK", targets: ["XuqmWebViewSDK"]),
// Convenience backward compatible, includes all modules
.library(name: "XuqmSDK", targets: ["XuqmSDK"]),
],
targets: [
.target(
name: "XuqmSDK",
path: "Sources/XuqmSDK",
name: "XuqmCoreSDK",
path: "Sources/XuqmCoreSDK",
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
.target(
name: "XuqmImSDK",
dependencies: ["XuqmCoreSDK", "XuqmFileSDK"],
path: "Sources/XuqmImSDK",
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
.target(
name: "XuqmPushSDK",
dependencies: ["XuqmCoreSDK"],
path: "Sources/XuqmPushSDK",
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
.target(
name: "XuqmUpdateSDK",
dependencies: ["XuqmCoreSDK"],
path: "Sources/XuqmUpdateSDK",
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
.target(
name: "XuqmLicenseSDK",
dependencies: ["XuqmCoreSDK"],
path: "Sources/XuqmLicenseSDK",
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
.target(
name: "XuqmFileSDK",
dependencies: ["XuqmCoreSDK"],
path: "Sources/XuqmFileSDK",
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
.target(
name: "XuqmWebViewSDK",
dependencies: ["XuqmSDK"],
path: "Sources/XuqmWebViewSDK",
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
.target(
name: "XuqmSDK",
dependencies: [
"XuqmCoreSDK", "XuqmImSDK", "XuqmPushSDK",
"XuqmUpdateSDK", "XuqmLicenseSDK", "XuqmFileSDK",
],
path: "Sources/XuqmSDK",
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
.testTarget(
name: "XuqmSDKTests",
dependencies: ["XuqmSDK"],

查看文件

@ -33,7 +33,7 @@ public extension SDKConfig {
extension SDKConfig {
}
enum SDKEndpoints {
static let apiBaseURL = URL(string: "https://dev.xuqinmin.com")!
static let imWebSocketURL = URL(string: "wss://dev.xuqinmin.com/ws/im")!
public enum SDKEndpoints {
public static let apiBaseURL = URL(string: "https://dev.xuqinmin.com")!
public static let imWebSocketURL = URL(string: "wss://dev.xuqinmin.com/ws/im")!
}

查看文件

@ -4,6 +4,8 @@ public final class TokenStore: @unchecked Sendable {
private let key = "com.xuqm.sdk.token"
public init() {}
public func save(_ token: String) {
UserDefaults.standard.set(token, forKey: key)
}

查看文件

@ -1,28 +1,74 @@
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()
private(set) var config: SDKConfig?
private(set) var tokenStore: TokenStore?
public private(set) var config: SDKConfig?
public private(set) var tokenStore: TokenStore?
public private(set) var currentUserId: String?
private var userSig: String?
private var cachedDeviceToken: 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 (Bundle.main/xuqm/license.xuqm).
/// Validates the bundle ID / package name against the license file.
/// Auto-initialize from the embedded license file using the registered licenseReader hook.
public func autoInitialize(debug: Bool = false) throws {
guard let file = LicenseFileReader.read() else {
guard let reader = licenseReader else {
throw XuqmSDKError.noLicenseFile
}
guard let file = try reader() else {
throw XuqmSDKError.noLicenseFile
}
let bundleId = Bundle.main.bundleIdentifier ?? ""
@ -42,17 +88,12 @@ public final class XuqmSDK: NSObject {
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()
}
}
onInitialize?(config)
}
public func requireConfig() -> SDKConfig {
@ -64,7 +105,6 @@ public final class XuqmSDK: NSObject {
public var initialized: Bool { isInitialized }
/// Wait for initialization to complete.
public func awaitInitialization() async {
guard !isInitialized else { return }
await withCheckedContinuation { cont in
@ -80,28 +120,26 @@ public final class XuqmSDK: NSObject {
self.userSig = userSig
do {
try await ImSDK.shared.login(userId, userSig)
try await onLogin?(userId, userSig)
} catch {
// IM login failed; silently ignored per facade pattern
// Module login failed; silently ignored per facade pattern
}
if let cachedDeviceToken {
do {
try await PushSDK.shared.registerToken(cachedDeviceToken, userId: userId)
try await onDeviceToken?(cachedDeviceToken, userId)
} catch {
// Push registration failed
// Device token registration failed
}
}
}
public func logout() async {
ImSDK.shared.disconnect()
if let userId = currentUserId {
do {
try await PushSDK.shared.unregisterToken(userId: userId)
try await onLogout?(userId)
} catch {
// Push unregistration failed
// Module logout failed
}
}
@ -116,7 +154,7 @@ public final class XuqmSDK: NSObject {
self.cachedDeviceToken = token
Task { @MainActor in
if let userId = self.currentUserId {
try? await PushSDK.shared.registerToken(token, userId: userId)
try? await self.onDeviceToken?(token, userId)
}
}
}

查看文件

@ -1,4 +1,5 @@
import Foundation
import XuqmCoreSDK
import UniformTypeIdentifiers
public struct FileUploadResult: Codable, Sendable {

查看文件

@ -1,4 +1,5 @@
import Foundation
import XuqmCoreSDK
public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked Sendable {

查看文件

@ -1,3 +1,5 @@
import XuqmCoreSDK
import XuqmFileSDK
import Foundation
public protocol ConversationDelegate: AnyObject {

查看文件

@ -1,4 +1,5 @@
import Foundation
import XuqmCoreSDK
public enum ImConnectionState: String, Sendable {
case connected
@ -53,6 +54,38 @@ public struct ImMessage: Codable, Sendable {
public let revoked: Bool?
public let createdAt: Int64
public let editedAt: Int64?
public init(
id: String,
appKey: String,
fromUserId: String,
fromId: String?,
toId: String,
chatType: ChatType,
msgType: MsgType,
content: String,
status: MsgStatus,
mentionedUserIds: String?,
groupReadCount: Int?,
revoked: Bool?,
createdAt: Int64,
editedAt: Int64?
) {
self.id = id
self.appKey = appKey
self.fromUserId = fromUserId
self.fromId = fromId
self.toId = toId
self.chatType = chatType
self.msgType = msgType
self.content = content
self.status = status
self.mentionedUserIds = mentionedUserIds
self.groupReadCount = groupReadCount
self.revoked = revoked
self.createdAt = createdAt
self.editedAt = editedAt
}
}
public struct PageResult<T: Decodable & Sendable>: Decodable, Sendable {

查看文件

@ -1,3 +1,4 @@
import XuqmCoreSDK
#if canImport(UIKit)
import UIKit
#endif

查看文件

@ -1,4 +1,5 @@
import Foundation
import XuqmCoreSDK
import CryptoKit
import CommonCrypto

查看文件

@ -1,8 +1,9 @@
import Foundation
import XuqmCoreSDK
enum LicenseFileReader {
public enum LicenseFileReader {
static func read() -> LicenseFile? {
public static func read() -> LicenseFile? {
guard let url = Bundle.main.url(forResource: "license", withExtension: "xuqm", subdirectory: "xuqm"),
let encrypted = try? String(contentsOf: url, encoding: .utf8) else {
return nil

查看文件

@ -1,16 +1,41 @@
import Foundation
import XuqmCoreSDK
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?
public struct LicenseFile: Codable, 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
}
}
public struct LicenseUserInfo: Sendable {

查看文件

@ -1,4 +1,5 @@
import Foundation
import XuqmCoreSDK
public final class LicenseSDK: @unchecked Sendable {

查看文件

@ -1,4 +1,5 @@
import Foundation
import XuqmCoreSDK
import Security
final class LicenseStore {

查看文件

@ -1,4 +1,5 @@
import Foundation
import XuqmCoreSDK
import UserNotifications
#if canImport(UIKit)
import UIKit

查看文件

@ -0,0 +1,75 @@
@_exported import XuqmCoreSDK
@_exported import XuqmImSDK
@_exported import XuqmPushSDK
@_exported import XuqmUpdateSDK
@_exported import XuqmLicenseSDK
@_exported import XuqmFileSDK
import Foundation
/// Auto-registers all feature module hooks with XuqmSDK.shared.
/// Import `XuqmSDK` to get the full SDK experience (backward compatible).
extension XuqmSDK {
/// Register all module hooks. Called automatically when any module is first used.
public func registerModuleHooks() {
// License reader hook
licenseReader = {
guard let file = LicenseFileReader.read() else { return nil }
return LicenseFileData(
appKey: file.appKey,
appName: file.appName,
companyName: file.companyName,
packageName: file.packageName,
iosBundleId: file.iosBundleId,
harmonyBundleName: file.harmonyBundleName,
baseUrl: file.baseUrl,
serverUrl: file.serverUrl,
issuedAt: file.issuedAt,
expiresAt: file.expiresAt
)
}
// Initialize hook auto-start push if configured
onInitialize = { @MainActor config in
if config.autoRegisterPush {
Task { @MainActor in
try? await PushSDK.shared.start()
}
}
}
// Login hook connect IM and register push token
onLogin = { @MainActor userId, userSig in
try await ImSDK.shared.login(userId, userSig)
if let token = XuqmSDK.shared.cachedDeviceToken {
try await PushSDK.shared.registerToken(token, userId: userId)
}
}
// Logout hook disconnect IM and unregister push
onLogout = { @MainActor userId in
ImSDK.shared.disconnect()
try await PushSDK.shared.unregisterToken(userId: userId)
}
// Device token hook register with push service
onDeviceToken = { @MainActor token, userId in
try await PushSDK.shared.registerToken(token, userId: userId)
}
}
}
/// Convenience initializer that auto-registers module hooks.
/// This provides backward compatibility for code that does `import XuqmSDK`.
@MainActor
public enum XuqmSDKSetup {
private static var hooksRegistered = false
/// Ensure module hooks are registered. Idempotent.
public static func ensureHooksRegistered() {
guard !hooksRegistered else { return }
hooksRegistered = true
XuqmSDK.shared.registerModuleHooks()
}
}

查看文件

@ -1,4 +1,5 @@
import Foundation
import XuqmCoreSDK
#if canImport(UIKit)
import UIKit
#endif

查看文件

@ -30,11 +30,11 @@ final class SmokeTests: XCTestCase {
msgType: .text,
content: "Hello",
status: .sent,
mentionedUserIds: nil,
groupReadCount: nil,
mentionedUserIds: nil as String?,
groupReadCount: nil as Int?,
revoked: false,
createdAt: 1714300000000,
editedAt: nil
editedAt: nil as Int64?
)
let data = try JSONEncoder().encode(msg)
let decoded = try JSONDecoder().decode(ImMessage.self, from: data)

12
XuqmCoreSDK.podspec 普通文件
查看文件

@ -0,0 +1,12 @@
Pod::Spec.new do |s|
s.name = 'XuqmCoreSDK'
s.version = '0.1.0'
s.summary = 'XuqmGroup iOS SDK — Core module'
s.homepage = 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK'
s.license = { :type => 'MIT' }
s.author = { 'XuqmGroup' => 'dev@xuqm.com' }
s.source = { :git => 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git', :tag => "core/#{s.version}" }
s.ios.deployment_target = '16.0'
s.swift_version = '5.9'
s.source_files = 'Sources/XuqmCoreSDK/**/*.swift'
end

13
XuqmFileSDK.podspec 普通文件
查看文件

@ -0,0 +1,13 @@
Pod::Spec.new do |s|
s.name = 'XuqmFileSDK'
s.version = '0.1.0'
s.summary = 'XuqmGroup iOS SDK — File module'
s.homepage = 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK'
s.license = { :type => 'MIT' }
s.author = { 'XuqmGroup' => 'dev@xuqm.com' }
s.source = { :git => 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git', :tag => "file/#{s.version}" }
s.ios.deployment_target = '16.0'
s.swift_version = '5.9'
s.source_files = 'Sources/XuqmFileSDK/**/*.swift'
s.dependency 'XuqmCoreSDK'
end

14
XuqmImSDK.podspec 普通文件
查看文件

@ -0,0 +1,14 @@
Pod::Spec.new do |s|
s.name = 'XuqmImSDK'
s.version = '0.1.0'
s.summary = 'XuqmGroup iOS SDK — IM module'
s.homepage = 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK'
s.license = { :type => 'MIT' }
s.author = { 'XuqmGroup' => 'dev@xuqm.com' }
s.source = { :git => 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git', :tag => "im/#{s.version}" }
s.ios.deployment_target = '16.0'
s.swift_version = '5.9'
s.source_files = 'Sources/XuqmImSDK/**/*.swift'
s.dependency 'XuqmCoreSDK'
s.dependency 'XuqmFileSDK'
end

13
XuqmLicenseSDK.podspec 普通文件
查看文件

@ -0,0 +1,13 @@
Pod::Spec.new do |s|
s.name = 'XuqmLicenseSDK'
s.version = '0.1.0'
s.summary = 'XuqmGroup iOS SDK — License module'
s.homepage = 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK'
s.license = { :type => 'MIT' }
s.author = { 'XuqmGroup' => 'dev@xuqm.com' }
s.source = { :git => 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git', :tag => "license/#{s.version}" }
s.ios.deployment_target = '16.0'
s.swift_version = '5.9'
s.source_files = 'Sources/XuqmLicenseSDK/**/*.swift'
s.dependency 'XuqmCoreSDK'
end

13
XuqmPushSDK.podspec 普通文件
查看文件

@ -0,0 +1,13 @@
Pod::Spec.new do |s|
s.name = 'XuqmPushSDK'
s.version = '0.1.0'
s.summary = 'XuqmGroup iOS SDK — Push module'
s.homepage = 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK'
s.license = { :type => 'MIT' }
s.author = { 'XuqmGroup' => 'dev@xuqm.com' }
s.source = { :git => 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git', :tag => "push/#{s.version}" }
s.ios.deployment_target = '16.0'
s.swift_version = '5.9'
s.source_files = 'Sources/XuqmPushSDK/**/*.swift'
s.dependency 'XuqmCoreSDK'
end

查看文件

@ -1,12 +1,18 @@
Pod::Spec.new do |s|
s.name = 'XuqmSDK'
s.version = '0.1.1'
s.summary = 'XuqmGroup iOS SDK — IM, Push Notifications, Version Management'
s.summary = 'XuqmGroup iOS SDK — convenience podspec (includes all modules)'
s.homepage = 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK'
s.license = { :type => 'MIT' }
s.author = { 'XuqmGroup' => 'dev@xuqm.com' }
s.source = { :git => 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git', :tag => s.version.to_s }
s.source = { :git => 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git', :tag => "sdk/#{s.version}" }
s.ios.deployment_target = '16.0'
s.swift_version = '5.9'
s.source_files = 'Sources/XuqmSDK/**/*.swift'
s.dependency 'XuqmCoreSDK'
s.dependency 'XuqmImSDK'
s.dependency 'XuqmPushSDK'
s.dependency 'XuqmUpdateSDK'
s.dependency 'XuqmLicenseSDK'
s.dependency 'XuqmFileSDK'
end

13
XuqmUpdateSDK.podspec 普通文件
查看文件

@ -0,0 +1,13 @@
Pod::Spec.new do |s|
s.name = 'XuqmUpdateSDK'
s.version = '0.1.0'
s.summary = 'XuqmGroup iOS SDK — Update module'
s.homepage = 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK'
s.license = { :type => 'MIT' }
s.author = { 'XuqmGroup' => 'dev@xuqm.com' }
s.source = { :git => 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git', :tag => "update/#{s.version}" }
s.ios.deployment_target = '16.0'
s.swift_version = '5.9'
s.source_files = 'Sources/XuqmUpdateSDK/**/*.swift'
s.dependency 'XuqmCoreSDK'
end

查看文件

@ -5,9 +5,8 @@ Pod::Spec.new do |s|
s.homepage = 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK'
s.license = { :type => 'MIT' }
s.author = { 'XuqmGroup' => 'dev@xuqm.com' }
s.source = { :git => 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git', :tag => s.version.to_s }
s.source = { :git => 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git', :tag => "webview/#{s.version}" }
s.ios.deployment_target = '16.0'
s.swift_version = '5.9'
s.source_files = 'Sources/XuqmWebViewSDK/**/*.swift'
s.dependency 'XuqmSDK'
end