Фикс: бэкграунд звонки — аудио, имя на CallKit, deactivation order, UUID race
This commit is contained in:
@@ -16,6 +16,12 @@ final class NotificationService: UNNotificationServiceExtension {
|
||||
private static let processedIdsKey = "nse_processed_message_ids"
|
||||
/// Max dedup entries kept in App Group — NSE has tight memory limits.
|
||||
private static let maxProcessedIds = 100
|
||||
/// Tracks dialogs recently read on another device (e.g. Desktop).
|
||||
/// When a READ push arrives, we store {dialogKey: timestamp}. Subsequent message
|
||||
/// pushes for the same dialog within the window are suppressed — the user is actively
|
||||
/// reading on Desktop, so the phone should stay silent.
|
||||
private static let recentlyReadKey = "nse_recently_read_dialogs"
|
||||
private static let recentlyReadWindow: TimeInterval = 30
|
||||
|
||||
/// Android parity: multiple key names for sender public key extraction.
|
||||
/// Server sends `dialog` field (was `from`). Both kept for backward compat.
|
||||
@@ -53,6 +59,17 @@ final class NotificationService: UNNotificationServiceExtension {
|
||||
dialogKey = String(dialogKey.dropFirst("#group:".count))
|
||||
}
|
||||
|
||||
// Track this dialog as "recently read on another device" (Desktop parity).
|
||||
// Next message push for this dialog within 30s will be suppressed.
|
||||
if !dialogKey.isEmpty, let shared {
|
||||
let now = Date().timeIntervalSince1970
|
||||
var recentlyRead = shared.dictionary(forKey: Self.recentlyReadKey) as? [String: Double] ?? [:]
|
||||
recentlyRead[dialogKey] = now
|
||||
// Evict stale entries (> 60s) to prevent unbounded growth.
|
||||
recentlyRead = recentlyRead.filter { now - $0.value < 60 }
|
||||
shared.set(recentlyRead, forKey: Self.recentlyReadKey)
|
||||
}
|
||||
|
||||
// Deliver silently — no sound, no alert.
|
||||
content.sound = nil
|
||||
content.title = ""
|
||||
@@ -136,6 +153,23 @@ final class NotificationService: UNNotificationServiceExtension {
|
||||
return
|
||||
}
|
||||
|
||||
// 3.1 Desktop-active suppression: if this dialog was read on another device
|
||||
// (Desktop) within the last 30s, suppress the notification. The user is
|
||||
// actively reading on Desktop — no need to buzz the phone.
|
||||
if !senderKey.isEmpty {
|
||||
let recentlyRead = shared.dictionary(forKey: Self.recentlyReadKey) as? [String: Double] ?? [:]
|
||||
if let lastReadTime = recentlyRead[senderKey] {
|
||||
let elapsed = Date().timeIntervalSince1970 - lastReadTime
|
||||
if elapsed < Self.recentlyReadWindow {
|
||||
content.sound = nil
|
||||
content.title = ""
|
||||
content.body = ""
|
||||
contentHandler(content)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3.5 Dedup: skip badge increment if we already processed this push.
|
||||
// Protects against duplicate FCM delivery (rare, but server dedup window is ~10s).
|
||||
let messageId = content.userInfo["message_id"] as? String
|
||||
@@ -174,7 +208,7 @@ final class NotificationService: UNNotificationServiceExtension {
|
||||
updatedInfo["sender_public_key"] = senderKey
|
||||
}
|
||||
|
||||
// 6. Resolve sender name from App Group cache (synced by DialogRepository).
|
||||
// 7. 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)
|
||||
@@ -186,12 +220,12 @@ final class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
content.userInfo = updatedInfo
|
||||
|
||||
// 7. Ensure notification category for CarPlay parity.
|
||||
// 8. Ensure notification category for CarPlay parity.
|
||||
if content.categoryIdentifier.isEmpty {
|
||||
content.categoryIdentifier = "message"
|
||||
}
|
||||
|
||||
// 8. Create Communication Notification via INSendMessageIntent.
|
||||
// 9. Create Communication Notification via INSendMessageIntent.
|
||||
let senderName = resolvedName
|
||||
?? Self.firstNonBlank(content.userInfo, keys: Self.senderNameKeyNames)
|
||||
?? content.title
|
||||
|
||||
Reference in New Issue
Block a user