Data-only пуши: обработка type/from/dialog, очистка по read, мут-проверка групп и имя отправителя
This commit is contained in:
@@ -14,8 +14,9 @@ final class NotificationService: UNNotificationServiceExtension {
|
||||
private static let badgeKey = "app_badge_count"
|
||||
|
||||
/// Android parity: multiple key names for sender public key extraction.
|
||||
/// Server currently sends `from` field in data-only push.
|
||||
private static let senderKeyNames = [
|
||||
"sender_public_key", "from_public_key", "fromPublicKey",
|
||||
"from", "sender_public_key", "from_public_key", "fromPublicKey",
|
||||
"public_key", "publicKey"
|
||||
]
|
||||
private static let senderNameKeyNames = [
|
||||
@@ -37,51 +38,101 @@ final class NotificationService: UNNotificationServiceExtension {
|
||||
return
|
||||
}
|
||||
|
||||
let shared = UserDefaults(suiteName: Self.appGroupID)
|
||||
let pushType = content.userInfo["type"] as? String ?? ""
|
||||
|
||||
// MARK: type=read — clear notifications for dialog, deliver silently.
|
||||
// Server sends read push when user reads a dialog on another device.
|
||||
if pushType == "read" {
|
||||
var dialogKey = content.userInfo["dialog"] as? String ?? ""
|
||||
if dialogKey.hasPrefix("#group:") {
|
||||
dialogKey = String(dialogKey.dropFirst("#group:".count))
|
||||
}
|
||||
|
||||
// Deliver silently — no sound, no alert.
|
||||
content.sound = nil
|
||||
content.title = ""
|
||||
content.body = ""
|
||||
|
||||
guard !dialogKey.isEmpty else {
|
||||
contentHandler(content)
|
||||
return
|
||||
}
|
||||
|
||||
// Clear notifications for the dialog BEFORE calling contentHandler.
|
||||
// NSE process can be suspended immediately after contentHandler returns,
|
||||
// so the async callback must complete first.
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.getDeliveredNotifications { delivered in
|
||||
let idsToRemove = delivered
|
||||
.filter { $0.request.content.userInfo["sender_public_key"] as? String == dialogKey }
|
||||
.map { $0.request.identifier }
|
||||
if !idsToRemove.isEmpty {
|
||||
center.removeDeliveredNotifications(withIdentifiers: idsToRemove)
|
||||
let current = shared?.integer(forKey: Self.badgeKey) ?? 0
|
||||
let newBadge = max(current - idsToRemove.count, 0)
|
||||
shared?.set(newBadge, forKey: Self.badgeKey)
|
||||
UNUserNotificationCenter.current().setBadgeCount(newBadge)
|
||||
}
|
||||
contentHandler(content)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MARK: Message types (personal_message / group_message)
|
||||
|
||||
// 1. Add sound for vibration — server APNs payload has no sound field.
|
||||
content.sound = .default
|
||||
|
||||
// 2. Increment badge count from shared App Group storage.
|
||||
if let shared = UserDefaults(suiteName: Self.appGroupID) {
|
||||
let current = shared.integer(forKey: Self.badgeKey)
|
||||
let newBadge = current + 1
|
||||
shared.set(newBadge, forKey: Self.badgeKey)
|
||||
content.badge = NSNumber(value: newBadge)
|
||||
// 2. Extract sender key — server sends `from` field.
|
||||
let senderKey = content.userInfo["from"] as? String
|
||||
?? Self.extractSenderKey(from: content.userInfo)
|
||||
|
||||
// 4. Filter muted chats.
|
||||
let senderKey = Self.extractSenderKey(from: content.userInfo)
|
||||
// 3. Filter muted chats BEFORE badge increment — muted chats must not inflate badge.
|
||||
if let shared {
|
||||
let mutedKeys = shared.stringArray(forKey: "muted_chats_keys") ?? []
|
||||
if !senderKey.isEmpty, mutedKeys.contains(senderKey) {
|
||||
// Muted: deliver silently (no sound, no alert)
|
||||
// Muted: deliver silently (no sound, no alert, no badge increment).
|
||||
content.sound = nil
|
||||
content.title = ""
|
||||
content.body = ""
|
||||
contentHandler(content)
|
||||
return
|
||||
}
|
||||
|
||||
// 4. Increment badge count — only for non-muted chats.
|
||||
let current = shared.integer(forKey: Self.badgeKey)
|
||||
let newBadge = current + 1
|
||||
shared.set(newBadge, forKey: Self.badgeKey)
|
||||
content.badge = NSNumber(value: newBadge)
|
||||
}
|
||||
|
||||
// 3. Normalize sender_public_key in userInfo for tap navigation.
|
||||
// Server may send under different key names — normalize to "sender_public_key".
|
||||
let senderKey = Self.extractSenderKey(from: content.userInfo)
|
||||
// 5. Normalize sender_public_key in userInfo for tap navigation.
|
||||
var updatedInfo = content.userInfo
|
||||
if !senderKey.isEmpty {
|
||||
var updatedInfo = content.userInfo
|
||||
updatedInfo["sender_public_key"] = senderKey
|
||||
// Also normalize sender_name
|
||||
if let name = Self.firstNonBlank(content.userInfo, keys: Self.senderNameKeyNames) {
|
||||
updatedInfo["sender_name"] = name
|
||||
}
|
||||
content.userInfo = updatedInfo
|
||||
}
|
||||
|
||||
// 5. Ensure notification category for CarPlay parity.
|
||||
// 6. Resolve sender name from App Group cache (synced by DialogRepository).
|
||||
let contactNames = shared?.dictionary(forKey: "contact_display_names") as? [String: String] ?? [:]
|
||||
let resolvedName = contactNames[senderKey]
|
||||
?? Self.firstNonBlank(content.userInfo, keys: Self.senderNameKeyNames)
|
||||
if let resolvedName, !resolvedName.isEmpty {
|
||||
updatedInfo["sender_name"] = resolvedName
|
||||
if content.title.isEmpty {
|
||||
content.title = resolvedName
|
||||
}
|
||||
}
|
||||
content.userInfo = updatedInfo
|
||||
|
||||
// 7. Ensure notification category for CarPlay parity.
|
||||
if content.categoryIdentifier.isEmpty {
|
||||
content.categoryIdentifier = "message"
|
||||
}
|
||||
|
||||
// 6. Create Communication Notification via INSendMessageIntent.
|
||||
// This makes the notification appear on CarPlay and work with Focus filters.
|
||||
// Apple requires INSendMessageIntent for messaging notifications on CarPlay (iOS 15+).
|
||||
let senderName = Self.firstNonBlank(content.userInfo, keys: Self.senderNameKeyNames)
|
||||
// 8. Create Communication Notification via INSendMessageIntent.
|
||||
let senderName = resolvedName
|
||||
?? Self.firstNonBlank(content.userInfo, keys: Self.senderNameKeyNames)
|
||||
?? content.title
|
||||
let finalContent = Self.makeCommunicationNotification(
|
||||
content: content,
|
||||
|
||||
Reference in New Issue
Block a user