Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
039FE1757084092146C0B616 /* ClientFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176A581FDC61737676782607 /* ClientFactoryProtocol.swift */; };
03BD83E8BDD23AE059802E0D /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */; };
03CDCA6243F89B194E3FAD17 /* EncryptionAuthenticity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955336CBD5ED73C792D1F580 /* EncryptionAuthenticity.swift */; };
03E9750B2F994A79FA1296A1 /* AccountProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67BCEFE35F8D00CADF108C9 /* AccountProvider.swift */; };
03F192C5C011AFA6068C9EAA /* SpaceServiceSDKMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B1E4D1F0A6FAAE64B54D76 /* SpaceServiceSDKMock.swift */; };
0437765FF480249486893CC7 /* ScreenTrackerViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196004E7695FBA292A7944AF /* ScreenTrackerViewModifier.swift */; };
044DD8F80231BC30570F7965 /* UserDiscoveryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AAD845E53B0C8B5E0812C2 /* UserDiscoveryService.swift */; };
Expand Down Expand Up @@ -370,6 +371,7 @@
3A0D1A201E2CD209A04CE89B /* SearchScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD63A653BCA2C8112654B19 /* SearchScreenModels.swift */; };
3A164187907DA43B7858F9EC /* CompletionSuggestionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EA0312A6262484AA393AC9 /* CompletionSuggestionServiceTests.swift */; };
3A2640C52505BAB3D983A92A /* RemotePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69A05E85E4872C3221C5C287 /* RemotePreference.swift */; };
3A363617757E6838AC3D15FB /* AccountProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67BCEFE35F8D00CADF108C9 /* AccountProvider.swift */; };
3A64A93A651A3CB8774ADE8E /* SwiftOGG in Frameworks */ = {isa = PBXBuildFile; productRef = 3FE40E79C36E7903121E6E3B /* SwiftOGG */; };
3A68752BB9D51B8713C0FC2C /* AuthenticationStartScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A768CA51A59B8A5D8C8FD599 /* AuthenticationStartScreen.swift */; };
3AA9E878FDCFF85664AC071F /* ComposerDraftService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D70253004A5AEC9C73D6A4F /* ComposerDraftService.swift */; };
Expand Down Expand Up @@ -734,6 +736,7 @@
756EA0D663261889EF64E6D4 /* VoiceMessageRecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E9CBF577B9711CFBB4FA40D /* VoiceMessageRecordingView.swift */; };
7573D682F089205F7F1D96CF /* SessionDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2067FF58B4996323EB40C /* SessionDirectories.swift */; };
757862045774A0F458357E19 /* RoomThreadListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544B9262E1BC6F489C03FFFA /* RoomThreadListScreen.swift */; };
75C1AFACE6C0DEA18F16456C /* AccountProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67BCEFE35F8D00CADF108C9 /* AccountProvider.swift */; };
75E55A763FE8BA64FB8513E4 /* TimelineStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F968FDB68F9D4CB9010FED70 /* TimelineStateTests.swift */; };
75ED4B73983228BB6922CE3C /* KnockRequestsListScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A5C217DD0749EC709EED028 /* KnockRequestsListScreenViewModelProtocol.swift */; };
761EA50B2619307AB30891B8 /* PhishingDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB07F03461023BC39C730922 /* PhishingDetector.swift */; };
Expand Down Expand Up @@ -1025,6 +1028,7 @@
A14A9419105A1CD42F0511C4 /* UserIndicatorModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43005941B3A2C9671E23C85 /* UserIndicatorModalView.swift */; };
A17FAD2EBC53E17B5FD384DB /* InviteUsersScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */; };
A180F7B8048C3697453760F2 /* SessionDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A98F895BD60A4448E2A278C /* SessionDirectories.swift */; };
A1A16FEF565ED7972591988B /* AccountProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D0FBA1FE29833A915B77AE /* AccountProviderTests.swift */; };
A1BA8D6BABAFA9BAAEAA3FFD /* NotificationSettingsProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FDD775CFD72DD2D3C8A8390 /* NotificationSettingsProxyProtocol.swift */; };
A1DF0E1E526A981ED6D5DF44 /* UserIndicatorControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */; };
A2091F4B1332D9BF273B09D5 /* SpaceServiceProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF36FC3DE25B20B7FA91F1FD /* SpaceServiceProxyMock.swift */; };
Expand Down Expand Up @@ -1852,6 +1856,7 @@
127D1947BA9C6CA62E3D03EC /* DeclineAndBlockScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclineAndBlockScreen.swift; sourceTree = "<group>"; };
128501375217576AF0FE3E92 /* RoomAttachmentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomAttachmentPicker.swift; sourceTree = "<group>"; };
12B09A94C519227264A41208 /* RoomMembershipDetailsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembershipDetailsProxy.swift; sourceTree = "<group>"; };
12D0FBA1FE29833A915B77AE /* AccountProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountProviderTests.swift; sourceTree = "<group>"; };
1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = "<group>"; };
130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = "<group>"; };
136F80A613B55BDD071DCEA5 /* JoinRoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenModels.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2711,6 +2716,7 @@
A566F8F2C27B99E1FB80C69B /* JoinRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRule.swift; sourceTree = "<group>"; };
A5C45578F406698388B97C07 /* AuthenticationClassicAppAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationClassicAppAccountView.swift; sourceTree = "<group>"; };
A5DA892E8643240C7BC41900 /* RoomListActivityVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListActivityVisibility.swift; sourceTree = "<group>"; };
A67BCEFE35F8D00CADF108C9 /* AccountProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountProvider.swift; sourceTree = "<group>"; };
A6B19D10B102956066AF117B /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = "<group>"; };
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriverProtocol.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5167,6 +5173,7 @@
73CD9796729EB702B4DFA88C /* Sources */ = {
isa = PBXGroup;
children = (
12D0FBA1FE29833A915B77AE /* AccountProviderTests.swift */,
58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */,
C687844F60BFF532D49A994C /* AnalyticsTests.swift */,
E461B3C8BBBFCA400B268D14 /* AppRouteURLParserTests.swift */,
Expand Down Expand Up @@ -6822,6 +6829,7 @@
D64E20C9E4C486D5C764D7F3 /* Settings */ = {
isa = PBXGroup;
children = (
A67BCEFE35F8D00CADF108C9 /* AccountProvider.swift */,
FA2397174D0DC3918A7A8A7B /* AnalyticsConfiguration.swift */,
353024006CB726E9F9187B3A /* AnalyticsConsentState.swift */,
0FD22AEFFA20065494ED2333 /* AppAppearance.swift */,
Expand Down Expand Up @@ -8153,6 +8161,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
75C1AFACE6C0DEA18F16456C /* AccountProvider.swift in Sources */,
CA2BFC5504AD91A028CFA45B /* AnalyticsConfiguration.swift in Sources */,
4E9ED3E6E979C6C7FEC1D011 /* AnalyticsConsentState.swift in Sources */,
68C3AF257678F6E7BB238C3F /* AppAppearance.swift in Sources */,
Expand Down Expand Up @@ -8246,6 +8255,7 @@
buildActionMask = 2147483647;
files = (
C915347779B3C7FDD073A87A /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift in Sources */,
A1A16FEF565ED7972591988B /* AccountProviderTests.swift in Sources */,
A9A5801D5EE3D4D91F6DDADB /* AnalyticsSettingsScreenViewModelTests.swift in Sources */,
890F0D453FE388756479AC97 /* AnalyticsTests.swift in Sources */,
8478992479B296C45150208F /* AppLockScreenViewModelTests.swift in Sources */,
Expand Down Expand Up @@ -8438,6 +8448,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3A363617757E6838AC3D15FB /* AccountProvider.swift in Sources */,
C6AC34B731F4F853E9D15146 /* AnalyticsConfiguration.swift in Sources */,
585DCA0487A0A6F4E59EF5CA /* AnalyticsConsentState.swift in Sources */,
88887E40301FF0543ED5B59F /* AppAppearance.swift in Sources */,
Expand Down Expand Up @@ -8509,6 +8520,7 @@
70394ECD2DCC70741538620D /* AccessibilityIdentifiers.swift in Sources */,
28E8C44DD6E39BEB2A1599C8 /* AccessibilityTestsAppCoordinator.swift in Sources */,
2538350683F421AB620AA950 /* AccessibleLongPress.swift in Sources */,
03E9750B2F994A79FA1296A1 /* AccountProvider.swift in Sources */,
21215E17C9908F1EBF621BE0 /* ActiveCallTimelineItemView.swift in Sources */,
09E1A358EA4205480AEF4698 /* AdvancedSettingsScreen.swift in Sources */,
34ADC506C929D10B138EBA51 /* AdvancedSettingsScreenCoordinator.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion ElementX/Sources/AppHooks/Hooks/RemoteSettingsHook.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct DefaultRemoteSettingsHook: RemoteSettingsHookProtocol {
let wellKnown = try JSONDecoder().decode(ElementWellKnown.self, from: wellKnownData)
if wellKnown.enforceElementPro == true {
let serverName = client.server() ?? client.homeserver()
let displayableServerName = LoginHomeserver(address: serverName, loginMode: .unknown).address
let displayableServerName = AccountProvider.generic(serverName).serverNameOrBaseURL
return .failure(.elementProRequired(serverName: displayableServerName))
} else {
return .success(())
Expand Down
58 changes: 58 additions & 0 deletions ElementX/Sources/Application/Settings/AccountProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// Copyright 2026 Element Creations 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

nonisolated enum AccountProvider: Equatable {
/// An account provider that was input/chosen by the user.
case generic(String)
/// An account provider as provided by the app.
case managed(serverName: String, baseURL: URL, canUseServerName: Bool = true)

/// The account provider's string value, shown in the UI and used to build a `Client` with.
var serverNameOrBaseURL: String {
let serverNameOrBaseURL = switch self {
case .generic(let serverNameOrBaseURL):
serverNameOrBaseURL
case .managed(let serverName, let baseURL, let canUseServerName):
canUseServerName ? serverName : baseURL.absoluteString
}

return sanitize(serverNameOrBaseURL)
}

/// Sanitizes an address with the following rules:
/// - Trim any whitespace.
/// - Lowercase the address.
/// - Removes an https scheme (treating it as implicit).
/// - Remove any trailing slashes.
private func sanitize(_ serverNameOrBaseURL: String) -> String {
serverNameOrBaseURL
.trimmingCharacters(in: .whitespacesAndNewlines)
.lowercased()
.trimmingPrefix("https://") // Intentionally continue to show http:// as an indicator (and for history).
.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
}
}

// MARK: - Array

extension [AccountProvider] {
/// Whether the collection includes a provider matching the given server name or base URL.
func contains(serverName: String?, orBaseURL baseURL: URL?) -> Bool {
contains { accountProvider in
switch accountProvider {
case .generic(let serverNameOrBaseURL):
// Lower case potential user input to be safe. Full sanitisation is wrong given the scheme would be ignored.
serverNameOrBaseURL.lowercased() == serverName || serverNameOrBaseURL.lowercased() == baseURL?.absoluteString
case .managed(let providerServerName, let providerBaseURL, _):
// We can ignore canUseServerName here, it's about constructing a client rather than matching.
providerServerName == serverName || providerBaseURL == baseURL
}
}
}
}
8 changes: 4 additions & 4 deletions ElementX/Sources/Application/Settings/AppSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ final nonisolated class AppSettings: @unchecked Sendable {
// MARK: - Hooks

// swiftlint:disable:next function_parameter_count
func override(accountProviders: [String],
func override(accountProviders: [AccountProvider],
allowOtherAccountProviders: Bool,
hideBrandChrome: Bool,
pushGatewayBaseURL: URL,
Expand Down Expand Up @@ -141,7 +141,7 @@ final nonisolated class AppSettings: @unchecked Sendable {
///
/// Account provider is the friendly term for the server name. It should not contain an `https` prefix and should
/// match the last part of the user ID. For example `example.com` and not `https://matrix.example.com`.
private(set) var accountProviders = ["matrix.org"]
private(set) var accountProviders: [AccountProvider] = [.managed(serverName: "matrix.org", baseURL: "https://matrix-client.matrix.org")]
/// Whether or not the user is allowed to manually enter their own account provider or must select from one of `defaultAccountProviders`.
private(set) var allowOtherAccountProviders = true
/// Whether the components surrounding the app brand/logo should be hidden or not
Expand Down Expand Up @@ -186,8 +186,8 @@ final nonisolated class AppSettings: @unchecked Sendable {
@UserPreference(key: "previousServers", defaultValue: [])
var previousServers: [String]

var defaultServer: String {
previousServers.first ?? accountProviders[0]
var defaultAccountProvider: AccountProvider {
previousServers.first.map { .generic($0) } ?? accountProviders[0]
}

// MARK: - Security
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
private func userHasSignedIn(userSession: UserSessionProtocol) {
delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession)

let newServer = authenticationService.homeserver.value.address
homeserverHistoryManager.addServerToList(newServer)
let newAccountProvider = authenticationService.homeserver.value.accountProvider
homeserverHistoryManager.addServerToList(newAccountProvider.serverNameOrBaseURL)
}
}
4 changes: 2 additions & 2 deletions ElementX/Sources/Mocks/ClientFactoryMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ extension ClientFactoryMock {
convenience init(_ configuration: Configuration) {
self.init()

makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure = { address, _, _, _, _, _ in
makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure = { address, _, _, _, _, _ in
guard let client = configuration.homeserverClients[address] else {
throw ClientBuildError.ServerUnreachable(message: "Not a known homeserver.")
}
return client
}

makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksClosure = { address, _, _, _ in
makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksClosure = { address, _, _, _ in
guard let client = configuration.homeserverClients[address] else {
throw ClientBuildError.ServerUnreachable(message: "Not a known homeserver.")
}
Expand Down
Loading
Loading