feat(update): 添加 API Key 管理和 WebSocket 实时通知功能
- 新增 API Key 管理功能,支持外部工具认证调用平台 API - 实现 WebSocket 实时通知,版本发布时推送轻量通知给客户端 - 添加 APK 文件哈希校验,支持已下载检测和直接安装 - 支持外部 APK 上传使用 API Key 认证 - 优化私有化部署自动注入 nginx WebSocket 代理配置 - 扩展 SDK 功能包括已下载检测、直接安装和实时通知监听
这个提交包含在:
父节点
5b34c60838
当前提交
bfebd8a99a
@ -30,10 +30,32 @@ public extension SDKConfig {
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKConfig {
|
||||
}
|
||||
|
||||
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")!
|
||||
private static var _apiBaseURL: URL?
|
||||
private static var _imWebSocketURL: URL?
|
||||
|
||||
public static var apiBaseURL: URL {
|
||||
_apiBaseURL ?? URL(string: "https://dev.xuqinmin.com")!
|
||||
}
|
||||
|
||||
public static var imWebSocketURL: URL {
|
||||
_imWebSocketURL ?? URL(string: "wss://dev.xuqinmin.com/ws/im")!
|
||||
}
|
||||
|
||||
public static func configure(
|
||||
apiBaseURL: String? = nil,
|
||||
imWebSocketURL: String? = nil
|
||||
) {
|
||||
if let api = apiBaseURL, let url = URL(string: api) {
|
||||
_apiBaseURL = url
|
||||
}
|
||||
if let ws = imWebSocketURL, let url = URL(string: ws) {
|
||||
_imWebSocketURL = url
|
||||
}
|
||||
}
|
||||
|
||||
public static func reset() {
|
||||
_apiBaseURL = nil
|
||||
_imWebSocketURL = nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,56 @@
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
public final class TokenStore: @unchecked Sendable {
|
||||
|
||||
private let key = "com.xuqm.sdk.token"
|
||||
private let service = "com.xuqm.sdk.token"
|
||||
private let account = "auth_token"
|
||||
|
||||
public init() {}
|
||||
|
||||
public func save(_ token: String) {
|
||||
UserDefaults.standard.set(token, forKey: key)
|
||||
delete()
|
||||
|
||||
guard let data = token.data(using: .utf8) else { return }
|
||||
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: service,
|
||||
kSecAttrAccount as String: account,
|
||||
kSecValueData as String: data,
|
||||
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
|
||||
]
|
||||
|
||||
SecItemAdd(query as CFDictionary, nil)
|
||||
}
|
||||
|
||||
public func get() -> String? {
|
||||
UserDefaults.standard.string(forKey: key)
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: service,
|
||||
kSecAttrAccount as String: account,
|
||||
kSecReturnData as String: true,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne,
|
||||
]
|
||||
|
||||
var item: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
||||
guard status == errSecSuccess, let data = item as? Data else {
|
||||
return nil
|
||||
}
|
||||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
|
||||
public func clear() {
|
||||
UserDefaults.standard.removeObject(forKey: key)
|
||||
delete()
|
||||
}
|
||||
|
||||
private func delete() {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: service,
|
||||
kSecAttrAccount as String: account,
|
||||
]
|
||||
SecItemDelete(query as CFDictionary)
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,6 +75,12 @@ public final class XuqmSDK: NSObject {
|
||||
if let configBundle = file.iosBundleId, !configBundle.isEmpty, configBundle != bundleId {
|
||||
throw XuqmSDKError.packageMismatch(license: configBundle, local: bundleId)
|
||||
}
|
||||
if let baseUrl = file.baseUrl {
|
||||
SDKEndpoints.configure(apiBaseURL: baseUrl)
|
||||
}
|
||||
if let serverUrl = file.serverUrl {
|
||||
SDKEndpoints.configure(imWebSocketURL: serverUrl + "/ws/im")
|
||||
}
|
||||
let cfg = SDKConfig(appKey: file.appKey, debug: debug)
|
||||
initialize(config: cfg)
|
||||
}
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户