diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index dd570d44d5..db213eac67 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -82,6 +82,7 @@ 1146E9EDCF8344F7D6E0D553 /* MockCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0376C429FAB1687C3D905F3E /* MockCoder.swift */; }; 119AE9A3FC6E0606C1146528 /* NotificationSettingsEditScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97F8963B14EB0AF3940DDBF /* NotificationSettingsEditScreenRoomCell.swift */; }; 11A6B8E3CBDBF0A4107FF4CE /* OnboardingFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */; }; + 121DDBFAD88B785630852C9A /* AppHooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B048CB133239700CD890F5D /* AppHooks.swift */; }; 126EE01D8BEAEF26105D83C5 /* RoomDetailsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */; }; 12C867E85E6D12EEDFD0B127 /* CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C4762F8D6112E43117DB2F /* CustomStringConvertible.swift */; }; 12CCA59536EDD99A3272CF77 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3F82523D6F48B926D6AF68 /* AppSettings.swift */; }; @@ -5712,6 +5713,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 121DDBFAD88B785630852C9A /* AppHooks.swift in Sources */, 43F06DF42EC00B3CE2B020A4 /* AppSettings.swift in Sources */, F253AAB4C8F06208173C9C4A /* Assets.swift in Sources */, 484202C5D50983442D24D061 /* AttributedString.swift in Sources */, diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 9658a67784..abec4b1304 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -102,7 +102,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg let keychainController = KeychainController(service: .sessions, accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier) - userSessionStore = UserSessionStore(keychainController: keychainController) + userSessionStore = UserSessionStore(keychainController: keychainController, appHooks: appHooks) let appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings) let appLockNavigationCoordinator = NavigationRootCoordinator() @@ -458,10 +458,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg let encryptionKeyProvider = EncryptionKeyProvider() let authenticationService = AuthenticationService(userSessionStore: userSessionStore, encryptionKeyProvider: encryptionKeyProvider, - appSettings: appSettings) + appSettings: appSettings, + appHooks: appHooks) let qrCodeLoginService = QRCodeLoginService(encryptionKeyProvider: encryptionKeyProvider, userSessionStore: userSessionStore, - appSettings: appSettings) + appSettings: appSettings, + appHooks: appHooks) authenticationFlowCoordinator = AuthenticationFlowCoordinator(authenticationService: authenticationService, qrCodeLoginService: qrCodeLoginService, @@ -489,7 +491,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg let authenticationService = AuthenticationService(userSessionStore: userSessionStore, encryptionKeyProvider: EncryptionKeyProvider(), - appSettings: appSettings) + appSettings: appSettings, + appHooks: appHooks) _ = await authenticationService.configure(for: userSession.clientProxy.homeserver) let parameters = SoftLogoutScreenCoordinatorParameters(authenticationService: authenticationService, diff --git a/ElementX/Sources/Hooks/AppHooks.swift b/ElementX/Sources/Hooks/AppHooks.swift index 3101ac9a7b..b8e53ce697 100644 --- a/ElementX/Sources/Hooks/AppHooks.swift +++ b/ElementX/Sources/Hooks/AppHooks.swift @@ -15,10 +15,12 @@ // import Foundation +import MatrixRustSDK // MARK: Registration class AppHooks: AppHooksProtocol { + #if IS_MAIN_APP private var appSettingsHook: AppSettingsHookProtocol? func registerAppSettingsHook(_ hook: AppSettingsHookProtocol) { appSettingsHook = hook @@ -38,6 +40,17 @@ class AppHooks: AppHooksProtocol { guard let bugReportHook else { return bugReport } return bugReportHook.run(bugReport: bugReport) } + #endif + + private var clientBuilderHook: ClientBuilderHookProtocol? + func registerClientBuilderHook(_ hook: ClientBuilderHookProtocol) { + clientBuilderHook = hook + } + + func runClientBuilderHook(_ clientBuilder: ClientBuilder) -> ClientBuilder { + guard let clientBuilderHook else { return clientBuilder } + return clientBuilderHook.run(builder: clientBuilder) + } } protocol AppHooksProtocol { @@ -50,6 +63,7 @@ extension AppHooksProtocol { // MARK: Protocols +#if IS_MAIN_APP protocol AppSettingsHookProtocol { func run(appSettings: AppSettings) -> AppSettings } @@ -57,3 +71,8 @@ protocol AppSettingsHookProtocol { protocol BugReportHookProtocol { func run(bugReport: BugReport) -> BugReport } +#endif + +protocol ClientBuilderHookProtocol { + func run(builder: ClientBuilder) -> ClientBuilder +} diff --git a/ElementX/Sources/Other/Extensions/ClientBuilder.swift b/ElementX/Sources/Other/Extensions/ClientBuilder.swift index 0ce19f5c97..d3b76e9c65 100644 --- a/ElementX/Sources/Other/Extensions/ClientBuilder.swift +++ b/ElementX/Sources/Other/Extensions/ClientBuilder.swift @@ -19,7 +19,11 @@ import MatrixRustSDK extension ClientBuilder { /// A helper method that applies the common builder modifiers needed for the app. - static func baseBuilder(setupEncryption: Bool = true, httpProxy: String? = nil, slidingSyncProxy: URL? = nil, sessionDelegate: ClientSessionDelegate) -> ClientBuilder { + static func baseBuilder(setupEncryption: Bool = true, + httpProxy: String? = nil, + slidingSyncProxy: URL? = nil, + sessionDelegate: ClientSessionDelegate, + appHooks: AppHooks) -> ClientBuilder { var builder = ClientBuilder() .slidingSyncProxy(slidingSyncProxy: slidingSyncProxy?.absoluteString) .enableCrossProcessRefreshLock(processId: InfoPlistReader.main.bundleIdentifier, sessionDelegate: sessionDelegate) @@ -36,6 +40,6 @@ extension ClientBuilder { builder = builder.proxy(url: httpProxy) } - return builder + return appHooks.runClientBuilderHook(builder) } } diff --git a/ElementX/Sources/Services/Authentication/AuthenticationService.swift b/ElementX/Sources/Services/Authentication/AuthenticationService.swift index 5e163832a4..0a9f618818 100644 --- a/ElementX/Sources/Services/Authentication/AuthenticationService.swift +++ b/ElementX/Sources/Services/Authentication/AuthenticationService.swift @@ -25,15 +25,17 @@ class AuthenticationService: AuthenticationServiceProtocol { private let userSessionStore: UserSessionStoreProtocol private let appSettings: AppSettings + private let appHooks: AppHooks private let homeserverSubject: CurrentValueSubject var homeserver: CurrentValuePublisher { homeserverSubject.asCurrentValuePublisher() } - init(userSessionStore: UserSessionStoreProtocol, encryptionKeyProvider: EncryptionKeyProviderProtocol, appSettings: AppSettings) { + init(userSessionStore: UserSessionStoreProtocol, encryptionKeyProvider: EncryptionKeyProviderProtocol, appSettings: AppSettings, appHooks: AppHooks) { sessionDirectory = .sessionsBaseDirectory.appending(component: UUID().uuidString) passphrase = encryptionKeyProvider.generateKey().base64EncodedString() self.userSessionStore = userSessionStore self.appSettings = appSettings + self.appHooks = appHooks homeserverSubject = .init(LoginHomeserver(address: appSettings.defaultHomeserverAddress, loginMode: .unknown)) @@ -140,7 +142,8 @@ class AuthenticationService: AuthenticationServiceProtocol { ClientBuilder .baseBuilder(httpProxy: appSettings.websiteURL.globalProxy, slidingSyncProxy: appSettings.slidingSyncProxyURL, - sessionDelegate: userSessionStore.clientSessionDelegate) + sessionDelegate: userSessionStore.clientSessionDelegate, + appHooks: appHooks) .sessionPath(path: sessionDirectory.path(percentEncoded: false)) .passphrase(passphrase: passphrase) .requiresSlidingSync() diff --git a/ElementX/Sources/Services/QRCode/QRCodeLoginService.swift b/ElementX/Sources/Services/QRCode/QRCodeLoginService.swift index e02e810273..68a6deccfb 100644 --- a/ElementX/Sources/Services/QRCode/QRCodeLoginService.swift +++ b/ElementX/Sources/Services/QRCode/QRCodeLoginService.swift @@ -22,8 +22,10 @@ import MatrixRustSDK final class QRCodeLoginService: QRCodeLoginServiceProtocol { private let sessionDirectory: URL private let passphrase: String + private let userSessionStore: UserSessionStoreProtocol private let appSettings: AppSettings + private let appHooks: AppHooks private let qrLoginProgressSubject = PassthroughSubject() var qrLoginProgressPublisher: AnyPublisher { @@ -32,11 +34,13 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol { init(encryptionKeyProvider: EncryptionKeyProviderProtocol, userSessionStore: UserSessionStoreProtocol, - appSettings: AppSettings) { - self.userSessionStore = userSessionStore - self.appSettings = appSettings + appSettings: AppSettings, + appHooks: AppHooks) { sessionDirectory = .sessionsBaseDirectory.appending(component: UUID().uuidString) passphrase = encryptionKeyProvider.generateKey().base64EncodedString() + self.userSessionStore = userSessionStore + self.appSettings = appSettings + self.appHooks = appHooks } func loginWithQRCode(data: Data) async -> Result { @@ -56,7 +60,8 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol { let client = try await ClientBuilder .baseBuilder(httpProxy: appSettings.websiteURL.globalProxy, slidingSyncProxy: appSettings.slidingSyncProxyURL, - sessionDelegate: userSessionStore.clientSessionDelegate) + sessionDelegate: userSessionStore.clientSessionDelegate, + appHooks: appHooks) .sessionPath(path: sessionDirectory.path(percentEncoded: false)) .passphrase(passphrase: passphrase) .requiresSlidingSync() diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index 4e69719177..a1e590dd12 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -20,6 +20,7 @@ import MatrixRustSDK class UserSessionStore: UserSessionStoreProtocol { private let keychainController: KeychainControllerProtocol + private let appHooks: AppHooks private let matrixSDKStateKey = "matrix-sdk-state" /// Whether or not there are sessions in the store. @@ -29,8 +30,9 @@ class UserSessionStore: UserSessionStoreProtocol { var clientSessionDelegate: ClientSessionDelegate { keychainController } - init(keychainController: KeychainControllerProtocol) { + init(keychainController: KeychainControllerProtocol, appHooks: AppHooks) { self.keychainController = keychainController + self.appHooks = appHooks } /// Deletes all data stored in the shared container and keychain @@ -120,7 +122,8 @@ class UserSessionStore: UserSessionStoreProtocol { let builder = ClientBuilder .baseBuilder(httpProxy: URL(string: homeserverURL)?.globalProxy, - sessionDelegate: keychainController) + sessionDelegate: keychainController, + appHooks: appHooks) .sessionPath(path: credentials.restorationToken.sessionDirectory.path(percentEncoded: false)) .username(username: credentials.userID) .homeserverUrl(url: homeserverURL) diff --git a/NSE/Sources/NotificationServiceExtension.swift b/NSE/Sources/NotificationServiceExtension.swift index 11cd835361..7041b629f6 100644 --- a/NSE/Sources/NotificationServiceExtension.swift +++ b/NSE/Sources/NotificationServiceExtension.swift @@ -50,6 +50,8 @@ class NotificationServiceExtension: UNNotificationServiceExtension { private var handler: ((UNNotificationContent) -> Void)? private var modifiedContent: UNMutableNotificationContent? + private let appHooks = AppHooks() + // Used to create one single UserSession across process/instances/runs private static let serialQueue = DispatchQueue(label: "io.element.elementx.nse") private static var userSession: NSEUserSession? @@ -82,9 +84,9 @@ class NotificationServiceExtension: UNNotificationServiceExtension { if Self.userSession == nil { // This function might be run concurrently and from different processes // It's imperative that we create **at most** one UserSession/Client per process - Task.synchronous { + Task.synchronous { [appHooks] in do { - Self.userSession = try await NSEUserSession(credentials: credentials, clientSessionDelegate: keychainController) + Self.userSession = try await NSEUserSession(credentials: credentials, clientSessionDelegate: keychainController, appHooks: appHooks) } catch { MXLog.error("Failed creating user session with error: \(error)") } diff --git a/NSE/Sources/Other/NSEUserSession.swift b/NSE/Sources/Other/NSEUserSession.swift index 2464d85083..d5b9152095 100644 --- a/NSE/Sources/Other/NSEUserSession.swift +++ b/NSE/Sources/Other/NSEUserSession.swift @@ -25,7 +25,7 @@ final class NSEUserSession { imageCache: .onlyOnDisk) private let delegateHandle: TaskHandle? - init(credentials: KeychainCredentials, clientSessionDelegate: ClientSessionDelegate) async throws { + init(credentials: KeychainCredentials, clientSessionDelegate: ClientSessionDelegate, appHooks: AppHooks) async throws { userID = credentials.userID if credentials.restorationToken.passphrase != nil { MXLog.info("Restoring client with encrypted store.") @@ -35,7 +35,8 @@ final class NSEUserSession { let clientBuilder = ClientBuilder .baseBuilder(setupEncryption: false, httpProxy: URL(string: homeserverURL)?.globalProxy, - sessionDelegate: clientSessionDelegate) + sessionDelegate: clientSessionDelegate, + appHooks: appHooks) .sessionPath(path: credentials.restorationToken.sessionDirectory.path(percentEncoded: false)) .username(username: credentials.userID) .homeserverUrl(url: homeserverURL) diff --git a/NSE/SupportingFiles/target.yml b/NSE/SupportingFiles/target.yml index bad92c044d..d553fba63f 100644 --- a/NSE/SupportingFiles/target.yml +++ b/NSE/SupportingFiles/target.yml @@ -107,3 +107,4 @@ targets: - path: ../../ElementX/Sources/Services/UserSession/RestorationToken.swift - path: ../../ElementX/Sources/Services/ElementCall/ElementCallServiceConstants.swift - path: ../../ElementX/Sources/Application/AppSettings.swift + - path: ../../ElementX/Sources/Hooks/AppHooks.swift