mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Make the remote settings hook usable within the app extensions. (#4342)
This commit is contained in:
@@ -33,11 +33,6 @@ class AppHooks: AppHooksProtocol {
|
||||
certificateValidatorHook = hook
|
||||
}
|
||||
|
||||
private(set) var elementWellKnownHook: ElementWellKnownHookProtocol = DefaultElementWellKnownHook()
|
||||
func registerElementWellKnownHook(_ hook: ElementWellKnownHookProtocol) {
|
||||
elementWellKnownHook = hook
|
||||
}
|
||||
|
||||
private(set) var roomScreenHook: RoomScreenHookProtocol = DefaultRoomScreenHook()
|
||||
func registerRoomScreenHook(_ hook: RoomScreenHookProtocol) {
|
||||
roomScreenHook = hook
|
||||
@@ -48,6 +43,11 @@ class AppHooks: AppHooksProtocol {
|
||||
func registerClientBuilderHook(_ hook: ClientBuilderHookProtocol) {
|
||||
clientBuilderHook = hook
|
||||
}
|
||||
|
||||
private(set) var remoteSettingsHook: RemoteSettingsHookProtocol = DefaultRemoteSettingsHook()
|
||||
func registerRemoteSettingsHook(_ hook: RemoteSettingsHookProtocol) {
|
||||
remoteSettingsHook = hook
|
||||
}
|
||||
}
|
||||
|
||||
protocol AppHooksProtocol {
|
||||
|
||||
+20
-8
@@ -8,15 +8,25 @@
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
protocol ElementWellKnownHookProtocol {
|
||||
func validate(using client: ClientProtocol) async -> Result<Void, ElementWellKnownError>
|
||||
enum RemoteSettingsError: Error {
|
||||
case elementProRequired(serverName: String)
|
||||
}
|
||||
|
||||
struct DefaultElementWellKnownHook: ElementWellKnownHookProtocol {
|
||||
protocol RemoteSettingsHookProtocol {
|
||||
#if IS_MAIN_APP
|
||||
func initializeCache(using client: ClientProtocol, applyingTo appSettings: CommonSettingsProtocol) async -> Result<Void, RemoteSettingsError>
|
||||
func updateCache(using client: ClientProtocol) async
|
||||
func reset(_ appSettings: CommonSettingsProtocol)
|
||||
#endif
|
||||
func loadCache(forHomeserver: String, applyingTo appSettings: CommonSettingsProtocol)
|
||||
}
|
||||
|
||||
struct DefaultRemoteSettingsHook: RemoteSettingsHookProtocol {
|
||||
#if IS_MAIN_APP
|
||||
/// A best effort implementation to let Element X advertise to users when they should be using
|
||||
/// Element Pro. In an ideal world the backend would be able to validate the client's requests
|
||||
/// instead of relying on it to check a well-known file for this.
|
||||
func validate(using client: ClientProtocol) async -> Result<Void, ElementWellKnownError> {
|
||||
func initializeCache(using client: ClientProtocol, applyingTo appSettings: CommonSettingsProtocol) async -> Result<Void, RemoteSettingsError> {
|
||||
guard case let .success(wellKnownData) = await client.elementWellKnown() else {
|
||||
// Nothing to check, carry on as normal.
|
||||
return .success(())
|
||||
@@ -36,6 +46,12 @@ struct DefaultElementWellKnownHook: ElementWellKnownHookProtocol {
|
||||
return .success(())
|
||||
}
|
||||
}
|
||||
|
||||
func updateCache(using client: ClientProtocol) async { }
|
||||
func reset(_ appSettings: any CommonSettingsProtocol) { }
|
||||
#endif
|
||||
|
||||
func loadCache(forHomeserver: String, applyingTo appSettings: CommonSettingsProtocol) { }
|
||||
}
|
||||
|
||||
private struct ElementWellKnown: Decodable {
|
||||
@@ -47,7 +63,3 @@ private struct ElementWellKnown: Decodable {
|
||||
case enforceElementPro = "enforce_element_pro"
|
||||
}
|
||||
}
|
||||
|
||||
enum ElementWellKnownError: Error {
|
||||
case elementProRequired(serverName: String)
|
||||
}
|
||||
@@ -702,6 +702,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
tearDownUserSession()
|
||||
|
||||
AppSettings.resetSessionSpecificSettings()
|
||||
appHooks.remoteSettingsHook.reset(appSettings)
|
||||
|
||||
// Reset analytics
|
||||
ServiceLocator.shared.analytics.optOut()
|
||||
|
||||
@@ -20,6 +20,7 @@ protocol CommonSettingsProtocol {
|
||||
var enableKeyShareOnInvite: Bool { get }
|
||||
var hideQuietNotificationAlerts: Bool { get }
|
||||
var threadsEnabled: Bool { get }
|
||||
var bugReportRageshakeURL: RemotePreference<RageshakeConfiguration> { get }
|
||||
}
|
||||
|
||||
/// Store Element specific app settings.
|
||||
@@ -252,7 +253,6 @@ final class AppSettings {
|
||||
|
||||
// MARK: - Bug report
|
||||
|
||||
let bugReportRageshakeURL: ConfigurableSetting<RageshakeConfiguration> = .init(Secrets.rageshakeURL.map { .url(URL(string: $0)!) } ?? .disabled) // swiftlint:disable:this force_unwrapping
|
||||
let bugReportSentryURL: URL? = Secrets.sentryDSN.map { URL(string: $0)! } // swiftlint:disable:this force_unwrapping
|
||||
let bugReportSentryRustURL: URL? = Secrets.sentryRustDSN.map { URL(string: $0)! } // swiftlint:disable:this force_unwrapping
|
||||
/// The name allocated by the bug report server
|
||||
@@ -375,6 +375,8 @@ final class AppSettings {
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.threadsEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var threadsEnabled
|
||||
|
||||
let bugReportRageshakeURL: RemotePreference<RageshakeConfiguration> = .init(Secrets.rageshakeURL.map { .url(URL(string: $0)!) } ?? .disabled) // swiftlint:disable:this force_unwrapping
|
||||
}
|
||||
|
||||
extension AppSettings: CommonSettingsProtocol { }
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
/// A setting that can be instantiated with an initial value, overridden and reset back again,
|
||||
/// automatically publishing the changes for downstream subscribers to react to.
|
||||
///
|
||||
/// Unlike ``UserPreference``, this type of setting isn't settable by the user, nor is the
|
||||
/// override persisted between app launches.
|
||||
struct ConfigurableSetting<T: Equatable> {
|
||||
private let initialValue: T
|
||||
private let subject: CurrentValueSubject<T, Never>
|
||||
var publisher: CurrentValuePublisher<T, Never> { subject.asCurrentValuePublisher() }
|
||||
var isOverridden: Bool { subject.value != initialValue }
|
||||
|
||||
init(_ initialValue: T) {
|
||||
self.initialValue = initialValue
|
||||
subject = .init(initialValue)
|
||||
}
|
||||
|
||||
func override(_ value: T) {
|
||||
subject.send(value)
|
||||
}
|
||||
|
||||
func reset() {
|
||||
if isOverridden {
|
||||
subject.send(initialValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A type that represents how rageshakes are collected by the app.
|
||||
enum RageshakeConfiguration: Equatable, Codable {
|
||||
/// Rageshakes should be sent to the provided URL
|
||||
case url(URL)
|
||||
/// Rageshakes are disabled.
|
||||
case disabled
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
/// A setting that can be instantiated with a default value, remotely overridden and reset back
|
||||
/// to the default, automatically publishing the changes for downstream subscribers to react to.
|
||||
///
|
||||
/// Unlike ``UserPreference``, this type of setting isn't settable by the user, nor is the
|
||||
/// remote value persisted between app launches.
|
||||
struct RemotePreference<T: Equatable> {
|
||||
private let defaultValue: T
|
||||
private let subject: CurrentValueSubject<T, Never>
|
||||
var publisher: CurrentValuePublisher<T, Never> { subject.asCurrentValuePublisher() }
|
||||
var isRemotelyConfigured: Bool { subject.value != defaultValue }
|
||||
|
||||
init(_ defaultValue: T) {
|
||||
self.defaultValue = defaultValue
|
||||
subject = .init(defaultValue)
|
||||
}
|
||||
|
||||
func applyRemoteValue(_ value: T) {
|
||||
subject.send(value)
|
||||
}
|
||||
|
||||
func reset() {
|
||||
if isRemotelyConfigured {
|
||||
subject.send(defaultValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,6 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
||||
var homeserver = LoginHomeserver(address: homeserverAddress, loginMode: .unknown)
|
||||
|
||||
let client = try await makeClient(homeserverAddress: homeserverAddress)
|
||||
try await appHooks.elementWellKnownHook.validate(using: client).get()
|
||||
let loginDetails = await client.homeserverLoginDetails()
|
||||
|
||||
MXLog.info("Sliding sync: \(client.slidingSyncVersion())")
|
||||
@@ -82,7 +81,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
||||
} catch ClientBuildError.SlidingSyncVersion(let error) {
|
||||
MXLog.info("User entered a homeserver that isn't configured for sliding sync: \(error)")
|
||||
return .failure(.slidingSyncNotAvailable)
|
||||
} catch ElementWellKnownError.elementProRequired(let serverName) {
|
||||
} catch RemoteSettingsError.elementProRequired(let serverName) {
|
||||
return .failure(.elementProRequired(serverName: serverName))
|
||||
} catch {
|
||||
MXLog.error("Failed configuring a server: \(error)")
|
||||
@@ -179,7 +178,6 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
||||
|
||||
do {
|
||||
let client = try await makeClient(homeserverAddress: scannedServerName)
|
||||
try await appHooks.elementWellKnownHook.validate(using: client).get()
|
||||
try await client.loginWithQrCode(qrCodeData: qrData,
|
||||
oidcConfiguration: appSettings.oidcConfiguration.rustValue,
|
||||
progressListener: listener)
|
||||
@@ -188,7 +186,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
||||
} catch let error as HumanQrLoginError {
|
||||
MXLog.error("QRCode login error: \(error)")
|
||||
return .failure(error.serviceError)
|
||||
} catch ElementWellKnownError.elementProRequired(let serverName) {
|
||||
} catch RemoteSettingsError.elementProRequired(let serverName) {
|
||||
return .failure(.elementProRequired(serverName: serverName))
|
||||
} catch {
|
||||
MXLog.error("QRCode login unknown error: \(error)")
|
||||
@@ -209,12 +207,15 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
||||
// so that caches (e.g. server versions) are always fresh for the new server.
|
||||
rotateSessionDirectory()
|
||||
|
||||
return try await clientFactory.makeClient(homeserverAddress: homeserverAddress,
|
||||
sessionDirectories: sessionDirectories,
|
||||
passphrase: passphrase,
|
||||
clientSessionDelegate: userSessionStore.clientSessionDelegate,
|
||||
appSettings: appSettings,
|
||||
appHooks: appHooks)
|
||||
let client = try await clientFactory.makeClient(homeserverAddress: homeserverAddress,
|
||||
sessionDirectories: sessionDirectories,
|
||||
passphrase: passphrase,
|
||||
clientSessionDelegate: userSessionStore.clientSessionDelegate,
|
||||
appSettings: appSettings,
|
||||
appHooks: appHooks)
|
||||
try await appHooks.remoteSettingsHook.initializeCache(using: client, applyingTo: appSettings).get()
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
private func rotateSessionDirectory() {
|
||||
|
||||
@@ -46,13 +46,6 @@ enum BugReportServiceError: LocalizedError {
|
||||
}
|
||||
}
|
||||
|
||||
enum RageshakeConfiguration: Equatable {
|
||||
/// Rageshakes should be sent to the provided URL
|
||||
case url(URL)
|
||||
/// Rageshakes are disabled.
|
||||
case disabled
|
||||
}
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol BugReportServiceProtocol: AnyObject {
|
||||
var isEnabled: Bool { get }
|
||||
|
||||
@@ -117,6 +117,7 @@ class UserSessionStore: UserSessionStoreProtocol {
|
||||
}
|
||||
|
||||
let homeserverURL = credentials.restorationToken.session.homeserverUrl
|
||||
appHooks.remoteSettingsHook.loadCache(forHomeserver: homeserverURL, applyingTo: appSettings)
|
||||
|
||||
let builder = ClientBuilder
|
||||
.baseBuilder(httpProxy: URL(string: homeserverURL)?.globalProxy,
|
||||
@@ -134,11 +135,12 @@ class UserSessionStore: UserSessionStoreProtocol {
|
||||
|
||||
do {
|
||||
let client = try await builder.build()
|
||||
|
||||
try await client.restoreSession(session: credentials.restorationToken.session)
|
||||
|
||||
MXLog.info("Set up session for user \(credentials.userID) at: \(credentials.restorationToken.sessionDirectories)")
|
||||
|
||||
Task(priority: .low) { await appHooks.remoteSettingsHook.updateCache(using: client) }
|
||||
|
||||
return try await .success(setupProxyForClient(client, needsSlidingSyncMigration: credentials.restorationToken.needsSlidingSyncMigration))
|
||||
} catch UserSessionStoreError.failedSettingUpClientProxy(let error) {
|
||||
// If this has failed, there is likely something wrong with the creation of the sync service
|
||||
|
||||
Reference in New Issue
Block a user