Two sync loop experimental solution to decrypt push notifications (#1082)

* Experimental two sync loop solution

* better logging

* improved the code and handled invite notifications display better

* improved invite notifications

* new exerimental branch

* new sync loop

* code updated

* code improvements

* code improvements

* fix typo

* code improvements

* removed some unused code and added a respawn

* fixing some NSE issues

* code improvements

* new version of the branch

* more logging

* running the nse process ONLY IF necessary

* finally works! made also the feature flag

* also the encryption value of the room list api will depend on the flag now

* changelog

* code improvements

* code improvement

* updated proj

* fixing some compilation error after the rebase

* opt-in for the encryption sync

* fix
This commit is contained in:
Mauro
2023-06-22 19:23:33 +02:00
committed by GitHub
parent 652fd3cf76
commit afc4332716
18 changed files with 184 additions and 60 deletions
@@ -563,6 +563,16 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
selector: #selector(applicationDidBecomeActive),
name: UIApplication.didBecomeActiveNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(applicationWillTerminate),
name: UIApplication.willTerminateNotification,
object: nil)
}
@objc
private func applicationWillTerminate() {
userSession?.clientProxy.stopSync()
}
@objc
@@ -163,9 +163,9 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.pusherProfileTag, storageType: .userDefaults(store))
var pusherProfileTag: String?
/// A set of all the notification identifiers that have been served so far, it's reset every time the app is launched
@UserPreference(key: SharedUserDefaultsKeys.servedNotificationIdentifiers, initialValue: [], storageType: .userDefaults(store))
var servedNotificationIdentifiers: Set<String>
/// Tag describing if the app and the NSE should use the encryption sync
@UserPreference(key: SharedUserDefaultsKeys.isEncryptionSyncEnabled, defaultValue: false, storageType: .userDefaults(store))
var isEncryptionSyncEnabled
// MARK: - Other
@@ -33,10 +33,6 @@ extension UNNotificationContent {
userInfo[NotificationConstants.UserInfoKey.receiverIdentifier] as? String
}
@objc var notificationID: String? {
userInfo[NotificationConstants.UserInfoKey.notificationIdentifier] as? String
}
@objc var roomID: String? {
userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String
}
@@ -74,15 +70,6 @@ extension UNMutableNotificationContent {
}
}
override var notificationID: String? {
get {
userInfo[NotificationConstants.UserInfoKey.notificationIdentifier] as? String
}
set {
userInfo[NotificationConstants.UserInfoKey.notificationIdentifier] = newValue
}
}
func addMediaAttachment(using mediaProvider: MediaProviderProtocol?,
mediaSource: MediaSourceProxy) async -> UNMutableNotificationContent {
guard let mediaProvider else {
@@ -15,5 +15,5 @@
//
enum SharedUserDefaultsKeys: String {
case servedNotificationIdentifiers
case isEncryptionSyncEnabled
}
@@ -28,11 +28,13 @@ struct DeveloperOptionsScreenViewStateBindings {
var shouldCollapseRoomStateEvents: Bool
var userSuggestionsEnabled: Bool
var readReceiptsEnabled: Bool
var isEncryptionSyncEnabled: Bool
}
enum DeveloperOptionsScreenViewAction {
case changedShouldCollapseRoomStateEvents
case changedUserSuggestionsEnabled
case changedReadReceiptsEnabled
case changedIsEncryptionSyncEnabled
case clearCache
}
@@ -27,7 +27,8 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
appSettings = ServiceLocator.shared.settings
let bindings = DeveloperOptionsScreenViewStateBindings(shouldCollapseRoomStateEvents: appSettings.shouldCollapseRoomStateEvents,
userSuggestionsEnabled: appSettings.userSuggestionsEnabled,
readReceiptsEnabled: appSettings.readReceiptsEnabled)
readReceiptsEnabled: appSettings.readReceiptsEnabled,
isEncryptionSyncEnabled: appSettings.isEncryptionSyncEnabled)
let state = DeveloperOptionsScreenViewState(bindings: bindings)
super.init(initialViewState: state)
@@ -45,6 +46,8 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
appSettings.userSuggestionsEnabled = state.bindings.userSuggestionsEnabled
case .changedReadReceiptsEnabled:
appSettings.readReceiptsEnabled = state.bindings.readReceiptsEnabled
case .changedIsEncryptionSyncEnabled:
appSettings.isEncryptionSyncEnabled = state.bindings.isEncryptionSyncEnabled
case .clearCache:
callback?(.clearCache)
}
@@ -44,6 +44,14 @@ struct DeveloperOptionsScreen: View {
.onChange(of: context.readReceiptsEnabled) { _ in
context.send(viewAction: .changedReadReceiptsEnabled)
}
Toggle(isOn: $context.isEncryptionSyncEnabled) {
Text("Use notification encryption sync")
Text("requires app reboot")
}
.onChange(of: context.isEncryptionSyncEnabled) { _ in
context.send(viewAction: .changedIsEncryptionSyncEnabled)
}
}
Section {
@@ -28,7 +28,10 @@ class ClientProxy: ClientProxyProtocol {
private var roomListService: RoomListService?
private var roomListStateUpdateTaskHandle: TaskHandle?
private var encryptionSyncService: EncryptionSync?
private var isEncryptionSyncing = false
var roomSummaryProvider: RoomSummaryProviderProtocol?
var inviteSummaryProvider: RoomSummaryProviderProtocol?
@@ -103,7 +106,7 @@ class ClientProxy: ClientProxyProtocol {
}
var isSyncing: Bool {
roomListService?.isSyncing() ?? false
roomListService?.isSyncing() ?? false && isEncryptionSyncing
}
func startSync() {
@@ -111,18 +114,30 @@ class ClientProxy: ClientProxyProtocol {
guard !isSyncing else {
return
}
startEncryptionSyncService()
roomListService?.sync()
}
func stopSync() {
MXLog.info("Stopping sync")
stopEncryptionSyncService()
do {
try roomListService?.stopSync()
} catch {
MXLog.error("Failed stopping room list service with error: \(error)")
}
}
private func stopEncryptionSyncService() {
guard isEncryptionSyncing else {
return
}
isEncryptionSyncing = false
encryptionSyncService?.stop()
MXLog.info("Stopping Encryption Sync service")
}
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> {
await Task.dispatch(on: clientQueue) {
@@ -359,14 +374,47 @@ class ClientProxy: ClientProxyProtocol {
self.avatarURLSubject.value = urlString.flatMap(URL.init)
}
}
private func startEncryptionSyncService() {
guard ServiceLocator.shared.settings.isEncryptionSyncEnabled else {
return
}
configureEncryptionSyncService()
}
private func configureEncryptionSyncService() {
do {
let listener = EncryptionSyncListenerProxy { [weak self] reason in
switch reason {
case .done:
MXLog.info("Encryption Sync has finished for user: \(self?.userID ?? "unknown")")
case .error(let msg):
MXLog.error("Encryption Sync has terminated for user: \(self?.userID ?? "unknown") for reason: \(msg)")
guard let self else {
return
}
Task {
self.configureEncryptionSyncService()
}
}
}
let encryptionSync = try client.mainEncryptionSync(id: "Main App", listener: listener)
encryptionSync.reloadCaches()
isEncryptionSyncing = true
encryptionSyncService = encryptionSync
MXLog.info("Encryption sync started for user: \(userID)")
} catch {
MXLog.error("Configure encryption sync failed with error: \(error)")
}
}
private func configureRoomListService() async {
guard roomListService == nil else {
fatalError("This shouldn't be called more than once")
}
do {
let roomListService = try client.roomListServiceWithEncryption()
let roomListService = try ServiceLocator.shared.settings.isEncryptionSyncEnabled ? client.roomListService() : client.roomListServiceWithEncryption()
roomListStateUpdateTaskHandle = roomListService.state(listener: RoomListStateListenerProxy { [weak self] state in
guard let self else { return }
MXLog.info("Received room list update: \(state)")
@@ -0,0 +1,31 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import MatrixRustSDK
final class EncryptionSyncListenerProxy: EncryptionSyncListener {
private let didTerminateClosure: (EncryptionSyncTerminationReason) -> Void
init(_ didTerminateClosure: @escaping (EncryptionSyncTerminationReason) -> Void) {
self.didTerminateClosure = didTerminateClosure
}
func didTerminate(reason: EncryptionSyncTerminationReason) {
didTerminateClosure(reason)
}
}
@@ -48,17 +48,6 @@ protocol NotificationItemProxyProtocol {
var isEncrypted: Bool { get }
}
extension NotificationItemProxyProtocol {
var id: String? {
let identifiers = receiverID + roomID + event.eventID
guard let data = identifiers.data(using: .utf8) else {
return nil
}
let digest = SHA256.hash(data: data)
return digest.compactMap { String(format: "%02x", $0) }.joined()
}
}
struct NotificationItemProxy: NotificationItemProxyProtocol {
let notificationItem: NotificationItem
let receiverID: String
@@ -168,7 +157,6 @@ extension NotificationItemProxyProtocol {
notification.receiverID = receiverID
notification.roomID = roomID
notification.eventID = event.eventID
notification.notificationID = id
notification.sound = isNoisy ? UNNotificationSound(named: UNNotificationSoundName(rawValue: "message.caf")) : nil
// So that the UI groups notification that are received for the same room but also for the same user
notification.threadIdentifier = "\(receiverID)\(roomID)"