feat(push): 添加推送服务功能支持
- 新增推送相关的类型定义,包括消息类型、聊天类型、推送配置等接口 - 实现 HarmonyOS 推送 SDK,集成 HarmonyOS NEXT Push Kit 服务 - 实现 iOS 推送 SDK,支持 APNS 推送注册和消息接收 - 添加服务器端 APNS 推送提供商,支持 JWT 认证和推送消息发送 - 添加服务器端 HarmonyOS 推送提供商基础框架 - 集成推送配置加载和路由功能,支持多渠道推送分类管理
这个提交包含在:
父节点
6867091c04
当前提交
bc77850c3e
@ -30,6 +30,8 @@ public final class PushSDK: NSObject, UNUserNotificationCenterDelegate {
|
|||||||
public static let shared = PushSDK()
|
public static let shared = PushSDK()
|
||||||
public weak var delegate: PushMessageDelegate?
|
public weak var delegate: PushMessageDelegate?
|
||||||
private var cachedToken: String?
|
private var cachedToken: String?
|
||||||
|
private var cachedPushConfigAt: Date?
|
||||||
|
private var cachedPushConfig: PushRuntimeConfig?
|
||||||
private override init() {
|
private override init() {
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
@ -39,6 +41,7 @@ public final class PushSDK: NSObject, UNUserNotificationCenterDelegate {
|
|||||||
#if canImport(UIKit) && canImport(ObjectiveC)
|
#if canImport(UIKit) && canImport(ObjectiveC)
|
||||||
PushAppDelegateInterceptor.install()
|
PushAppDelegateInterceptor.install()
|
||||||
#endif
|
#endif
|
||||||
|
try? await applyTenantConfiguration()
|
||||||
_ = try await requestAuthorization(options: options)
|
_ = try await requestAuthorization(options: options)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +65,7 @@ public final class PushSDK: NSObject, UNUserNotificationCenterDelegate {
|
|||||||
|
|
||||||
public func registerToken(_ token: String, userId: String, vendor: PushVendor = .apns) async throws {
|
public func registerToken(_ token: String, userId: String, vendor: PushVendor = .apns) async throws {
|
||||||
let config = XuqmSDK.shared.requireConfig()
|
let config = XuqmSDK.shared.requireConfig()
|
||||||
|
try? await applyTenantConfiguration()
|
||||||
cachedToken = token
|
cachedToken = token
|
||||||
let _: EmptyResponse = try await ApiClient.shared.request(
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
||||||
path: "/api/push/register",
|
path: "/api/push/register",
|
||||||
@ -93,6 +97,13 @@ public final class PushSDK: NSObject, UNUserNotificationCenterDelegate {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func categoryIdentifier(for routeType: String) async -> String? {
|
||||||
|
if cachedPushConfig == nil {
|
||||||
|
try? await applyTenantConfiguration()
|
||||||
|
}
|
||||||
|
return cachedPushConfig?.routing[routeType]?.category
|
||||||
|
}
|
||||||
|
|
||||||
public func unregisterToken(userId: String, vendor: PushVendor = .apns) async throws {
|
public func unregisterToken(userId: String, vendor: PushVendor = .apns) async throws {
|
||||||
let config = XuqmSDK.shared.requireConfig()
|
let config = XuqmSDK.shared.requireConfig()
|
||||||
let _: EmptyResponse = try await ApiClient.shared.request(
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
||||||
@ -144,6 +155,79 @@ public final class PushSDK: NSObject, UNUserNotificationCenterDelegate {
|
|||||||
rawUserInfo: userInfo
|
rawUserInfo: userInfo
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func applyTenantConfiguration(force: Bool = false) async throws {
|
||||||
|
if !force,
|
||||||
|
let cachedPushConfigAt,
|
||||||
|
Date().timeIntervalSince(cachedPushConfigAt) < 300 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let config = XuqmSDK.shared.requireConfig()
|
||||||
|
let response: SdkRuntimeConfigResponse = try await ApiClient.shared.request(
|
||||||
|
path: "/api/sdk/config",
|
||||||
|
queryItems: [
|
||||||
|
URLQueryItem(name: "appId", value: config.appId),
|
||||||
|
URLQueryItem(name: "platform", value: "IOS"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
cachedPushConfig = response.pushConfig
|
||||||
|
cachedPushConfigAt = Date()
|
||||||
|
registerNotificationCategories(response.pushConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func registerNotificationCategories(_ config: PushRuntimeConfig?) {
|
||||||
|
let categoryIds = Set(config?.routing.values.compactMap {
|
||||||
|
$0.category.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : $0.category
|
||||||
|
} ?? [])
|
||||||
|
guard !categoryIds.isEmpty else { return }
|
||||||
|
let categories = categoryIds.map { categoryId in
|
||||||
|
UNNotificationCategory(
|
||||||
|
identifier: categoryId,
|
||||||
|
actions: [],
|
||||||
|
intentIdentifiers: [],
|
||||||
|
options: []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
UNUserNotificationCenter.current().setNotificationCategories(Set(categories))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct SdkRuntimeConfigResponse: Decodable {
|
||||||
|
let pushConfig: PushRuntimeConfig?
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct PushRuntimeConfig: Decodable {
|
||||||
|
let channels: [PushChannelConfig]
|
||||||
|
let routing: [String: PushRouteConfig]
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
channels = try container.decodeIfPresent([PushChannelConfig].self, forKey: .channels) ?? []
|
||||||
|
routing = try container.decodeIfPresent([String: PushRouteConfig].self, forKey: .routing) ?? [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case channels
|
||||||
|
case routing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct PushChannelConfig: Decodable {
|
||||||
|
let key: String
|
||||||
|
let channelId: String
|
||||||
|
let version: Int
|
||||||
|
let name: String
|
||||||
|
let description: String?
|
||||||
|
let importance: String
|
||||||
|
let sound: Bool
|
||||||
|
let vibration: Bool
|
||||||
|
let badge: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct PushRouteConfig: Decodable {
|
||||||
|
let channel: String
|
||||||
|
let category: String
|
||||||
|
let priority: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#if canImport(UIKit) && canImport(ObjectiveC)
|
#if canImport(UIKit) && canImport(ObjectiveC)
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户