diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index b83e21a52..124426100 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -5645,7 +5645,7 @@ repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = 1.1.13; + version = 1.1.14; }; }; 821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e47ee4ad0..f42d6e7b0 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -129,8 +129,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-rust-components-swift", "state" : { - "revision" : "482c8c04019e6e6ac9638ae792adae9d67b08114", - "version" : "1.1.13" + "revision" : "db9a4c6eddc385c62695afdf7562827c7a586e72", + "version" : "1.1.14" } }, { @@ -217,7 +217,7 @@ { "identity" : "swiftui-introspect", "kind" : "remoteSourceControl", - "location" : "https://github.com/siteline/SwiftUI-Introspect.git", + "location" : "https://github.com/siteline/SwiftUI-Introspect", "state" : { "revision" : "b94da693e57eaf79d16464b8b7c90d09cba4e290", "version" : "0.9.2" diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 9eb0aca63..c2bd9a507 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -527,8 +527,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, switch callback { case .didReceiveAuthError(let isSoftLogout): stateMachine.processEvent(.signOut(isSoft: isSoftLogout)) - case .updateRestorationToken: - userSessionStore.refreshRestorationToken(for: userSession) default: break } diff --git a/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift b/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift index fc3ded081..8a4f55a68 100644 --- a/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift +++ b/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift @@ -44,7 +44,9 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol { passphrase: nil, userAgent: UserAgentBuilder.makeASCIIUserAgent(), oidcConfiguration: oidcConfiguration, - customSlidingSyncProxy: appSettings.slidingSyncProxyURL?.absoluteString) + customSlidingSyncProxy: appSettings.slidingSyncProxyURL?.absoluteString, + sessionDelegate: userSessionStore.clientSessionDelegate, + crossProcessRefreshLockId: InfoPlistReader.main.bundleIdentifier) } // MARK: - Public diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 06b367aee..84678a6d2 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -86,8 +86,6 @@ class ClientProxy: ClientProxyProtocol { client.setDelegate(delegate: ClientDelegateWrapper { [weak self] isSoftLogout in self?.hasEncounteredAuthError = true self?.callbacks.send(.receivedAuthError(isSoftLogout: isSoftLogout)) - } tokenRefreshCallback: { [weak self] in - self?.callbacks.send(.updateRestorationToken) }) await configureAppService() @@ -536,12 +534,9 @@ private class RoomListServiceSyncIndicatorListenerProxy: RoomListServiceSyncIndi private class ClientDelegateWrapper: ClientDelegate { private let authErrorCallback: (Bool) -> Void - private let tokenRefreshCallback: () -> Void - init(authErrorCallback: @escaping (Bool) -> Void, - tokenRefreshCallback: @escaping () -> Void) { + init(authErrorCallback: @escaping (Bool) -> Void) { self.authErrorCallback = authErrorCallback - self.tokenRefreshCallback = tokenRefreshCallback } // MARK: - ClientDelegate @@ -552,7 +547,6 @@ private class ClientDelegateWrapper: ClientDelegate { } func didRefreshTokens() { - MXLog.info("The session has updated tokens.") - tokenRefreshCallback() + MXLog.info("Delegating session updates to the ClientSessionDelegate.") } } diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index 7cd797978..c2481c082 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -21,7 +21,6 @@ import MatrixRustSDK enum ClientProxyCallback { case receivedSyncUpdate case receivedAuthError(isSoftLogout: Bool) - case updateRestorationToken var isSyncUpdate: Bool { if case .receivedSyncUpdate = self { diff --git a/ElementX/Sources/Services/Keychain/KeychainController.swift b/ElementX/Sources/Services/Keychain/KeychainController.swift index 8d773dea4..f1545b123 100644 --- a/ElementX/Sources/Services/Keychain/KeychainController.swift +++ b/ElementX/Sources/Services/Keychain/KeychainController.swift @@ -16,6 +16,7 @@ import Foundation import KeychainAccess +import MatrixRustSDK enum KeychainControllerService: String { case sessions @@ -86,4 +87,20 @@ class KeychainController: KeychainControllerProtocol { MXLog.error("Failed removing all tokens") } } + + // MARK: - ClientSessionDelegate + + func retrieveSessionFromKeychain(userId: String) throws -> Session { + MXLog.info("Retrieving an updated Session from the keychain.") + guard let session = restorationTokenForUsername(userId)?.session else { + throw ClientError.Generic(msg: "Failed to find RestorationToken in the Keychain.") + } + return session + } + + func saveSessionInKeychain(session: Session) { + MXLog.info("Saving session changes in the keychain.") + let restorationToken = RestorationToken(session: session) + setRestorationToken(restorationToken, forUsername: session.userId) + } } diff --git a/ElementX/Sources/Services/Keychain/KeychainControllerProtocol.swift b/ElementX/Sources/Services/Keychain/KeychainControllerProtocol.swift index c7d21a6fe..51570ce2a 100644 --- a/ElementX/Sources/Services/Keychain/KeychainControllerProtocol.swift +++ b/ElementX/Sources/Services/Keychain/KeychainControllerProtocol.swift @@ -15,13 +15,14 @@ // import Foundation +import MatrixRustSDK struct KeychainCredentials { let userID: String let restorationToken: RestorationToken } -protocol KeychainControllerProtocol { +protocol KeychainControllerProtocol: ClientSessionDelegate { func setRestorationToken(_ restorationToken: RestorationToken, forUsername: String) func restorationTokenForUsername(_ username: String) -> RestorationToken? func restorationTokens() -> [KeychainCredentials] diff --git a/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxyProtocol.swift b/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxyProtocol.swift index 89ead6dbe..9f1ca8d41 100644 --- a/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxyProtocol.swift +++ b/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxyProtocol.swift @@ -65,7 +65,7 @@ extension NotificationItemProxyProtocol { return false case let .messageLike(content): switch content { - case let .roomMessage(messageType): + case let .roomMessage(messageType, _): switch messageType { case .image, .video, .audio: return true diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift index 85052484a..26b62bd5e 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift @@ -229,7 +229,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { inviterProxy = RoomMemberProxy(member: inviter, backgroundTaskService: backgroundTaskService) } - let notificationMode = roomInfo.notificationMode.flatMap { RoomNotificationModeProxy.from(roomNotificationMode: $0) } + let notificationMode = roomInfo.userDefinedNotificationMode.flatMap { RoomNotificationModeProxy.from(roomNotificationMode: $0) } let details = RoomSummaryDetails(id: roomInfo.id, name: roomInfo.name ?? roomInfo.id, diff --git a/ElementX/Sources/Services/Session/UserSession.swift b/ElementX/Sources/Services/Session/UserSession.swift index 201e4f8b9..b5b45661e 100644 --- a/ElementX/Sources/Services/Session/UserSession.swift +++ b/ElementX/Sources/Services/Session/UserSession.swift @@ -94,8 +94,6 @@ class UserSession: UserSessionProtocol { case .receivedAuthError(let isSoftLogout): callbacks.send(.didReceiveAuthError(isSoftLogout: isSoftLogout)) tearDownAuthErrorWatchdog() - case .updateRestorationToken: - callbacks.send(.updateRestorationToken) default: break } diff --git a/ElementX/Sources/Services/Session/UserSessionProtocol.swift b/ElementX/Sources/Services/Session/UserSessionProtocol.swift index 0d7caa855..b8da4cd65 100644 --- a/ElementX/Sources/Services/Session/UserSessionProtocol.swift +++ b/ElementX/Sources/Services/Session/UserSessionProtocol.swift @@ -21,7 +21,6 @@ enum UserSessionCallback { case sessionVerificationNeeded case didVerifySession case didReceiveAuthError(isSoftLogout: Bool) - case updateRestorationToken } protocol UserSessionProtocol { diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index 9faf680d4..4792e2f10 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -31,6 +31,8 @@ class UserSessionStore: UserSessionStoreProtocol { /// The base directory where all session data is stored. let baseDirectory: URL + var clientSessionDelegate: ClientSessionDelegate { keychainController } + init(backgroundTaskService: BackgroundTaskServiceProtocol) { keychainController = KeychainController(service: .sessions, accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier) @@ -76,16 +78,6 @@ class UserSessionStore: UserSessionStoreProtocol { return .failure(error) } } - - func refreshRestorationToken(for userSession: UserSessionProtocol) -> Result { - guard let restorationToken = userSession.clientProxy.restorationToken else { - return .failure(.failedRefreshingRestoreToken) - } - - keychainController.setRestorationToken(restorationToken, forUsername: userSession.clientProxy.userID) - - return .success(()) - } func logout(userSession: UserSessionProtocol) { let userID = userSession.clientProxy.userID @@ -115,6 +107,8 @@ class UserSessionStore: UserSessionStoreProtocol { .username(username: credentials.userID) .homeserverUrl(url: credentials.restorationToken.session.homeserverUrl) .userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent()) + .enableCrossProcessRefreshLock(processId: InfoPlistReader.main.bundleIdentifier, + sessionDelegate: keychainController) .serverVersions(versions: ["v1.0", "v1.1", "v1.2", "v1.3", "v1.4", "v1.5"]) // FIXME: Quick and dirty fix for stopping version requests on startup https://github.com/matrix-org/matrix-rust-sdk/pull/1376 do { @@ -133,9 +127,9 @@ class UserSessionStore: UserSessionStoreProtocol { private func setupProxyForClient(_ client: Client) async -> Result { do { let session = try client.session() - let userId = try client.userId() + let userID = try client.userId() - keychainController.setRestorationToken(RestorationToken(session: session), forUsername: userId) + keychainController.setRestorationToken(RestorationToken(session: session), forUsername: userID) } catch { MXLog.error("Failed setting up user session with error: \(error)") return .failure(.failedSettingUpSession) diff --git a/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift b/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift index be0eefaad..9ff7b4ba7 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift @@ -37,15 +37,14 @@ protocol UserSessionStoreProtocol { /// Returns the location to store user data for a particular username. var baseDirectory: URL { get } + /// Returns the delegate that should handle any changes to a `Client`'s `Session`. + var clientSessionDelegate: ClientSessionDelegate { get } + /// Restores an existing user session. func restoreUserSession() async -> Result /// Creates a user session for a new client from the SDK. func userSession(for client: Client) async -> Result - - /// Refresh the restore token of the client for a given session. - @discardableResult - func refreshRestorationToken(for userSession: UserSessionProtocol) -> Result /// Logs out of the specified session. func logout(userSession: UserSessionProtocol) diff --git a/NSE/Sources/NotificationContentBuilder.swift b/NSE/Sources/NotificationContentBuilder.swift index b666d38d2..85648b58e 100644 --- a/NSE/Sources/NotificationContentBuilder.swift +++ b/NSE/Sources/NotificationContentBuilder.swift @@ -36,7 +36,7 @@ struct NotificationContentBuilder { switch try? event.eventType() { case let .messageLike(content): switch content { - case .roomMessage(messageType: let messageType): + case .roomMessage(let messageType, _): return try await processRoomMessage(notificationItem: notificationItem, messageType: messageType, mediaProvider: mediaProvider) default: return processEmpty(notificationItem: notificationItem) diff --git a/NSE/Sources/NotificationServiceExtension.swift b/NSE/Sources/NotificationServiceExtension.swift index 759212c0b..979b95b32 100644 --- a/NSE/Sources/NotificationServiceExtension.swift +++ b/NSE/Sources/NotificationServiceExtension.swift @@ -41,12 +41,6 @@ class NotificationServiceExtension: UNNotificationServiceExtension { // - NotificationID could not be resolved return contentHandler(request.content) } - - if credentials.restorationToken.session.oidcData != nil { - // Notification content is disabled for OIDC sessions - // until token refresh is multi-process aware. - return contentHandler(request.content) - } handler = contentHandler modifiedContent = request.content.mutableCopy() as? UNMutableNotificationContent @@ -80,8 +74,9 @@ class NotificationServiceExtension: UNNotificationServiceExtension { MXLog.info("\(tag) run with roomId: \(roomId), eventId: \(eventId)") do { - let userSession = try NSEUserSession(credentials: credentials) + let userSession = try NSEUserSession(credentials: credentials, clientSessionDelegate: keychainController) self.userSession = userSession + guard let itemProxy = await userSession.notificationItemProxy(roomID: roomId, eventID: eventId) else { MXLog.info("\(tag) no notification for the event, discard") return discard() diff --git a/NSE/Sources/Other/NSEUserSession.swift b/NSE/Sources/Other/NSEUserSession.swift index 1f63f3a56..77161df90 100644 --- a/NSE/Sources/Other/NSEUserSession.swift +++ b/NSE/Sources/Other/NSEUserSession.swift @@ -25,27 +25,30 @@ final class NSEUserSession { imageCache: .onlyOnDisk, backgroundTaskService: nil) - init(credentials: KeychainCredentials) throws { + init(credentials: KeychainCredentials, clientSessionDelegate: ClientSessionDelegate) throws { userID = credentials.userID baseClient = try ClientBuilder() .basePath(path: URL.sessionsBaseDirectory.path) .username(username: credentials.userID) .userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent()) + .enableCrossProcessRefreshLock(processId: InfoPlistReader.main.bundleIdentifier, + sessionDelegate: clientSessionDelegate) .build() - + + baseClient.setDelegate(delegate: ClientDelegateWrapper()) try baseClient.restoreSession(session: credentials.restorationToken.session) - + notificationClient = try baseClient .notificationClient(processSetup: .multipleProcesses) .filterByPushRules() .finish() } - + func notificationItemProxy(roomID: String, eventID: String) async -> NotificationItemProxyProtocol? { await Task.dispatch(on: .global()) { do { let notification = try self.notificationClient.getNotification(roomId: roomID, eventId: eventID) - + guard let notification else { return nil } @@ -60,3 +63,15 @@ final class NSEUserSession { } } } + +private class ClientDelegateWrapper: ClientDelegate { + // MARK: - ClientDelegate + + func didReceiveAuthError(isSoftLogout: Bool) { + MXLog.error("Received authentication error, the NSE can't handle this.") + } + + func didRefreshTokens() { + MXLog.info("Delegating session updates to the ClientSessionDelegate.") + } +} diff --git a/changelog.d/1712.change b/changelog.d/1712.change new file mode 100644 index 000000000..fdd3a00e3 --- /dev/null +++ b/changelog.d/1712.change @@ -0,0 +1 @@ +Enable token refresh in the NSE (and notifications for OIDC accounts). \ No newline at end of file diff --git a/project.yml b/project.yml index e6b7925ce..05e1a30ea 100644 --- a/project.yml +++ b/project.yml @@ -46,7 +46,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/matrix-org/matrix-rust-components-swift - exactVersion: 1.1.13 + exactVersion: 1.1.14 # path: ../matrix-rust-sdk DesignKit: path: DesignKit