chore(ci): 更新 Jenkins 配置以支持多模块版本管理
- 为 Android、Flutter、iOS 和 RN SDK 的 Jenkinsfile 添加模块化版本控制 - 引入版本升级策略选择(major/minor/patch)和自定义版本号功能 - 实现多模块独立版本管理和选择性构建发布 - 更新 iOS SDK Package.swift 以支持独立模块化库 - 修改 iOS SDK podspec 文件以适应新的标签命名约定 - 优化 Jenkins 构建流程以支持按需选择特定模块进行构建和发布 - 修复 iOS 测试中的可选类型转换问题以提高代码健壮性
这个提交包含在:
父节点
c9aff03a63
当前提交
e016adbb71
228
Jenkinsfile
vendored
228
Jenkinsfile
vendored
@ -2,15 +2,34 @@ 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')
|
||||
}
|
||||
|
||||
environment {
|
||||
GIT_URL = 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git'
|
||||
SPEC_REPO = 'https://xuqinmin.com/xuqinmin12/xuqm-specs.git'
|
||||
GIT_URL = 'https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK.git'
|
||||
SPEC_REPO = 'https://xuqinmin.com/xuqinmin12/xuqm-specs.git'
|
||||
}
|
||||
|
||||
options {
|
||||
@ -23,18 +42,98 @@ pipeline {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: 'main']],
|
||||
extensions: [[$class: 'CleanBeforeCheckout']],
|
||||
userRemoteConfigs: scm.userRemoteConfigs
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: 'main']],
|
||||
extensions: [[$class: 'CleanBeforeCheckout']],
|
||||
userRemoteConfigs: scm.userRemoteConfigs
|
||||
])
|
||||
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"
|
||||
} else {
|
||||
env.PUBLISH_VERSION = sh(script: 'grep "s.version" XuqmSDK.podspec | head -1 | sed "s/.*= *\\"\\(.*\\)\\".*/\\1/"', returnStdout: true).trim()
|
||||
|
||||
// ── 解析选中的模块 ──────────────────────────────────────
|
||||
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 {
|
||||
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')]) {
|
||||
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
|
||||
"""
|
||||
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 *.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
普通文件
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
普通文件
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
普通文件
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
普通文件
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
普通文件
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
普通文件
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
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户