From 8b1a4aaba07070d91652b1a3c33d1189315c0f44 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 19 Sep 2023 10:15:57 +1000 Subject: [PATCH] Added additional notification metadata and rate-limited PN subscriptions Fixed an issue where the PN subscription API call wasn't taking the frequency rate limit into account Added the timestamp and expiration timestamp to the notification metadata --- Session/Notifications/SyncPushTokensJob.swift | 31 ++++++++++++++++++- .../Models/NotificationMetadata.swift | 10 ++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index fac7107fa..7683ebbb7 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -85,7 +85,36 @@ public enum SyncPushTokensJob: JobExecutor { Logger.info("Re-registering for remote notifications.") PushRegistrationManager.shared.requestPushTokens() .flatMap { (pushToken: String, voipToken: String) -> AnyPublisher in - PushNotificationAPI + /// For our `subscribe` endpoint we only want to call it if: + /// • It's been longer than `SyncPushTokensJob.maxFrequency` since the last subscription; + /// • The token has changed; or + /// • We want to force an update + let timeSinceLastSubscription: TimeInterval = dependencies.dateNow + .timeIntervalSince( + dependencies.standardUserDefaults[.lastPushNotificationSync] + .defaulting(to: Date.distantPast) + ) + let uploadOnlyIfStale: Bool? = { + guard + let detailsData: Data = job.details, + let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData) + else { return nil } + + return details.uploadOnlyIfStale + }() + + guard + timeSinceLastSubscription >= SyncPushTokensJob.maxFrequency || + dependencies.storage[.lastRecordedPushToken] != pushToken || + uploadOnlyIfStale == false + else { + SNLog("[SyncPushTokensJob] OS subscription completed, skipping server subscription due to frequency") + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + return PushNotificationAPI .subscribe( token: Data(hex: pushToken), isForcedUpdate: true, diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift index fc9e93f9e..41fdf6b52 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift @@ -8,6 +8,8 @@ extension PushNotificationAPI { case accountId = "@" case hash = "#" case namespace = "n" + case createdTimestampMs = "t" + case expirationTimestampMs = "z" case dataLength = "l" case dataTooLong = "B" } @@ -21,6 +23,12 @@ extension PushNotificationAPI { /// The swarm namespace in which this message arrived. let namespace: Int + /// The swarm timestamp when the message was created (unix epoch milliseconds) + let createdTimestampMs: Int64 + + /// The message's swarm expiry timestamp (unix epoch milliseconds) + let expirationTimestampMs: Int64 + /// The length of the message data. This is always included, even if the message content /// itself was too large to fit into the push notification. let dataLength: Int @@ -40,6 +48,8 @@ extension PushNotificationAPI.NotificationMetadata { accountId: try container.decode(String.self, forKey: .accountId), hash: try container.decode(String.self, forKey: .hash), namespace: try container.decode(Int.self, forKey: .namespace), + createdTimestampMs: try container.decode(Int64.self, forKey: .createdTimestampMs), + expirationTimestampMs: try container.decode(Int64.self, forKey: .expirationTimestampMs), dataLength: try container.decode(Int.self, forKey: .dataLength), dataTooLong: ((try? container.decode(Int.self, forKey: .dataTooLong) != 0) ?? false) )