diff --git a/CHANGES.rst b/CHANGES.rst index f165afb3e..600bac046 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,30 @@ +Changes in 1.1.3 (2020-12-16) +================================================= + +✨ Features + * + +🙌 Improvements + * AuthVC: Update SSO button wording. + +🐛 Bugfix + * Refresh account details on NSE runs (#3719). + +⚠️ API Changes + * + +🗣 Translations + * + +🧱 Build + * + +Others + * + +Improvements: + * Upgrade MatrixKit version ([v0.13.3](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.13.3)). + Changes in 1.1.2 (2020-12-02) ================================================= @@ -7,6 +34,7 @@ Changes in 1.1.2 (2020-12-02) 🙌 Improvements * Room History: Remove the report option for outgoing messages. * Empty views: Add empty screen when there is nothing to display on home, people, favourites and rooms screen (#3836). + * BuildSettings.messageDetailsAllowShare now hide /show action button in document preview (#3864). 🐛 Bugfix * Restore the modular widget events in the rooms histories. @@ -105,6 +133,7 @@ Changes in 1.0.18 (2020-10-27) * Update MatomoTracker to 7.2.2 (#3570). * Update SwiftGen to 6.3.0 (#3570). * Update SwiftLint to 0.40.3 (#3570). + * NSE: Utilize MXBackgroundService on pushes, to make messages available when the app is foregrounded (#3579). 🐛 Bugfix * Fix typos in UI @@ -133,7 +162,7 @@ Changes in 1.0.17 (2020-10-14) 🙌 Improvements * Device verification: Do not check for existing key backup after SSSS & Cross-Signing reset. * Cross-signing: Detect when cross-signing keys have been changed. - * Make copying & pasting media configurable. + * Make copying & pasting media configurable. 🐛 Bugfix * diff --git a/Podfile b/Podfile index d113b6171..57559860c 100644 --- a/Podfile +++ b/Podfile @@ -11,7 +11,7 @@ use_frameworks! # - `{ {kit spec hash} => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for each repo. Used by Fastfile during CI # # Warning: our internal tooling depends on the name of this variable name, so be sure not to change it -$matrixKitVersion = '= 0.13.2' +$matrixKitVersion = '= 0.13.3' # $matrixKitVersion = :local # $matrixKitVersion = {'develop' => 'develop'} @@ -35,7 +35,6 @@ end # Method to import the right MatrixKit flavour def import_MatrixKit pod 'MatrixSDK', $matrixSDKVersionSpec - pod 'MatrixSDK/SwiftSupport', $matrixSDKVersionSpec pod 'MatrixSDK/JingleCallStack', $matrixSDKVersionSpec pod 'MatrixKit', $matrixKitVersionSpec end @@ -43,7 +42,6 @@ end # Method to import the right MatrixKit/AppExtension flavour def import_MatrixKitAppExtension pod 'MatrixSDK', $matrixSDKVersionSpec - pod 'MatrixSDK/SwiftSupport', $matrixSDKVersionSpec pod 'MatrixKit/AppExtension', $matrixKitVersionSpec end diff --git a/Podfile.lock b/Podfile.lock index 497b76094..f3ca284a7 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -60,39 +60,37 @@ PODS: - MatomoTracker (7.2.2): - MatomoTracker/Core (= 7.2.2) - MatomoTracker/Core (7.2.2) - - MatrixKit (0.13.2): + - MatrixKit (0.13.3): - Down (~> 0.9.3) - DTCoreText (~> 1.6.23) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixKit/Core (= 0.13.2) - - MatrixSDK (= 0.17.4) - - MatrixKit/AppExtension (0.13.2): + - MatrixKit/Core (= 0.13.3) + - MatrixSDK (= 0.17.5) + - MatrixKit/AppExtension (0.13.3): - Down (~> 0.9.3) - DTCoreText (~> 1.6.23) - DTCoreText/Extension - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.17.4) - - MatrixKit/Core (0.13.2): + - MatrixSDK (= 0.17.5) + - MatrixKit/Core (0.13.3): - Down (~> 0.9.3) - DTCoreText (~> 1.6.23) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.17.4) - - MatrixSDK (0.17.4): - - MatrixSDK/Core (= 0.17.4) - - MatrixSDK/Core (0.17.4): + - MatrixSDK (= 0.17.5) + - MatrixSDK (0.17.5): + - MatrixSDK/Core (= 0.17.5) + - MatrixSDK/Core (0.17.5): - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) - OLMKit (~> 3.1.0) - Realm (= 10.1.4) - - MatrixSDK/JingleCallStack (0.17.4): + - MatrixSDK/JingleCallStack (0.17.5): - JitsiMeetSDK (= 2.11.0) - MatrixSDK/Core - - MatrixSDK/SwiftSupport (0.17.4): - - MatrixSDK/Core - OLMKit (3.1.0): - OLMKit/olmc (= 3.1.0) - OLMKit/olmcpp (= 3.1.0) @@ -129,11 +127,10 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.1) - KTCenterFlowLayout (~> 1.3.1) - MatomoTracker (~> 7.2.2) - - MatrixKit (= 0.13.2) - - MatrixKit/AppExtension (= 0.13.2) + - MatrixKit (= 0.13.3) + - MatrixKit/AppExtension (= 0.13.3) - MatrixSDK - MatrixSDK/JingleCallStack - - MatrixSDK/SwiftSupport - OLMKit - ReadMoreTextView (~> 3.0.1) - Reusable (~> 4.1) @@ -202,8 +199,8 @@ SPEC CHECKSUMS: LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b MatomoTracker: a59ec4da0f580be57bdc6baa708a71a86532a832 - MatrixKit: 411348d4690b414e18958a0437c13edc21054a18 - MatrixSDK: 39282219213aebf621326f6335073b5a2b1e9113 + MatrixKit: 86f5239fab112ab0d3714fd1f366916ebd4d2b25 + MatrixSDK: aadf483438804d9e93d5d46ad3e5e832b7bb8848 OLMKit: 4ee0159d63feeb86d836fdcfefe418e163511639 ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d Realm: 80f4fb2971ccb9adc27a47d0955ae8e533a7030b @@ -215,6 +212,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 5f40ae970fc6a939c548fedf78849358a895c315 +PODFILE CHECKSUM: 22fcc5047435925034265c61cd821507736bf492 COCOAPODS: 1.10.0 diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index d3cdcaf96..c454cec20 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -895,7 +895,6 @@ EC85D752247C0F52002C44C9 /* UNUserNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D750247C0E8F002C44C9 /* UNUserNotificationCenter.swift */; }; EC85D754247C0F5B002C44C9 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D753247C0F5B002C44C9 /* Constants.swift */; }; EC85D755247C0F84002C44C9 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D753247C0F5B002C44C9 /* Constants.swift */; }; - EC85D757247E700F002C44C9 /* NSEMemoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D756247E700F002C44C9 /* NSEMemoryStore.swift */; }; EC9A3EC524E1616900A8CFAE /* PushNotificationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC9A3EC424E1616900A8CFAE /* PushNotificationStore.swift */; }; EC9A3EC624E1632C00A8CFAE /* PushNotificationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC9A3EC424E1616900A8CFAE /* PushNotificationStore.swift */; }; EC9A3EC724E1634100A8CFAE /* KeyValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1CA87124C823E700DE9EBF /* KeyValueStore.swift */; }; @@ -2137,7 +2136,6 @@ EC85D74E2477E614002C44C9 /* RiotNSE.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RiotNSE.entitlements; sourceTree = ""; }; EC85D750247C0E8F002C44C9 /* UNUserNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNUserNotificationCenter.swift; sourceTree = ""; }; EC85D753247C0F5B002C44C9 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; - EC85D756247E700F002C44C9 /* NSEMemoryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEMemoryStore.swift; sourceTree = ""; }; EC9A3EC424E1616900A8CFAE /* PushNotificationStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotificationStore.swift; sourceTree = ""; }; ECAE7AE424EC0E01002FA813 /* TableViewSections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewSections.swift; sourceTree = ""; }; ECAE7AE624EC15F7002FA813 /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; @@ -5187,7 +5185,6 @@ children = ( EC85D74E2477E614002C44C9 /* RiotNSE.entitlements */, EC85D7452477E5F7002C44C9 /* NotificationService.swift */, - EC85D756247E700F002C44C9 /* NSEMemoryStore.swift */, EC85D7472477E5F7002C44C9 /* Info.plist */, EC1CA8D924D811B400DE9EBF /* NSE-Common.xcconfig */, EC1CA8BC24D1B4CF00DE9EBF /* NSE-Debug.xcconfig */, @@ -6213,7 +6210,6 @@ files = ( EC85D74F2477E8EB002C44C9 /* RiotSettings.swift in Sources */, 32FD756824D2AD5100BA7B37 /* BuildSettings.swift in Sources */, - EC85D757247E700F002C44C9 /* NSEMemoryStore.swift in Sources */, EC2B4EF224A1EF34005EB739 /* DataProtectionHelper.swift in Sources */, EC85D7462477E5F7002C44C9 /* NotificationService.swift in Sources */, 32FD755424D074C700BA7B37 /* CommonConfiguration.swift in Sources */, @@ -7351,7 +7347,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1.1.2; + CURRENT_PROJECT_VERSION = 1.1.3; DEFINES_MODULE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -7371,7 +7367,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.1.2; + MARKETING_VERSION = 1.1.3; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -7410,7 +7406,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 1.1.2; + CURRENT_PROJECT_VERSION = 1.1.3; DEFINES_MODULE = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -7423,7 +7419,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.1.2; + MARKETING_VERSION = 1.1.3; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index dc29d77fc..910125790 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -72,7 +72,7 @@ "auth_register" = "Register"; "auth_submit" = "Submit"; "auth_skip" = "Skip"; -"auth_login_single_sign_on" = "Sign in with single sign-on"; +"auth_login_single_sign_on" = "Sign In"; "auth_send_reset_email" = "Send Reset Email"; "auth_return_to_login" = "Return to login screen"; "auth_user_id_placeholder" = "Email or user name"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 46934e2a6..8bbb3550d 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -114,7 +114,7 @@ internal enum VectorL10n { internal static var authLogin: String { return VectorL10n.tr("Vector", "auth_login") } - /// Sign in with single sign-on + /// Sign In internal static var authLoginSingleSignOn: String { return VectorL10n.tr("Vector", "auth_login_single_sign_on") } diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 096482692..c6be618c7 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -617,8 +617,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni } _isAppForeground = YES; - - [self configurePinCodeScreenFor:application createIfRequired:NO]; } - (void)applicationDidBecomeActive:(UIApplication *)application @@ -1109,9 +1107,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni - (void)pushNotificationService:(PushNotificationService *)pushNotificationService shouldNavigateToRoomWithId:(NSString *)roomId { - [MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:AnalyticsNoficationsTimeToDisplayContent - category:AnalyticsNoficationsCategory]; - _lastNavigatedRoomIdFromPush = roomId; [self navigateToRoomById:roomId]; } diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 6d9e8e2bb..27b14118f 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -294,6 +294,9 @@ // Listen to the event sent state changes [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(eventDidChangeSentState:) name:kMXEventDidChangeSentStateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(eventDidChangeIdentifier:) name:kMXEventDidChangeIdentifierNotification object:nil]; + + // Show / hide actions button in document preview according BuildSettings + self.allowActionsInDocumentPreview = BuildSettings.messageDetailsAllowShare; } - (void)viewDidLoad diff --git a/RiotNSE/NSEMemoryStore.swift b/RiotNSE/NSEMemoryStore.swift deleted file mode 100644 index a45864db2..000000000 --- a/RiotNSE/NSEMemoryStore.swift +++ /dev/null @@ -1,133 +0,0 @@ -/* - Copyright 2020 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 MatrixSDK - -// error domain -let NSEMemoryStoreErrorDomain: String = "NSEMemoryStoreErrorDomain" - -// error codes -enum NSEMemoryStoreErrorCode: Int { - case userIDMissing = 1001 // User ID is missing in credentials -} - -/// Fake memory store implementation. Uses some real values from an MXFileStore instance. -class NSEMemoryStore: MXMemoryStore { - - // In-memory value for eventStreamToken. Will be used as eventStreamToken if provided. - private var lastStoredEventStreamToken: String? - private var credentials: MXCredentials - // real store - private var fileStore: MXFileStore! - private var myUser: MXUser? - - init(withCredentials credentials: MXCredentials) { - self.credentials = credentials - if fileStore == nil { - fileStore = MXFileStore(credentials: credentials) - // load real eventStreamToken - fileStore.loadMetaData() - } - } - - override func open(with credentials: MXCredentials, onComplete: (() -> Void)?, failure: ((Error?) -> Void)? = nil) { - super.open(with: credentials, onComplete: { - guard let userId = credentials.userId else { - failure?(NSError(domain: NSEMemoryStoreErrorDomain, - code: NSEMemoryStoreErrorCode.userIDMissing.rawValue, - userInfo: nil)) - return - } - // load session user before calling onComplete - self.fileStore.asyncUsers(withUserIds: [userId], success: { (users) in - if let user = users.first { - self.myUser = user - } - onComplete?() - }, failure: failure) - }, failure: failure) - } - - // Return real eventStreamToken, to be able to launch a meaningful background sync - override var eventStreamToken: String? { - get { - // if more up-to-date token exists, use it - if let token = lastStoredEventStreamToken { - return token - } - return fileStore.eventStreamToken - } set { - // store new token values in memory, and return these values in future reads - lastStoredEventStreamToken = newValue - } - } - - // Return real userAccountData, to be able to use push rules - override var userAccountData: [AnyHashable : Any]? { - get { - return fileStore.userAccountData - } set { - // no-op - } - } - - // This store should act like as a permanent one - override var isPermanent: Bool { - return true - } - - // Some mandatory methods to implement to be permanent - override func storeState(forRoom roomId: String, stateEvents: [MXEvent]) { - // no-op - } - - // Fetch real room state - override func state(ofRoom roomId: String, success: @escaping ([MXEvent]) -> Void, failure: ((Error) -> Void)? = nil) { - fileStore.state(ofRoom: roomId, success: success, failure: failure) - } - - // Fetch real soom summary - override func summary(ofRoom roomId: String) -> MXRoomSummary? { - return fileStore.summary(ofRoom: roomId) - } - - // Fetch real room account data - override func accountData(ofRoom roomId: String) -> MXRoomAccountData? { - return fileStore.accountData(ofRoom: roomId) - } - - // Override and return a user to be stored on session.myUser - override func user(withUserId userId: String) -> MXUser? { - if userId == credentials.userId, let myUser = myUser { - // if asking for session user and myUser is set, return that - return myUser - } - return MXUser(userId: userId) - } - - override var syncFilterId: String? { - get { - let filter = MXFilterJSONModel() - filter.room = MXRoomFilter() - filter.room.rooms = [] - return filter.jsonString() - } set { - // no-op - } - } - -} diff --git a/RiotNSE/NotificationService.swift b/RiotNSE/NotificationService.swift index ebc08f96f..e97eeab1b 100644 --- a/RiotNSE/NotificationService.swift +++ b/RiotNSE/NotificationService.swift @@ -21,21 +21,21 @@ import MatrixSDK class NotificationService: UNNotificationServiceExtension { /// Content handlers. Keys are eventId's - var contentHandlers: [String: ((UNNotificationContent) -> Void)] = [:] + private var contentHandlers: [String: ((UNNotificationContent) -> Void)] = [:] + + private var userAccount: MXKAccount? /// Best attempt contents. Will be updated incrementally, if something fails during the process, this best attempt content will be showed as notification. Keys are eventId's - var bestAttemptContents: [String: UNMutableNotificationContent] = [:] + private var bestAttemptContents: [String: UNMutableNotificationContent] = [:] - /// Cached events. Keys are eventId's - var cachedEvents: [String: MXEvent] = [:] - static var mxSession: MXSession? - var showDecryptedContentInNotifications: Bool { + private static var backgroundSyncService: MXBackgroundSyncService! + private var showDecryptedContentInNotifications: Bool { return RiotSettings.shared.showDecryptedContentInNotifications } - lazy var configuration: Configurable = { + private lazy var configuration: Configurable = { return CommonConfiguration() }() - static var isLoggerInitialized: Bool = false + private static var isLoggerInitialized: Bool = false private lazy var pushGatewayRestClient: MXPushGatewayRestClient = { let url = URL(string: BuildSettings.serverConfigSygnalAPIUrlString)! return MXPushGatewayRestClient(pushGateway: url.scheme! + "://" + url.host!, andOnUnrecognizedCertificateBlock: nil) @@ -110,25 +110,13 @@ class NotificationService: UNNotificationServiceExtension { } func setup(withRoomId roomId: String, eventId: String, completion: @escaping () -> Void) { - if let userAccount = MXKAccountManager.shared()?.activeAccounts.first { - if NotificationService.mxSession == nil { - let store = NSEMemoryStore(withCredentials: userAccount.mxCredentials) - NotificationService.mxSession = MXSession(matrixRestClient: MXRestClient(credentials: userAccount.mxCredentials, unrecognizedCertificateHandler: nil)) - NotificationService.mxSession?.setStore(store, completion: { (response) in - switch response { - case .success: - completion() - break - case .failure(let error): - NSLog("[NotificationService] setup: MXSession.setStore method returned error: \(String(describing: error))") - self.fallbackToBestAttemptContent(forEventId: eventId) - break - } - }) - } else { - NSLog("[NotificationService] Instance: Reusing session") - completion() + MXKAccountManager.shared()?.forceReloadAccounts() + self.userAccount = MXKAccountManager.shared()?.activeAccounts.first + if let userAccount = userAccount { + if NotificationService.backgroundSyncService == nil { + NotificationService.backgroundSyncService = MXBackgroundSyncService(withCredentials: userAccount.mxCredentials) } + completion() } else { NSLog("[NotificationService] setup: No active accounts") fallbackToBestAttemptContent(forEventId: eventId) @@ -144,10 +132,9 @@ class NotificationService: UNNotificationServiceExtension { NSLog("[NotificationService] preprocessPayload: Do not preprocess because app protection is set") return } - guard let session = NotificationService.mxSession else { return } - guard let roomDisplayName = session.store.summary?(ofRoom: roomId)?.displayname else { return } - let isDirect = session.directUserId(inRoom: roomId) != nil - if isDirect { + guard let roomSummary = NotificationService.backgroundSyncService.roomSummary(forRoomId: roomId) else { return } + guard let roomDisplayName = roomSummary.displayname else { return } + if roomSummary.isDirect == true { bestAttemptContents[eventId]?.body = NSString.localizedUserNotificationString(forKey: "MESSAGE_FROM_X", arguments: [roomDisplayName as Any]) } else { bestAttemptContents[eventId]?.body = NSString.localizedUserNotificationString(forKey: "MESSAGE_IN_X", arguments: [roomDisplayName as Any]) @@ -155,133 +142,29 @@ class NotificationService: UNNotificationServiceExtension { } func fetchEvent(withEventId eventId: String, roomId: String, allowSync: Bool = true) { - guard let mxSession = NotificationService.mxSession else { - // there is something wrong, do not change the content - NSLog("[NotificationService] fetchEvent: Either originalContent or mxSession is missing.") - fallbackToBestAttemptContent(forEventId: eventId) - return - } + NSLog("[NotificationService] fetchEvent") - /// Inline function to handle decryption failure - func handleDecryptionFailure() { - if allowSync { - NSLog("[NotificationService] fetchEvent: Launch a background sync.") - self.launchBackgroundSync(forEventId: eventId, roomId: roomId) - } else { - NSLog("[NotificationService] fetchEvent: Do not sync anymore.") - self.fallbackToBestAttemptContent(forEventId: eventId) - } - } - - /// Inline function to handle encryption for event, either from cache or from the backend - /// - Parameter event: The event to be handled - func handleEncryption(forEvent event: MXEvent) { - if !event.isEncrypted { - // not encrypted, go on processing - NSLog("[NotificationService] fetchEvent: Event not encrypted.") - self.processEvent(event) - return - } - - // encrypted - if event.clear != nil { - // already decrypted - NSLog("[NotificationService] fetchEvent: Event already decrypted.") - self.processEvent(event) - return - } - - // should decrypt it first - if mxSession.crypto.hasKeys(toDecryptEvent: event) { - // we have keys to decrypt the event - NSLog("[NotificationService] fetchEvent: Event needs to be decrpyted, and we have the keys to decrypt it.") - if mxSession.decryptEvent(event, inTimeline: nil) { - // decryption succeeded - NSLog("[NotificationService] fetchEvent: Event decrypted successfully.") - self.processEvent(event) - } else { - // decryption failed - NSLog("[NotificationService] fetchEvent: Decryption failed even crypto claimed it has the keys.") - handleDecryptionFailure() - } - } else { - // we don't have keys to decrypt the event - NSLog("[NotificationService] fetchEvent: Event needs to be decrpyted, but we don't have the keys to decrypt it.") - handleDecryptionFailure() - } - } - - // check if we've fetched the event before - if let cachedEvent = self.cachedEvents[eventId] { - // use cached event - handleEncryption(forEvent: cachedEvent) - } else { - // attempt to fetch the event - mxSession.event(withEventId: eventId, inRoom: roomId, success: { [weak self] (event) in - guard let self = self else { - NSLog("[NotificationService] fetchEvent: MXSession.event method returned too late successfully.") - return - } - - guard let event = event else { - NSLog("[NotificationService] fetchEvent: MXSession.event method returned successfully with no event.") - self.fallbackToBestAttemptContent(forEventId: eventId) - return - } - - // cache this event - self.cachedEvents[eventId] = event - - // handle encryption for this event - handleEncryption(forEvent: event) - }) { [weak self] (error) in - guard let self = self else { - NSLog("[NotificationService] fetchEvent: MXSession.event method returned too late with error: \(String(describing: error))") - return - } - NSLog("[NotificationService] fetchEvent: MXSession.event method returned error: \(String(describing: error))") - self.fallbackToBestAttemptContent(forEventId: eventId) - } - } + NotificationService.backgroundSyncService.event(withEventId: eventId, + inRoom: roomId, + completion: { (response) in + switch response { + case .success(let event): + NSLog("[NotificationService] fetchEvent: Event fetched successfully") + self.processEvent(event) + case .failure(let error): + NSLog("[NotificationService] fetchEvent: error: \(error)") + self.fallbackToBestAttemptContent(forEventId: eventId) + } + }) } - func launchBackgroundSync(forEventId eventId: String, roomId: String) { - guard let mxSession = NotificationService.mxSession else { - NSLog("[NotificationService] launchBackgroundSync: mxSession is missing.") - self.fallbackToBestAttemptContent(forEventId: eventId) - return - } - - // launch an initial background sync - mxSession.backgroundSync(withTimeout: 20, ignoreSessionState: true) { [weak self] (response) in - switch response { - case .success: - guard let self = self else { - NSLog("[NotificationService] launchBackgroundSync: MXSession.initialBackgroundSync returned too late successfully") - return - } - // do not allow to sync anymore - self.fetchEvent(withEventId: eventId, roomId: roomId, allowSync: false) - break - case .failure(let error): - guard let self = self else { - NSLog("[NotificationService] launchBackgroundSync: MXSession.initialBackgroundSync returned too late with error: \(String(describing: error))") - return - } - NSLog("[NotificationService] launchBackgroundSync: MXSession.initialBackgroundSync returned with error: \(String(describing: error))") - self.fallbackToBestAttemptContent(forEventId: eventId) - break - } - } - } - - func processEvent(_ event: MXEvent) { - guard let content = bestAttemptContents[event.eventId], let mxSession = NotificationService.mxSession else { + private func processEvent(_ event: MXEvent) { + guard let content = bestAttemptContents[event.eventId], let userAccount = userAccount else { self.fallbackToBestAttemptContent(forEventId: event.eventId) return } - self.notificationContent(forEvent: event, inSession: mxSession) { (notificationContent) in + self.notificationContent(forEvent: event, forAccount: userAccount) { (notificationContent) in var isUnwantedNotification = false // Modify the notification content here... @@ -301,10 +184,13 @@ class NotificationService: UNNotificationServiceExtension { NSLog("[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) } } - func fallbackToBestAttemptContent(forEventId eventId: String) { + private func fallbackToBestAttemptContent(forEventId eventId: String) { NSLog("[NotificationService] fallbackToBestAttemptContent: method called.") guard let content = bestAttemptContents[eventId] else { @@ -314,190 +200,163 @@ class NotificationService: UNNotificationServiceExtension { // call contentHandler contentHandlers[eventId]?(content) + // clear maps + contentHandlers.removeValue(forKey: eventId) + bestAttemptContents.removeValue(forKey: eventId) } - func notificationContent(forEvent event: MXEvent, inSession session: MXSession, onComplete: @escaping (UNNotificationContent?) -> Void) { + private func notificationContent(forEvent event: MXEvent, forAccount account: MXKAccount, onComplete: @escaping (UNNotificationContent?) -> Void) { guard let content = event.content, content.count > 0 else { NSLog("[NotificationService] notificationContentForEvent: empty event content") onComplete(nil) return } - guard let room = MXRoom.load(from: session.store, withRoomId: event.roomId, matrixSession: session) as? MXRoom else { - NSLog("[NotificationService] notificationContentForEvent: Unknown room") - onComplete(nil) - return - } + + let roomId = event.roomId! + let isRoomMentionsOnly = NotificationService.backgroundSyncService.isRoomMentionsOnly(roomId) + let roomSummary = NotificationService.backgroundSyncService.roomSummary(forRoomId: roomId) NSLog("[NotificationService] notificationContentForEvent: Attempt to fetch the room state") - room.state { (roomState) in - guard let roomState = roomState else { - NSLog("[NotificationService] notificationContentForEvent: Could not fetch the room state") - onComplete(nil) - return - } - - var notificationTitle: String? - var notificationBody: String? - - var threadIdentifier = room.roomId - let eventSenderName = roomState.members.memberName(event.sender) - let currentUserId = session.credentials.userId - - let pushRule = session.notificationCenter.rule(matching: event, roomState: roomState) - - switch event.eventType { - case .callInvite: - let offer = event.content["offer"] as? [AnyHashable: Any] - let sdp = offer?["sdp"] as? String - let isVideoCall = sdp?.contains("m=video") ?? false + + NotificationService.backgroundSyncService.roomState(forRoomId: roomId, completion: { (response) in + switch response { + case .success(let roomState): + var notificationTitle: String? + var notificationBody: String? - if isVideoCall { - notificationBody = NSString.localizedUserNotificationString(forKey: "VIDEO_CALL_FROM_USER", arguments: [eventSenderName as Any]) - } else { - notificationBody = NSString.localizedUserNotificationString(forKey: "VOICE_CALL_FROM_USER", arguments: [eventSenderName as Any]) - } + var threadIdentifier: String? = roomId + let eventSenderName = roomState.members.memberName(event.sender) + let currentUserId = account.mxCredentials.userId + let roomDisplayName = roomSummary?.displayname + let pushRule = NotificationService.backgroundSyncService.pushRule(matching: event, roomState: roomState) - // call notifications should stand out from normal messages, so we don't stack them - threadIdentifier = nil - self.sendVoipPush(forEvent: event) - case .roomMessage, .roomEncrypted: - if room.isMentionsOnly { - // A local notification will be displayed only for highlighted notification. - var isHighlighted = false + switch event.eventType { + case .callInvite: + let offer = event.content["offer"] as? [AnyHashable: Any] + let sdp = offer?["sdp"] as? String + let isVideoCall = sdp?.contains("m=video") ?? false - // Check whether is there an highlight tweak on it - for ruleAction in pushRule?.actions ?? [] { - guard let action = ruleAction as? MXPushRuleAction else { continue } - guard action.actionType == MXPushRuleActionTypeSetTweak else { continue } - guard action.parameters["set_tweak"] as? String == "highlight" else { continue } - // Check the highlight tweak "value" - // If not present, highlight. Else check its value before highlighting - if nil == action.parameters["value"] || true == (action.parameters["value"] as? Bool) { - isHighlighted = true - break + if isVideoCall { + notificationBody = NSString.localizedUserNotificationString(forKey: "VIDEO_CALL_FROM_USER", arguments: [eventSenderName as Any]) + } else { + notificationBody = NSString.localizedUserNotificationString(forKey: "VOICE_CALL_FROM_USER", arguments: [eventSenderName as Any]) + } + + // call notifications should stand out from normal messages, so we don't stack them + threadIdentifier = nil + self.sendVoipPush(forEvent: event) + case .roomMessage, .roomEncrypted: + if isRoomMentionsOnly { + // A local notification will be displayed only for highlighted notification. + var isHighlighted = false + + // Check whether is there an highlight tweak on it + for ruleAction in pushRule?.actions ?? [] { + guard let action = ruleAction as? MXPushRuleAction else { continue } + guard action.actionType == MXPushRuleActionTypeSetTweak else { continue } + guard action.parameters["set_tweak"] as? String == "highlight" else { continue } + // Check the highlight tweak "value" + // If not present, highlight. Else check its value before highlighting + if nil == action.parameters["value"] || true == (action.parameters["value"] as? Bool) { + isHighlighted = true + break + } + } + + if !isHighlighted { + // Ignore this notif. + NSLog("[NotificationService] notificationContentForEvent: Ignore non highlighted notif in mentions only room") + onComplete(nil) + return } } - if !isHighlighted { - // Ignore this notif. - NSLog("[NotificationService] notificationContentForEvent: Ignore non highlighted notif in mentions only room") - onComplete(nil) - return - } - } - - var msgType = event.content["msgtype"] as? String - let messageContent = event.content["body"] as? String - - if event.isEncrypted && !self.showDecryptedContentInNotifications { - // Hide the content - msgType = nil - } - - let roomDisplayName = session.store.summary?(ofRoom: room.roomId)?.displayname - let myUserId = session.myUser.userId - let isIncomingEvent = event.sender != myUserId - - // Display the room name only if it is different than the sender name - if roomDisplayName != nil && roomDisplayName != eventSenderName { - notificationTitle = NSString.localizedUserNotificationString(forKey: "MSG_FROM_USER_IN_ROOM_TITLE", arguments: [eventSenderName as Any, roomDisplayName as Any]) + var msgType = event.content["msgtype"] as? String + let messageContent = event.content["body"] as? String - if msgType == kMXMessageTypeText { - notificationBody = messageContent - } else if msgType == kMXMessageTypeEmote { - notificationBody = NSString.localizedUserNotificationString(forKey: "ACTION_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any]) - } else if msgType == kMXMessageTypeImage { - notificationBody = NSString.localizedUserNotificationString(forKey: "IMAGE_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any]) - } else if room.isDirect && isIncomingEvent && msgType == kMXMessageTypeKeyVerificationRequest { - session.crypto.keyVerificationManager.keyVerification(fromKeyVerificationEvent: event, - success:{ (keyVerification) in - guard let request = keyVerification.request, request.state == MXKeyVerificationRequestStatePending else { - onComplete(nil) - return - } - // TODO: Add accept and decline actions to notification - let body = NSString.localizedUserNotificationString(forKey: "KEY_VERIFICATION_REQUEST_FROM_USER", arguments: [eventSenderName as Any]) - - let notificationContent = self.notificationContent(withTitle: notificationTitle, - body: body, - threadIdentifier: threadIdentifier, - userId: currentUserId, - event: event, - pushRule: pushRule) - - onComplete(notificationContent) - }, failure:{ (error) in - NSLog("[NotificationService] notificationContentForEvent: failed to fetch key verification with error: \(error)") - onComplete(nil) - }) + if event.isEncrypted && !self.showDecryptedContentInNotifications { + // Hide the content + msgType = nil + } + + // Display the room name only if it is different than the sender name + if roomDisplayName != nil && roomDisplayName != eventSenderName { + notificationTitle = NSString.localizedUserNotificationString(forKey: "MSG_FROM_USER_IN_ROOM_TITLE", arguments: [eventSenderName as Any, roomDisplayName as Any]) + + if msgType == kMXMessageTypeText { + notificationBody = messageContent + } else if msgType == kMXMessageTypeEmote { + notificationBody = NSString.localizedUserNotificationString(forKey: "ACTION_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any]) + } else if msgType == kMXMessageTypeImage { + notificationBody = NSString.localizedUserNotificationString(forKey: "IMAGE_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any]) + } else { + // Encrypted messages falls here + notificationBody = NSString.localizedUserNotificationString(forKey: "MESSAGE", arguments: []) + } } else { - // Encrypted messages falls here - notificationBody = NSString.localizedUserNotificationString(forKey: "MESSAGE", arguments: []) + notificationTitle = eventSenderName + + switch msgType { + case kMXMessageTypeText: + notificationBody = messageContent + break + case kMXMessageTypeEmote: + notificationBody = NSString.localizedUserNotificationString(forKey: "ACTION_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any]) + break + case kMXMessageTypeImage: + notificationBody = NSString.localizedUserNotificationString(forKey: "IMAGE_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any]) + break + default: + // Encrypted messages falls here + notificationBody = NSString.localizedUserNotificationString(forKey: "MESSAGE", arguments: []) + break + } + } + break + case .roomMember: + if roomDisplayName != nil && roomDisplayName != eventSenderName { + notificationBody = NSString.localizedUserNotificationString(forKey: "USER_INVITE_TO_NAMED_ROOM", arguments: [eventSenderName as Any, roomDisplayName as Any]) + } else { + notificationBody = NSString.localizedUserNotificationString(forKey: "USER_INVITE_TO_CHAT", arguments: [eventSenderName as Any]) + } + case .sticker: + if roomDisplayName != nil && roomDisplayName != eventSenderName { + notificationTitle = NSString.localizedUserNotificationString(forKey: "MSG_FROM_USER_IN_ROOM_TITLE", arguments: [eventSenderName as Any, roomDisplayName as Any]) + } else { + notificationTitle = eventSenderName } - } else { - notificationTitle = eventSenderName - switch msgType { - case kMXMessageTypeText: - notificationBody = messageContent - break - case kMXMessageTypeEmote: - notificationBody = NSString.localizedUserNotificationString(forKey: "ACTION_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any]) - break - case kMXMessageTypeImage: - notificationBody = NSString.localizedUserNotificationString(forKey: "IMAGE_FROM_USER", arguments: [eventSenderName as Any, messageContent as Any]) - break - default: - // Encrypted messages falls here - notificationBody = NSString.localizedUserNotificationString(forKey: "MESSAGE", arguments: []) - break - } - } - break - case .roomMember: - let roomDisplayName = room.summary.displayname - - if roomDisplayName != nil && roomDisplayName != eventSenderName { - notificationBody = NSString.localizedUserNotificationString(forKey: "USER_INVITE_TO_NAMED_ROOM", arguments: [eventSenderName as Any, roomDisplayName as Any]) - } else { - notificationBody = NSString.localizedUserNotificationString(forKey: "USER_INVITE_TO_CHAT", arguments: [eventSenderName as Any]) - } - case .sticker: - let roomDisplayName = room.summary.displayname - - if roomDisplayName != nil && roomDisplayName != eventSenderName { - notificationTitle = NSString.localizedUserNotificationString(forKey: "MSG_FROM_USER_IN_ROOM_TITLE", arguments: [eventSenderName as Any, roomDisplayName as Any]) - } else { - notificationTitle = eventSenderName + notificationBody = NSString.localizedUserNotificationString(forKey: "STICKER_FROM_USER", arguments: [eventSenderName as Any]) + default: + break } - notificationBody = NSString.localizedUserNotificationString(forKey: "STICKER_FROM_USER", arguments: [eventSenderName as Any]) - default: - break - } - - if self.localAuthenticationService.isProtectionSet { - NSLog("[NotificationService] notificationContentForEvent: Resetting title and body because app protection is set") - notificationBody = NSString.localizedUserNotificationString(forKey: "MESSAGE_PROTECTED", arguments: []) - notificationTitle = nil - } - - guard notificationBody != nil else { - NSLog("[NotificationService] notificationContentForEvent: notificationBody is nil") + if self.localAuthenticationService.isProtectionSet { + NSLog("[NotificationService] notificationContentForEvent: Resetting title and body because app protection is set") + notificationBody = NSString.localizedUserNotificationString(forKey: "MESSAGE_PROTECTED", arguments: []) + notificationTitle = nil + } + + guard notificationBody != nil else { + NSLog("[NotificationService] notificationContentForEvent: notificationBody is nil") + onComplete(nil) + return + } + + let notificationContent = self.notificationContent(withTitle: notificationTitle, + body: notificationBody, + threadIdentifier: threadIdentifier, + userId: currentUserId, + event: event, + pushRule: pushRule) + + NSLog("[NotificationService] notificationContentForEvent: Calling onComplete.") + onComplete(notificationContent) + case .failure(let error): + NSLog("[NotificationService] notificationContentForEvent: error: \(error)") onComplete(nil) - return } - - let notificationContent = self.notificationContent(withTitle: notificationTitle, - body: notificationBody, - threadIdentifier: threadIdentifier, - userId: currentUserId, - event: event, - pushRule: pushRule) - - NSLog("[NotificationService] notificationContentForEvent: Calling onComplete.") - onComplete(notificationContent) - } + }) } func notificationContent(withTitle title: String?, @@ -597,40 +456,3 @@ class NotificationService: UNNotificationServiceExtension { } } - -extension MXRoom { - - func getRoomPushRule() -> MXPushRule? { - guard let rules = self.mxSession.notificationCenter.rules.global.room else { - return nil - } - - for rule in rules { - guard let pushRule = rule as? MXPushRule else { continue } - // the rule id is the room Id - // it is the server trick to avoid duplicated rule on the same room. - if pushRule.ruleId == self.roomId { - return pushRule - } - } - - return nil - } - - var isMentionsOnly: Bool { - // Check push rules at room level - guard let rule = self.getRoomPushRule() else { - return false - } - - for ruleAction in rule.actions { - guard let action = ruleAction as? MXPushRuleAction else { continue } - if action.actionType == MXPushRuleActionTypeDontNotify { - return rule.enabled - } - } - - return false - } - -}