Add boot detection in the NSE and use this to inform the user that there may be more notifications. (#5054)

* Add an initial implementation of the receivedWhileOfflineNotification.

* Only deliver a single receivedWhileOffline notification per boot and clarify the API.

* Add a 15-minute threshold for the receiveWhileOfflineNotification.
This commit is contained in:
Doug
2026-02-06 15:18:09 +00:00
committed by GitHub
parent bff328554a
commit 7532a792b7
11 changed files with 157 additions and 21 deletions
@@ -454,6 +454,7 @@
"notification_mentioned_you_body" = "Mentioned you: %1$@";
"notification_new_messages" = "New Messages";
"notification_reaction_body" = "Reacted with %1$@";
"notification_received_while_offline_ios" = "You received one or more notifications while offline.";
"notification_room_invite_body" = "Invited you to join the room";
"notification_room_invite_body_with_sender" = "%1$@ invited you to join the room";
"notification_sender_me" = "Me";
@@ -454,6 +454,7 @@
"notification_mentioned_you_body" = "Mentioned you: %1$@";
"notification_new_messages" = "New Messages";
"notification_reaction_body" = "Reacted with %1$@";
"notification_received_while_offline_ios" = "You received one or more notifications while offline.";
"notification_room_invite_body" = "Invited you to join the room";
"notification_room_invite_body_with_sender" = "%1$@ invited you to join the room";
"notification_sender_me" = "Me";
@@ -14,7 +14,9 @@ import Foundation
import SwiftUI
/// Common settings between app and NSE
protocol CommonSettingsProtocol {
protocol CommonSettingsProtocol: AnyObject {
var lastNotificationBootTime: TimeInterval? { get set }
var logLevel: LogLevel { get }
var traceLogPacks: Set<TraceLogPack> { get }
var bugReportRageshakeURL: RemotePreference<RageshakeConfiguration> { get }
@@ -51,6 +53,7 @@ final class AppSettings {
case enableNotifications
case enableInAppNotifications
case pusherProfileTag
case lastNotificationBootTime
case logLevel
case traceLogPacks
case viewSourceEnabled
@@ -291,6 +294,10 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.pusherProfileTag, storageType: .userDefaults(store))
var pusherProfileTag: String?
/// The device's last boot time as recorded by the NSE.
@UserPreference(key: UserDefaultsKeys.lastNotificationBootTime, storageType: .userDefaults(store))
var lastNotificationBootTime: TimeInterval?
// MARK: - Logging
@UserPreference(key: UserDefaultsKeys.logLevel, defaultValue: LogLevel.info, storageType: .userDefaults(store))
+2
View File
@@ -1060,6 +1060,8 @@ internal enum L10n {
internal static func notificationReactionBody(_ p1: Any) -> String {
return L10n.tr("Localizable", "notification_reaction_body", String(describing: p1))
}
/// You received one or more notifications while offline.
internal static var notificationReceivedWhileOfflineIos: String { return L10n.tr("Localizable", "notification_received_while_offline_ios") }
/// Mark as read
internal static var notificationRoomActionMarkAsRead: String { return L10n.tr("Localizable", "notification_room_action_mark_as_read") }
/// Quick reply
@@ -29,6 +29,10 @@ extension UNNotificationContent {
@objc var threadRootEventID: String? {
userInfo[NotificationConstants.UserInfoKey.threadRootEventIdentifier] as? String
}
var unreadCount: Int? {
userInfo[NotificationConstants.UserInfoKey.unreadCount] as? Int
}
}
extension UNMutableNotificationContent {
@@ -40,7 +44,7 @@ extension UNMutableNotificationContent {
userInfo[NotificationConstants.UserInfoKey.receiverIdentifier] = newValue
}
}
override var roomID: String? {
get {
userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String
@@ -67,8 +71,4 @@ extension UNMutableNotificationContent {
userInfo[NotificationConstants.UserInfoKey.threadRootEventIdentifier] = newValue
}
}
var unreadCount: Int? {
userInfo[NotificationConstants.UserInfoKey.unreadCount] as? Int
}
}
@@ -56,8 +56,14 @@ final class NotificationManager: NSObject, NotificationManagerProtocol {
self?.enableNotifications(newValue)
}
.store(in: &cancellables)
}
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
.sink { [weak self] _ in
self?.removeReceivedWhileOfflineNotification()
}
.store(in: &cancellables)
}
func requestAuthorization() {
guard appSettings.enableNotifications, !userSession.isNil else { return }
Task {
@@ -151,6 +157,10 @@ final class NotificationManager: NSObject, NotificationManagerProtocol {
notificationCenter.removeDeliveredNotifications(withIdentifiers: notificationsIdentifiers)
}
private func removeReceivedWhileOfflineNotification() {
notificationCenter.removeDeliveredNotifications(withIdentifiers: [NotificationServiceExtension.receivedWhileOfflineNotificationID])
}
private func setPusher(with deviceToken: Data, clientProxy: ClientProxyProtocol) async -> Bool {
do {
@@ -166,6 +166,14 @@
<string>7D9E.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>8FFB.1</string>
</array>
</dict>
</array>
</dict>
</plist>