Skip to content

Commit

Permalink
NSE - Prevent playback on already read messages and enable on device …
Browse files Browse the repository at this point in the history
…silencing for unwanted push notifications
  • Loading branch information
stefanceriu committed Jun 10, 2022
1 parent 6dbe865 commit 0ffefe1
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 38 deletions.
117 changes: 79 additions & 38 deletions RiotNSE/NotificationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class NotificationService: UNNotificationServiceExtension {
// preprocess the payload, will attempt to fetch room display name
self.preprocessPayload(forEventId: eventId, roomId: roomId)
// fetch the event first
self.fetchEvent(withEventId: eventId, roomId: roomId)
self.fetchAndProcessEvent(withEventId: eventId, roomId: roomId)
}
}

Expand Down Expand Up @@ -232,21 +232,51 @@ class NotificationService: UNNotificationServiceExtension {
// At this stage we don't know the message type, so leave the body as set in didReceive.
}

private func fetchEvent(withEventId eventId: String, roomId: String, allowSync: Bool = true) {
MXLog.debug("[NotificationService] fetchEvent")

NotificationService.backgroundSyncService.event(withEventId: eventId,
inRoom: roomId,
completion: { (response) in
switch response {
case .success(let event):
MXLog.debug("[NotificationService] fetchEvent: Event fetched successfully")
self.processEvent(event)
case .failure(let error):
MXLog.debug("[NotificationService] fetchEvent: error: \(error)")
self.fallbackToBestAttemptContent(forEventId: eventId)
}
})
private func fetchAndProcessEvent(withEventId eventId: String, roomId: String) {
MXLog.debug("[NotificationService] fetchAndProcessEvent")

NotificationService.backgroundSyncService.event(withEventId: eventId, inRoom: roomId) { [weak self] (response) in
switch response {
case .success(let event):
MXLog.debug("[NotificationService] fetchAndProcessEvent: Event fetched successfully")
self?.checkPlaybackAndContinueProcessing(event, roomId: roomId)
case .failure(let error):
MXLog.error("[NotificationService] fetchAndProcessEvent: Failed fetching notification event with error: \(error)")
self?.fallbackToBestAttemptContent(forEventId: eventId)
}
}
}

private func checkPlaybackAndContinueProcessing(_ notificationEvent: MXEvent, roomId: String) {
NotificationService.backgroundSyncService.roomAccountData(forRoomId: roomId) { [weak self] response in
switch response {
case .success(let roomAccountData):
NotificationService.backgroundSyncService.event(withEventId: roomAccountData.readMarkerEventId, inRoom: roomId) { (response) in
switch response {
case .success(let readMarkerEvent):
MXLog.debug("[NotificationService] checkPlaybackAndContinueProcessing: Read marker event fetched successfully")

// As origin server timestamps are not always correct data in a federated environment, we add 10 minutes
// to the calculation to reduce the possibility that an event is marked as read which isn't.
let notificationTimestamp = notificationEvent.originServerTs + (10 * 60 * 1000)

if readMarkerEvent.originServerTs > notificationTimestamp {
MXLog.error("[NotificationService] checkPlaybackAndContinueProcessing: Event already read, discarding.")
self?.discardEvent(event: notificationEvent)
} else {
self?.processEvent(notificationEvent)
}

case .failure(let error):
MXLog.error("[NotificationService] checkPlaybackAndContinueProcessing: Failed fetching read marker event with error: \(error)")
self?.processEvent(notificationEvent)
}
}
case .failure(let error):
MXLog.error("[NotificationService] checkPlaybackAndContinueProcessing: Failed fetching room account data with error: \(error)")
self?.processEvent(notificationEvent)
}
}
}

private func processEvent(_ event: MXEvent) {
Expand All @@ -259,24 +289,24 @@ class NotificationService: UNNotificationServiceExtension {
return
}

self.notificationContent(forEvent: event, forAccount: userAccount) { (notificationContent, ignoreBadgeUpdate) in
var isUnwantedNotification = false
self.notificationContent(forEvent: event, forAccount: userAccount) { [weak self] (notificationContent, ignoreBadgeUpdate) in
guard let self = self else { return }

// Modify the notification content here...
if let newContent = notificationContent {
content.title = newContent.title
content.subtitle = newContent.subtitle
content.body = newContent.body
content.threadIdentifier = newContent.threadIdentifier
content.categoryIdentifier = newContent.categoryIdentifier
content.userInfo = newContent.userInfo
content.sound = newContent.sound
} else {
// this is an unwanted notification, mark as to be deleted when app is foregrounded again OR a new push came
guard let newContent = notificationContent else {
// We still want them removed if the NSE filtering entitlement is not available
content.categoryIdentifier = Constants.toBeRemovedNotificationCategoryIdentifier
isUnwantedNotification = true
self.discardEvent(event: event)
return
}

content.title = newContent.title
content.subtitle = newContent.subtitle
content.body = newContent.body
content.threadIdentifier = newContent.threadIdentifier
content.categoryIdentifier = newContent.categoryIdentifier
content.userInfo = newContent.userInfo
content.sound = newContent.sound

if ignoreBadgeUpdate {
content.badge = nil
}
Expand All @@ -289,18 +319,29 @@ class NotificationService: UNNotificationServiceExtension {
// When it completes, it'll continue with the bestAttemptContent.
return
} else {
MXLog.debug("[NotificationService] processEvent: Calling content handler for: \(String(describing: event.eventId)), isUnwanted: \(isUnwantedNotification)")
self.contentHandlers[event.eventId]?(content)
// clear maps
self.contentHandlers.removeValue(forKey: event.eventId)
self.bestAttemptContents.removeValue(forKey: event.eventId)

// We are done for this push
MXLog.debug("--------------------------------------------------------------------------------")
self.finishProcessingEvent(event, withContent: content)
}
}
}

private func discardEvent(event:MXEvent) {
MXLog.debug("[NotificationService] discardEvent: Discarding event: \(String(describing: event.eventId))")
finishProcessingEvent(event, withContent: UNNotificationContent())
}

private func finishProcessingEvent(_ event:MXEvent, withContent content: UNNotificationContent) {
MXLog.debug("[NotificationService] finishProcessingEvent: Calling content handler for: \(String(describing: event.eventId))")

self.contentHandlers[event.eventId]?(content)

// clear maps
self.contentHandlers.removeValue(forKey: event.eventId)
self.bestAttemptContents.removeValue(forKey: event.eventId)

// We are done for this push
MXLog.debug("--------------------------------------------------------------------------------")
}

private func fallbackToBestAttemptContent(forEventId eventId: String) {
MXLog.debug("[NotificationService] fallbackToBestAttemptContent: method called.")

Expand Down
2 changes: 2 additions & 0 deletions RiotNSE/RiotNSE.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<array>
<string>$(APPLICATION_GROUP_IDENTIFIER)</string>
</array>
<key>com.apple.developer.usernotifications.filtering</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(KEYCHAIN_ACCESS_GROUP)</string>
Expand Down

0 comments on commit 0ffefe1

Please sign in to comment.