mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
+2
@@ -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
|
||||
}
|
||||
|
||||
+4
-1
@@ -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)
|
||||
}
|
||||
|
||||
+8
@@ -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)"
|
||||
|
||||
Reference in New Issue
Block a user