From bc77850c3ea90c78f55e6c9a2778357c2111a426 Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Tue, 5 May 2026 22:26:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(push):=20=E6=B7=BB=E5=8A=A0=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E6=9C=8D=E5=8A=A1=E5=8A=9F=E8=83=BD=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增推送相关的类型定义,包括消息类型、聊天类型、推送配置等接口 - 实现 HarmonyOS 推送 SDK,集成 HarmonyOS NEXT Push Kit 服务 - 实现 iOS 推送 SDK,支持 APNS 推送注册和消息接收 - 添加服务器端 APNS 推送提供商,支持 JWT 认证和推送消息发送 - 添加服务器端 HarmonyOS 推送提供商基础框架 - 集成推送配置加载和路由功能,支持多渠道推送分类管理 --- Sources/XuqmSDK/Push/PushSDK.swift | 84 ++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/Sources/XuqmSDK/Push/PushSDK.swift b/Sources/XuqmSDK/Push/PushSDK.swift index 3a77620..f0dcea6 100644 --- a/Sources/XuqmSDK/Push/PushSDK.swift +++ b/Sources/XuqmSDK/Push/PushSDK.swift @@ -30,6 +30,8 @@ public final class PushSDK: NSObject, UNUserNotificationCenterDelegate { public static let shared = PushSDK() public weak var delegate: PushMessageDelegate? private var cachedToken: String? + private var cachedPushConfigAt: Date? + private var cachedPushConfig: PushRuntimeConfig? private override init() { super.init() } @@ -39,6 +41,7 @@ public final class PushSDK: NSObject, UNUserNotificationCenterDelegate { #if canImport(UIKit) && canImport(ObjectiveC) PushAppDelegateInterceptor.install() #endif + try? await applyTenantConfiguration() _ = 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 { let config = XuqmSDK.shared.requireConfig() + try? await applyTenantConfiguration() cachedToken = token let _: EmptyResponse = try await ApiClient.shared.request( path: "/api/push/register", @@ -93,6 +97,13 @@ public final class PushSDK: NSObject, UNUserNotificationCenterDelegate { #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 { let config = XuqmSDK.shared.requireConfig() let _: EmptyResponse = try await ApiClient.shared.request( @@ -144,6 +155,79 @@ public final class PushSDK: NSObject, UNUserNotificationCenterDelegate { 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)