mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Add an AccountProvider type and use it within the authentication flow. (#6103)
* Add an AccountProvider type and use it within AppSettings. * Use the AccountProvider type within the Authentication flow too. * Fix unit test compilation with Swift 6.4 * Make sure defaultAccountProvider takes allowOtherAccountProviders into account. * Document a caveat on using AuthenticationService.homeserver.value.accountProvider.
This commit is contained in:
@@ -43,6 +43,7 @@
|
|||||||
039FE1757084092146C0B616 /* ClientFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176A581FDC61737676782607 /* ClientFactoryProtocol.swift */; };
|
039FE1757084092146C0B616 /* ClientFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176A581FDC61737676782607 /* ClientFactoryProtocol.swift */; };
|
||||||
03BD83E8BDD23AE059802E0D /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */; };
|
03BD83E8BDD23AE059802E0D /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */; };
|
||||||
03CDCA6243F89B194E3FAD17 /* EncryptionAuthenticity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955336CBD5ED73C792D1F580 /* EncryptionAuthenticity.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 */; };
|
03F192C5C011AFA6068C9EAA /* SpaceServiceSDKMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B1E4D1F0A6FAAE64B54D76 /* SpaceServiceSDKMock.swift */; };
|
||||||
0437765FF480249486893CC7 /* ScreenTrackerViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196004E7695FBA292A7944AF /* ScreenTrackerViewModifier.swift */; };
|
0437765FF480249486893CC7 /* ScreenTrackerViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196004E7695FBA292A7944AF /* ScreenTrackerViewModifier.swift */; };
|
||||||
044DD8F80231BC30570F7965 /* UserDiscoveryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AAD845E53B0C8B5E0812C2 /* UserDiscoveryService.swift */; };
|
044DD8F80231BC30570F7965 /* UserDiscoveryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AAD845E53B0C8B5E0812C2 /* UserDiscoveryService.swift */; };
|
||||||
@@ -370,6 +371,7 @@
|
|||||||
3A0D1A201E2CD209A04CE89B /* SearchScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD63A653BCA2C8112654B19 /* SearchScreenModels.swift */; };
|
3A0D1A201E2CD209A04CE89B /* SearchScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD63A653BCA2C8112654B19 /* SearchScreenModels.swift */; };
|
||||||
3A164187907DA43B7858F9EC /* CompletionSuggestionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EA0312A6262484AA393AC9 /* CompletionSuggestionServiceTests.swift */; };
|
3A164187907DA43B7858F9EC /* CompletionSuggestionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EA0312A6262484AA393AC9 /* CompletionSuggestionServiceTests.swift */; };
|
||||||
3A2640C52505BAB3D983A92A /* RemotePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69A05E85E4872C3221C5C287 /* RemotePreference.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 */; };
|
3A64A93A651A3CB8774ADE8E /* SwiftOGG in Frameworks */ = {isa = PBXBuildFile; productRef = 3FE40E79C36E7903121E6E3B /* SwiftOGG */; };
|
||||||
3A68752BB9D51B8713C0FC2C /* AuthenticationStartScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A768CA51A59B8A5D8C8FD599 /* AuthenticationStartScreen.swift */; };
|
3A68752BB9D51B8713C0FC2C /* AuthenticationStartScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A768CA51A59B8A5D8C8FD599 /* AuthenticationStartScreen.swift */; };
|
||||||
3AA9E878FDCFF85664AC071F /* ComposerDraftService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D70253004A5AEC9C73D6A4F /* ComposerDraftService.swift */; };
|
3AA9E878FDCFF85664AC071F /* ComposerDraftService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D70253004A5AEC9C73D6A4F /* ComposerDraftService.swift */; };
|
||||||
@@ -736,6 +738,7 @@
|
|||||||
756EA0D663261889EF64E6D4 /* VoiceMessageRecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E9CBF577B9711CFBB4FA40D /* VoiceMessageRecordingView.swift */; };
|
756EA0D663261889EF64E6D4 /* VoiceMessageRecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E9CBF577B9711CFBB4FA40D /* VoiceMessageRecordingView.swift */; };
|
||||||
7573D682F089205F7F1D96CF /* SessionDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2067FF58B4996323EB40C /* SessionDirectories.swift */; };
|
7573D682F089205F7F1D96CF /* SessionDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2067FF58B4996323EB40C /* SessionDirectories.swift */; };
|
||||||
757862045774A0F458357E19 /* RoomThreadListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544B9262E1BC6F489C03FFFA /* RoomThreadListScreen.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 */; };
|
75E55A763FE8BA64FB8513E4 /* TimelineStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F968FDB68F9D4CB9010FED70 /* TimelineStateTests.swift */; };
|
||||||
75ED4B73983228BB6922CE3C /* KnockRequestsListScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A5C217DD0749EC709EED028 /* KnockRequestsListScreenViewModelProtocol.swift */; };
|
75ED4B73983228BB6922CE3C /* KnockRequestsListScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A5C217DD0749EC709EED028 /* KnockRequestsListScreenViewModelProtocol.swift */; };
|
||||||
761EA50B2619307AB30891B8 /* PhishingDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB07F03461023BC39C730922 /* PhishingDetector.swift */; };
|
761EA50B2619307AB30891B8 /* PhishingDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB07F03461023BC39C730922 /* PhishingDetector.swift */; };
|
||||||
@@ -1027,6 +1030,7 @@
|
|||||||
A14A9419105A1CD42F0511C4 /* UserIndicatorModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43005941B3A2C9671E23C85 /* UserIndicatorModalView.swift */; };
|
A14A9419105A1CD42F0511C4 /* UserIndicatorModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43005941B3A2C9671E23C85 /* UserIndicatorModalView.swift */; };
|
||||||
A17FAD2EBC53E17B5FD384DB /* InviteUsersScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */; };
|
A17FAD2EBC53E17B5FD384DB /* InviteUsersScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */; };
|
||||||
A180F7B8048C3697453760F2 /* SessionDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A98F895BD60A4448E2A278C /* SessionDirectories.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 */; };
|
A1BA8D6BABAFA9BAAEAA3FFD /* NotificationSettingsProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FDD775CFD72DD2D3C8A8390 /* NotificationSettingsProxyProtocol.swift */; };
|
||||||
A1DF0E1E526A981ED6D5DF44 /* UserIndicatorControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */; };
|
A1DF0E1E526A981ED6D5DF44 /* UserIndicatorControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */; };
|
||||||
A2091F4B1332D9BF273B09D5 /* SpaceServiceProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF36FC3DE25B20B7FA91F1FD /* SpaceServiceProxyMock.swift */; };
|
A2091F4B1332D9BF273B09D5 /* SpaceServiceProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF36FC3DE25B20B7FA91F1FD /* SpaceServiceProxyMock.swift */; };
|
||||||
@@ -1350,6 +1354,7 @@
|
|||||||
D69CCEC785E6C819551B402F /* SearchScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5413213B56FB76C1071E2BFB /* SearchScreenViewModelTests.swift */; };
|
D69CCEC785E6C819551B402F /* SearchScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5413213B56FB76C1071E2BFB /* SearchScreenViewModelTests.swift */; };
|
||||||
D6DE764B17FB4A9A12C33BF4 /* MessageComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1DF3FFFE5ED2B8133F43A7 /* MessageComposer.swift */; };
|
D6DE764B17FB4A9A12C33BF4 /* MessageComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1DF3FFFE5ED2B8133F43A7 /* MessageComposer.swift */; };
|
||||||
D75C591C44C2510FDFA9C3D8 /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = AF574A0FC6F3A83D3BFFC9B3 /* Macros */; };
|
D75C591C44C2510FDFA9C3D8 /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = AF574A0FC6F3A83D3BFFC9B3 /* Macros */; };
|
||||||
|
D7A26828D8715A17A1D52E66 /* AppSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11D53EE8E666C25F97B4369 /* AppSettingsTests.swift */; };
|
||||||
D7CC123DC5138C3BF1F9999D /* MapLibreAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50642DAE43672897C0BBAE2 /* MapLibreAnnotation.swift */; };
|
D7CC123DC5138C3BF1F9999D /* MapLibreAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50642DAE43672897C0BBAE2 /* MapLibreAnnotation.swift */; };
|
||||||
D7CDBAE82782BD0529DECB5F /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */; };
|
D7CDBAE82782BD0529DECB5F /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */; };
|
||||||
D820B3C223E4C2E77BB2A2BF /* ElementCallServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EA2AFF6EB59FE25234D29F3 /* ElementCallServiceTests.swift */; };
|
D820B3C223E4C2E77BB2A2BF /* ElementCallServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EA2AFF6EB59FE25234D29F3 /* ElementCallServiceTests.swift */; };
|
||||||
@@ -1856,6 +1861,7 @@
|
|||||||
127D1947BA9C6CA62E3D03EC /* DeclineAndBlockScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclineAndBlockScreen.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
136F80A613B55BDD071DCEA5 /* JoinRoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenModels.swift; sourceTree = "<group>"; };
|
||||||
@@ -2691,6 +2697,7 @@
|
|||||||
A0E7B059E84E7E374D3322A2 /* SpaceRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceRoomCell.swift; sourceTree = "<group>"; };
|
A0E7B059E84E7E374D3322A2 /* SpaceRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceRoomCell.swift; sourceTree = "<group>"; };
|
||||||
A103580EBA06155B1343EF16 /* HomeScreenKnockedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenKnockedCell.swift; sourceTree = "<group>"; };
|
A103580EBA06155B1343EF16 /* HomeScreenKnockedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenKnockedCell.swift; sourceTree = "<group>"; };
|
||||||
A1087DCC491CD4C027173DDA /* EmojiPickerScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenViewModelTests.swift; sourceTree = "<group>"; };
|
A1087DCC491CD4C027173DDA /* EmojiPickerScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
|
A11D53EE8E666C25F97B4369 /* AppSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsTests.swift; sourceTree = "<group>"; };
|
||||||
A12D3B1BCF920880CA8BBB6B /* UserIndicatorControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerProtocol.swift; sourceTree = "<group>"; };
|
A12D3B1BCF920880CA8BBB6B /* UserIndicatorControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerProtocol.swift; sourceTree = "<group>"; };
|
||||||
A130A2251A15A7AACC84FD37 /* RoomPollsHistoryScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
A130A2251A15A7AACC84FD37 /* RoomPollsHistoryScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
A14FD296A75F5F5637EDC365 /* RoomThreadListScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomThreadListScreenCoordinator.swift; sourceTree = "<group>"; };
|
A14FD296A75F5F5637EDC365 /* RoomThreadListScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomThreadListScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
@@ -2716,6 +2723,7 @@
|
|||||||
A566F8F2C27B99E1FB80C69B /* JoinRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRule.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriverProtocol.swift; sourceTree = "<group>"; };
|
||||||
@@ -5178,9 +5186,11 @@
|
|||||||
73CD9796729EB702B4DFA88C /* Sources */ = {
|
73CD9796729EB702B4DFA88C /* Sources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
12D0FBA1FE29833A915B77AE /* AccountProviderTests.swift */,
|
||||||
58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */,
|
58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */,
|
||||||
C687844F60BFF532D49A994C /* AnalyticsTests.swift */,
|
C687844F60BFF532D49A994C /* AnalyticsTests.swift */,
|
||||||
E461B3C8BBBFCA400B268D14 /* AppRouteURLParserTests.swift */,
|
E461B3C8BBBFCA400B268D14 /* AppRouteURLParserTests.swift */,
|
||||||
|
A11D53EE8E666C25F97B4369 /* AppSettingsTests.swift */,
|
||||||
893777A4997BBDB68079D4F5 /* ArrayTests.swift */,
|
893777A4997BBDB68079D4F5 /* ArrayTests.swift */,
|
||||||
AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */,
|
AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */,
|
||||||
37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */,
|
37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */,
|
||||||
@@ -6833,6 +6843,7 @@
|
|||||||
D64E20C9E4C486D5C764D7F3 /* Settings */ = {
|
D64E20C9E4C486D5C764D7F3 /* Settings */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A67BCEFE35F8D00CADF108C9 /* AccountProvider.swift */,
|
||||||
FA2397174D0DC3918A7A8A7B /* AnalyticsConfiguration.swift */,
|
FA2397174D0DC3918A7A8A7B /* AnalyticsConfiguration.swift */,
|
||||||
353024006CB726E9F9187B3A /* AnalyticsConsentState.swift */,
|
353024006CB726E9F9187B3A /* AnalyticsConsentState.swift */,
|
||||||
0FD22AEFFA20065494ED2333 /* AppAppearance.swift */,
|
0FD22AEFFA20065494ED2333 /* AppAppearance.swift */,
|
||||||
@@ -8165,6 +8176,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
75C1AFACE6C0DEA18F16456C /* AccountProvider.swift in Sources */,
|
||||||
CA2BFC5504AD91A028CFA45B /* AnalyticsConfiguration.swift in Sources */,
|
CA2BFC5504AD91A028CFA45B /* AnalyticsConfiguration.swift in Sources */,
|
||||||
4E9ED3E6E979C6C7FEC1D011 /* AnalyticsConsentState.swift in Sources */,
|
4E9ED3E6E979C6C7FEC1D011 /* AnalyticsConsentState.swift in Sources */,
|
||||||
68C3AF257678F6E7BB238C3F /* AppAppearance.swift in Sources */,
|
68C3AF257678F6E7BB238C3F /* AppAppearance.swift in Sources */,
|
||||||
@@ -8258,6 +8270,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
C915347779B3C7FDD073A87A /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift in Sources */,
|
C915347779B3C7FDD073A87A /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift in Sources */,
|
||||||
|
A1A16FEF565ED7972591988B /* AccountProviderTests.swift in Sources */,
|
||||||
A9A5801D5EE3D4D91F6DDADB /* AnalyticsSettingsScreenViewModelTests.swift in Sources */,
|
A9A5801D5EE3D4D91F6DDADB /* AnalyticsSettingsScreenViewModelTests.swift in Sources */,
|
||||||
890F0D453FE388756479AC97 /* AnalyticsTests.swift in Sources */,
|
890F0D453FE388756479AC97 /* AnalyticsTests.swift in Sources */,
|
||||||
8478992479B296C45150208F /* AppLockScreenViewModelTests.swift in Sources */,
|
8478992479B296C45150208F /* AppLockScreenViewModelTests.swift in Sources */,
|
||||||
@@ -8267,6 +8280,7 @@
|
|||||||
25C4C1100B6EA79F5CC7CBB5 /* AppLockSetupPINScreenViewModelTests.swift in Sources */,
|
25C4C1100B6EA79F5CC7CBB5 /* AppLockSetupPINScreenViewModelTests.swift in Sources */,
|
||||||
0EEC614342F823E5BF966C2C /* AppLockTimerTests.swift in Sources */,
|
0EEC614342F823E5BF966C2C /* AppLockTimerTests.swift in Sources */,
|
||||||
EA78A7512AFB1E5451744EB1 /* AppRouteURLParserTests.swift in Sources */,
|
EA78A7512AFB1E5451744EB1 /* AppRouteURLParserTests.swift in Sources */,
|
||||||
|
D7A26828D8715A17A1D52E66 /* AppSettingsTests.swift in Sources */,
|
||||||
3EC698F80DDEEFA273857841 /* ArrayTests.swift in Sources */,
|
3EC698F80DDEEFA273857841 /* ArrayTests.swift in Sources */,
|
||||||
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */,
|
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */,
|
||||||
5100F53E6884A15F9BA07CC3 /* AttributedStringTests.swift in Sources */,
|
5100F53E6884A15F9BA07CC3 /* AttributedStringTests.swift in Sources */,
|
||||||
@@ -8450,6 +8464,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
3A363617757E6838AC3D15FB /* AccountProvider.swift in Sources */,
|
||||||
C6AC34B731F4F853E9D15146 /* AnalyticsConfiguration.swift in Sources */,
|
C6AC34B731F4F853E9D15146 /* AnalyticsConfiguration.swift in Sources */,
|
||||||
585DCA0487A0A6F4E59EF5CA /* AnalyticsConsentState.swift in Sources */,
|
585DCA0487A0A6F4E59EF5CA /* AnalyticsConsentState.swift in Sources */,
|
||||||
88887E40301FF0543ED5B59F /* AppAppearance.swift in Sources */,
|
88887E40301FF0543ED5B59F /* AppAppearance.swift in Sources */,
|
||||||
@@ -8521,6 +8536,7 @@
|
|||||||
70394ECD2DCC70741538620D /* AccessibilityIdentifiers.swift in Sources */,
|
70394ECD2DCC70741538620D /* AccessibilityIdentifiers.swift in Sources */,
|
||||||
28E8C44DD6E39BEB2A1599C8 /* AccessibilityTestsAppCoordinator.swift in Sources */,
|
28E8C44DD6E39BEB2A1599C8 /* AccessibilityTestsAppCoordinator.swift in Sources */,
|
||||||
2538350683F421AB620AA950 /* AccessibleLongPress.swift in Sources */,
|
2538350683F421AB620AA950 /* AccessibleLongPress.swift in Sources */,
|
||||||
|
03E9750B2F994A79FA1296A1 /* AccountProvider.swift in Sources */,
|
||||||
21215E17C9908F1EBF621BE0 /* ActiveCallTimelineItemView.swift in Sources */,
|
21215E17C9908F1EBF621BE0 /* ActiveCallTimelineItemView.swift in Sources */,
|
||||||
09E1A358EA4205480AEF4698 /* AdvancedSettingsScreen.swift in Sources */,
|
09E1A358EA4205480AEF4698 /* AdvancedSettingsScreen.swift in Sources */,
|
||||||
34ADC506C929D10B138EBA51 /* AdvancedSettingsScreenCoordinator.swift in Sources */,
|
34ADC506C929D10B138EBA51 /* AdvancedSettingsScreenCoordinator.swift in Sources */,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ struct DefaultRemoteSettingsHook: RemoteSettingsHookProtocol {
|
|||||||
let wellKnown = try JSONDecoder().decode(ElementWellKnown.self, from: wellKnownData)
|
let wellKnown = try JSONDecoder().decode(ElementWellKnown.self, from: wellKnownData)
|
||||||
if wellKnown.enforceElementPro == true {
|
if wellKnown.enforceElementPro == true {
|
||||||
let serverName = client.server() ?? client.homeserver()
|
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))
|
return .failure(.elementProRequired(serverName: displayableServerName))
|
||||||
} else {
|
} else {
|
||||||
return .success(())
|
return .success(())
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isGeneric: Bool {
|
||||||
|
switch self {
|
||||||
|
case .generic: true
|
||||||
|
case .managed: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,7 +78,7 @@ final nonisolated class AppSettings: @unchecked Sendable {
|
|||||||
// MARK: - Hooks
|
// MARK: - Hooks
|
||||||
|
|
||||||
// swiftlint:disable:next function_parameter_count
|
// swiftlint:disable:next function_parameter_count
|
||||||
func override(accountProviders: [String],
|
func override(accountProviders: [AccountProvider],
|
||||||
allowOtherAccountProviders: Bool,
|
allowOtherAccountProviders: Bool,
|
||||||
hideBrandChrome: Bool,
|
hideBrandChrome: Bool,
|
||||||
pushGatewayBaseURL: URL,
|
pushGatewayBaseURL: URL,
|
||||||
@@ -143,7 +143,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
|
/// 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`.
|
/// 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`.
|
/// 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
|
private(set) var allowOtherAccountProviders = true
|
||||||
/// Whether the components surrounding the app brand/logo should be hidden or not
|
/// Whether the components surrounding the app brand/logo should be hidden or not
|
||||||
@@ -188,8 +188,12 @@ final nonisolated class AppSettings: @unchecked Sendable {
|
|||||||
@UserPreference(key: "previousServers", defaultValue: [])
|
@UserPreference(key: "previousServers", defaultValue: [])
|
||||||
var previousServers: [String]
|
var previousServers: [String]
|
||||||
|
|
||||||
var defaultServer: String {
|
var defaultAccountProvider: AccountProvider {
|
||||||
previousServers.first ?? accountProviders[0]
|
if allowOtherAccountProviders {
|
||||||
|
previousServers.first.map { .generic($0) } ?? accountProviders[0]
|
||||||
|
} else {
|
||||||
|
accountProviders[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Security
|
// MARK: - Security
|
||||||
|
|||||||
@@ -465,7 +465,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
private func userHasSignedIn(userSession: UserSessionProtocol) {
|
private func userHasSignedIn(userSession: UserSessionProtocol) {
|
||||||
delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession)
|
delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession)
|
||||||
|
|
||||||
let newServer = authenticationService.homeserver.value.address
|
let newAccountProvider = authenticationService.homeserver.value.accountProvider
|
||||||
homeserverHistoryManager.addServerToList(newServer)
|
homeserverHistoryManager.addServerToList(newAccountProvider.serverNameOrBaseURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,14 +46,14 @@ extension ClientFactoryMock {
|
|||||||
convenience init(_ configuration: Configuration) {
|
convenience init(_ configuration: Configuration) {
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure = { address, _, _, _, _, _ in
|
makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure = { address, _, _, _, _, _ in
|
||||||
guard let client = configuration.homeserverClients[address] else {
|
guard let client = configuration.homeserverClients[address] else {
|
||||||
throw ClientBuildError.ServerUnreachable(message: "Not a known homeserver.")
|
throw ClientBuildError.ServerUnreachable(message: "Not a known homeserver.")
|
||||||
}
|
}
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksClosure = { address, _, _, _ in
|
makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksClosure = { address, _, _, _ in
|
||||||
guard let client = configuration.homeserverClients[address] else {
|
guard let client = configuration.homeserverClients[address] else {
|
||||||
throw ClientBuildError.ServerUnreachable(message: "Not a known homeserver.")
|
throw ClientBuildError.ServerUnreachable(message: "Not a known homeserver.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2080,94 +2080,94 @@ nonisolated class ClientFactoryMock: ClientFactoryProtocol, @unchecked Sendable
|
|||||||
|
|
||||||
//MARK: - makeAuthenticationClient
|
//MARK: - makeAuthenticationClient
|
||||||
|
|
||||||
nonisolated(unsafe) var makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksThrowableError: Error?
|
nonisolated(unsafe) var makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksThrowableError: Error?
|
||||||
private let makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCountLock = NSLock()
|
private let makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCountLock = NSLock()
|
||||||
private nonisolated(unsafe) var makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = 0
|
private nonisolated(unsafe) var makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = 0
|
||||||
var makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount: Int {
|
var makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount: Int {
|
||||||
get { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCountLock.withLock { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount } }
|
get { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCountLock.withLock { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount } }
|
||||||
set { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCountLock.withLock { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = newValue } }
|
set { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCountLock.withLock { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = newValue } }
|
||||||
}
|
}
|
||||||
var makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCalled: Bool {
|
var makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCalled: Bool {
|
||||||
return makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount > 0
|
return makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount > 0
|
||||||
}
|
}
|
||||||
private let makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArgumentsLock = NSLock()
|
private let makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArgumentsLock = NSLock()
|
||||||
private nonisolated(unsafe) var makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedArguments: (homeserverAddress: String, sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)?
|
private nonisolated(unsafe) var makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedArguments: (serverNameOrBaseURL: String, sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)?
|
||||||
var makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments: (homeserverAddress: String, sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)? {
|
var makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments: (serverNameOrBaseURL: String, sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)? {
|
||||||
get { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArgumentsLock.withLock { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedArguments } }
|
get { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArgumentsLock.withLock { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedArguments } }
|
||||||
set { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArgumentsLock.withLock { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedArguments = newValue } }
|
set { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArgumentsLock.withLock { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedArguments = newValue } }
|
||||||
}
|
}
|
||||||
private let makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock = NSLock()
|
private let makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock = NSLock()
|
||||||
private nonisolated(unsafe) var makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations: [(homeserverAddress: String, sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)] = []
|
private nonisolated(unsafe) var makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations: [(serverNameOrBaseURL: String, sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)] = []
|
||||||
var makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocations: [(homeserverAddress: String, sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)] {
|
var makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocations: [(serverNameOrBaseURL: String, sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)] {
|
||||||
get { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock.withLock { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations } }
|
get { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock.withLock { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations } }
|
||||||
set { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock.withLock { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations = newValue } }
|
set { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock.withLock { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations = newValue } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private let makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValueLock = NSLock()
|
private let makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValueLock = NSLock()
|
||||||
private nonisolated(unsafe) var makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue: ClientProtocol!
|
private nonisolated(unsafe) var makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue: ClientProtocol!
|
||||||
var makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValue: ClientProtocol! {
|
var makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValue: ClientProtocol! {
|
||||||
get { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValueLock.withLock { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue } }
|
get { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValueLock.withLock { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue } }
|
||||||
set { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValueLock.withLock { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue = newValue } }
|
set { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValueLock.withLock { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue = newValue } }
|
||||||
}
|
}
|
||||||
nonisolated(unsafe) var makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure: ((String, SessionDirectories, String, ClientSessionDelegate, AppSettings, AppHooks) async throws -> ClientProtocol)?
|
nonisolated(unsafe) var makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure: ((String, SessionDirectories, String, ClientSessionDelegate, AppSettings, AppHooks) async throws -> ClientProtocol)?
|
||||||
|
|
||||||
@concurrent func makeAuthenticationClient(homeserverAddress: String, sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks) async throws -> ClientProtocol {
|
@concurrent func makeAuthenticationClient(serverNameOrBaseURL: String, sessionDirectories: SessionDirectories, passphrase: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks) async throws -> ClientProtocol {
|
||||||
if let error = makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksThrowableError {
|
if let error = makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksThrowableError {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCountLock.withLock { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount += 1 }
|
makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCountLock.withLock { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount += 1 }
|
||||||
makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments = (homeserverAddress: homeserverAddress, sessionDirectories: sessionDirectories, passphrase: passphrase, clientSessionDelegate: clientSessionDelegate, appSettings: appSettings, appHooks: appHooks)
|
makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments = (serverNameOrBaseURL: serverNameOrBaseURL, sessionDirectories: sessionDirectories, passphrase: passphrase, clientSessionDelegate: clientSessionDelegate, appSettings: appSettings, appHooks: appHooks)
|
||||||
makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock.withLock { makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations.append((homeserverAddress: homeserverAddress, sessionDirectories: sessionDirectories, passphrase: passphrase, clientSessionDelegate: clientSessionDelegate, appSettings: appSettings, appHooks: appHooks)) }
|
makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock.withLock { makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations.append((serverNameOrBaseURL: serverNameOrBaseURL, sessionDirectories: sessionDirectories, passphrase: passphrase, clientSessionDelegate: clientSessionDelegate, appSettings: appSettings, appHooks: appHooks)) }
|
||||||
if let makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure = makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure {
|
if let makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure = makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure {
|
||||||
return try await makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure(homeserverAddress, sessionDirectories, passphrase, clientSessionDelegate, appSettings, appHooks)
|
return try await makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksClosure(serverNameOrBaseURL, sessionDirectories, passphrase, clientSessionDelegate, appSettings, appHooks)
|
||||||
} else {
|
} else {
|
||||||
return makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValue
|
return makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//MARK: - makeInMemoryClient
|
//MARK: - makeInMemoryClient
|
||||||
|
|
||||||
nonisolated(unsafe) var makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksThrowableError: Error?
|
nonisolated(unsafe) var makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksThrowableError: Error?
|
||||||
private let makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksCallsCountLock = NSLock()
|
private let makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksCallsCountLock = NSLock()
|
||||||
private nonisolated(unsafe) var makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = 0
|
private nonisolated(unsafe) var makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = 0
|
||||||
var makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksCallsCount: Int {
|
var makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksCallsCount: Int {
|
||||||
get { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksCallsCountLock.withLock { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount } }
|
get { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksCallsCountLock.withLock { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount } }
|
||||||
set { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksCallsCountLock.withLock { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = newValue } }
|
set { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksCallsCountLock.withLock { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount = newValue } }
|
||||||
}
|
}
|
||||||
var makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksCalled: Bool {
|
var makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksCalled: Bool {
|
||||||
return makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksCallsCount > 0
|
return makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksCallsCount > 0
|
||||||
}
|
}
|
||||||
private let makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReceivedArgumentsLock = NSLock()
|
private let makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReceivedArgumentsLock = NSLock()
|
||||||
private nonisolated(unsafe) var makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedArguments: (homeserverAddress: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)?
|
private nonisolated(unsafe) var makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedArguments: (serverNameOrBaseURL: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)?
|
||||||
var makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReceivedArguments: (homeserverAddress: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)? {
|
var makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReceivedArguments: (serverNameOrBaseURL: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)? {
|
||||||
get { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReceivedArgumentsLock.withLock { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedArguments } }
|
get { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReceivedArgumentsLock.withLock { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedArguments } }
|
||||||
set { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReceivedArgumentsLock.withLock { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedArguments = newValue } }
|
set { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReceivedArgumentsLock.withLock { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedArguments = newValue } }
|
||||||
}
|
}
|
||||||
private let makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock = NSLock()
|
private let makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock = NSLock()
|
||||||
private nonisolated(unsafe) var makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations: [(homeserverAddress: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)] = []
|
private nonisolated(unsafe) var makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations: [(serverNameOrBaseURL: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)] = []
|
||||||
var makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReceivedInvocations: [(homeserverAddress: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)] {
|
var makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReceivedInvocations: [(serverNameOrBaseURL: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks)] {
|
||||||
get { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock.withLock { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations } }
|
get { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock.withLock { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations } }
|
||||||
set { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock.withLock { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations = newValue } }
|
set { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock.withLock { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations = newValue } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private let makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReturnValueLock = NSLock()
|
private let makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReturnValueLock = NSLock()
|
||||||
private nonisolated(unsafe) var makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue: ClientProtocol!
|
private nonisolated(unsafe) var makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue: ClientProtocol!
|
||||||
var makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReturnValue: ClientProtocol! {
|
var makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReturnValue: ClientProtocol! {
|
||||||
get { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReturnValueLock.withLock { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue } }
|
get { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReturnValueLock.withLock { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue } }
|
||||||
set { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReturnValueLock.withLock { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue = newValue } }
|
set { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReturnValueLock.withLock { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingReturnValue = newValue } }
|
||||||
}
|
}
|
||||||
nonisolated(unsafe) var makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksClosure: ((String, ClientSessionDelegate, AppSettings, AppHooks) async throws -> ClientProtocol)?
|
nonisolated(unsafe) var makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksClosure: ((String, ClientSessionDelegate, AppSettings, AppHooks) async throws -> ClientProtocol)?
|
||||||
|
|
||||||
@concurrent func makeInMemoryClient(homeserverAddress: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks) async throws -> ClientProtocol {
|
@concurrent func makeInMemoryClient(serverNameOrBaseURL: String, clientSessionDelegate: ClientSessionDelegate, appSettings: AppSettings, appHooks: AppHooks) async throws -> ClientProtocol {
|
||||||
if let error = makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksThrowableError {
|
if let error = makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksThrowableError {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksCallsCountLock.withLock { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount += 1 }
|
makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksCallsCountLock.withLock { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingCallsCount += 1 }
|
||||||
makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReceivedArguments = (homeserverAddress: homeserverAddress, clientSessionDelegate: clientSessionDelegate, appSettings: appSettings, appHooks: appHooks)
|
makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReceivedArguments = (serverNameOrBaseURL: serverNameOrBaseURL, clientSessionDelegate: clientSessionDelegate, appSettings: appSettings, appHooks: appHooks)
|
||||||
makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock.withLock { makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations.append((homeserverAddress: homeserverAddress, clientSessionDelegate: clientSessionDelegate, appSettings: appSettings, appHooks: appHooks)) }
|
makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReceivedInvocationsLock.withLock { makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksUnderlyingReceivedInvocations.append((serverNameOrBaseURL: serverNameOrBaseURL, clientSessionDelegate: clientSessionDelegate, appSettings: appSettings, appHooks: appHooks)) }
|
||||||
if let makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksClosure = makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksClosure {
|
if let makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksClosure = makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksClosure {
|
||||||
return try await makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksClosure(homeserverAddress, clientSessionDelegate, appSettings, appHooks)
|
return try await makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksClosure(serverNameOrBaseURL, clientSessionDelegate, appSettings, appHooks)
|
||||||
} else {
|
} else {
|
||||||
return makeInMemoryClientHomeserverAddressClientSessionDelegateAppSettingsAppHooksReturnValue
|
return makeInMemoryClientServerNameOrBaseURLClientSessionDelegateAppSettingsAppHooksReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//MARK: - makeAppClient
|
//MARK: - makeAppClient
|
||||||
|
|||||||
@@ -10,35 +10,10 @@ import Foundation
|
|||||||
|
|
||||||
/// Information about a homeserver that is ready for display in the authentication flow.
|
/// Information about a homeserver that is ready for display in the authentication flow.
|
||||||
struct LoginHomeserver: Equatable {
|
struct LoginHomeserver: Equatable {
|
||||||
/// The homeserver string to be shown to the user.
|
/// The account provider who operates the homeserver.
|
||||||
let address: String
|
let accountProvider: AccountProvider
|
||||||
/// The types login supported by the homeserver.
|
/// The types login supported by the homeserver.
|
||||||
var loginMode: LoginMode
|
var loginMode: LoginMode
|
||||||
|
|
||||||
/// Creates a new homeserver value.
|
|
||||||
init(address: String, loginMode: LoginMode) {
|
|
||||||
let address = Self.sanitized(address).components(separatedBy: "://").last ?? address
|
|
||||||
|
|
||||||
self.address = address
|
|
||||||
self.loginMode = loginMode
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sanitizes a user entered homeserver address with the following rules
|
|
||||||
/// - Trim any whitespace.
|
|
||||||
/// - Lowercase the address.
|
|
||||||
/// - Ensure the address contains a scheme, otherwise make it `https`.
|
|
||||||
/// - Remove any trailing slashes.
|
|
||||||
static func sanitized(_ address: String) -> String {
|
|
||||||
var address = address.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
||||||
|
|
||||||
if !address.contains("://") {
|
|
||||||
address = "https://\(address)"
|
|
||||||
}
|
|
||||||
|
|
||||||
address = address.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
|
||||||
|
|
||||||
return address
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Mocks
|
// MARK: - Mocks
|
||||||
@@ -46,21 +21,21 @@ struct LoginHomeserver: Equatable {
|
|||||||
extension LoginHomeserver {
|
extension LoginHomeserver {
|
||||||
/// A mock homeserver that is configured just like matrix.org.
|
/// A mock homeserver that is configured just like matrix.org.
|
||||||
static var mockMatrixDotOrg: LoginHomeserver {
|
static var mockMatrixDotOrg: LoginHomeserver {
|
||||||
LoginHomeserver(address: "matrix.org", loginMode: .oAuth(supportsCreatePrompt: true))
|
LoginHomeserver(accountProvider: .generic("matrix.org"), loginMode: .oAuth(supportsCreatePrompt: true))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mock homeserver that supports login and registration via a password but has no OAuth support.
|
/// A mock homeserver that supports login and registration via a password but has no OAuth support.
|
||||||
static var mockBasicServer: LoginHomeserver {
|
static var mockBasicServer: LoginHomeserver {
|
||||||
LoginHomeserver(address: "example.com", loginMode: .password)
|
LoginHomeserver(accountProvider: .generic("example.com"), loginMode: .password)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mock homeserver that supports only supports authentication via OAuth.
|
/// A mock homeserver that supports only supports authentication via OAuth.
|
||||||
static var mockOAuth: LoginHomeserver {
|
static var mockOAuth: LoginHomeserver {
|
||||||
LoginHomeserver(address: "company.com", loginMode: .oAuth(supportsCreatePrompt: false))
|
LoginHomeserver(accountProvider: .generic("company.com"), loginMode: .oAuth(supportsCreatePrompt: false))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mock homeserver that only with no supported login flows.
|
/// A mock homeserver that only with no supported login flows.
|
||||||
static var mockUnsupported: LoginHomeserver {
|
static var mockUnsupported: LoginHomeserver {
|
||||||
LoginHomeserver(address: "server.net", loginMode: .unsupported)
|
LoginHomeserver(accountProvider: .generic("server.net"), loginMode: .unsupported)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ struct LoginScreen: View {
|
|||||||
BigIcon(icon: \.lockSolid)
|
BigIcon(icon: \.lockSolid)
|
||||||
.padding(.bottom, 8)
|
.padding(.bottom, 8)
|
||||||
|
|
||||||
Text(L10n.screenLoginTitleWithHomeserver(context.viewState.homeserver.address))
|
Text(L10n.screenLoginTitleWithHomeserver(context.viewState.homeserver.accountProvider.serverNameOrBaseURL))
|
||||||
.font(.compound.headingMDBold)
|
.font(.compound.headingMDBold)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.foregroundColor(.compound.textPrimary)
|
.foregroundColor(.compound.textPrimary)
|
||||||
@@ -127,7 +127,7 @@ struct LoginScreen: View {
|
|||||||
struct LoginScreen_Previews: PreviewProvider, TestablePreview {
|
struct LoginScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let viewModel = makeViewModel()
|
static let viewModel = makeViewModel()
|
||||||
static let credentialsViewModel = makeViewModel(withCredentials: true)
|
static let credentialsViewModel = makeViewModel(withCredentials: true)
|
||||||
static let unconfiguredViewModel = makeViewModel(homeserverAddress: "somethingtofailconfiguration")
|
static let unconfiguredViewModel = makeViewModel(serverNameOrBaseURL: "somethingtofailconfiguration")
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ElementNavigationStack {
|
ElementNavigationStack {
|
||||||
@@ -148,10 +148,10 @@ struct LoginScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
.previewDisplayName("Unsupported")
|
.previewDisplayName("Unsupported")
|
||||||
}
|
}
|
||||||
|
|
||||||
static func makeViewModel(homeserverAddress: String = "example.com", withCredentials: Bool = false) -> LoginScreenViewModel {
|
static func makeViewModel(serverNameOrBaseURL: String = "example.com", withCredentials: Bool = false) -> LoginScreenViewModel {
|
||||||
let authenticationService = AuthenticationService.mock
|
let authenticationService = AuthenticationService.mock
|
||||||
|
|
||||||
Task { await authenticationService.configure(for: homeserverAddress, flow: .login) }
|
Task { await authenticationService.configure(for: serverNameOrBaseURL, flow: .login) }
|
||||||
|
|
||||||
let viewModel = LoginScreenViewModel(authenticationService: authenticationService,
|
let viewModel = LoginScreenViewModel(authenticationService: authenticationService,
|
||||||
loginHint: nil,
|
loginHint: nil,
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@ final class ServerSelectionScreenCoordinator: CoordinatorProtocol {
|
|||||||
let mode: ServerSelectionScreenMode = if parameters.appSettings.allowOtherAccountProviders {
|
let mode: ServerSelectionScreenMode = if parameters.appSettings.allowOtherAccountProviders {
|
||||||
.userInput
|
.userInput
|
||||||
} else {
|
} else {
|
||||||
.picker(parameters.appSettings.accountProviders)
|
.picker(parameters.appSettings.accountProviders.map(\.serverNameOrBaseURL))
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel = ServerSelectionScreenViewModel(authenticationService: parameters.authenticationService,
|
viewModel = ServerSelectionScreenViewModel(authenticationService: parameters.authenticationService,
|
||||||
|
|||||||
+6
-6
@@ -88,18 +88,18 @@ struct ServerSelectionScreenViewState: BindableState {
|
|||||||
var hasValidationError: Bool {
|
var hasValidationError: Bool {
|
||||||
switch mode {
|
switch mode {
|
||||||
case .userInput:
|
case .userInput:
|
||||||
bindings.homeserverAddress.isEmpty || isShowingFooterError
|
bindings.serverNameOrBaseURL.isEmpty || isShowingFooterError
|
||||||
case .picker(let options):
|
case .picker(let options):
|
||||||
!options.contains(bindings.homeserverAddress) || isShowingFooterError
|
!options.contains(bindings.serverNameOrBaseURL) || isShowingFooterError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ServerSelectionScreenBindings {
|
struct ServerSelectionScreenBindings {
|
||||||
/// The homeserver address input or chosen by the user.
|
/// The homeserver input or chosen by the user.
|
||||||
var homeserverAddress: String
|
var serverNameOrBaseURL: String
|
||||||
/// The selection range in `homeserverAddress`
|
/// The selection range in `serverNameOrBaseURL`
|
||||||
var homeserverSelection: TextSelection?
|
var serverNameOrBaseURLSelection: TextSelection?
|
||||||
|
|
||||||
/// Information describing the currently displayed alert.
|
/// Information describing the currently displayed alert.
|
||||||
var alertInfo: AlertInfo<ServerSelectionScreenErrorType>?
|
var alertInfo: AlertInfo<ServerSelectionScreenErrorType>?
|
||||||
|
|||||||
+18
-19
@@ -41,13 +41,12 @@ class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, Server
|
|||||||
self.userIndicatorController = userIndicatorController
|
self.userIndicatorController = userIndicatorController
|
||||||
self.homeserverHistoryManager = homeserverHistoryManager
|
self.homeserverHistoryManager = homeserverHistoryManager
|
||||||
|
|
||||||
let homeserverAddress: String
|
let serverNameOrBaseURL = if case .picker(let providers) = mode {
|
||||||
if case .picker(let providers) = mode {
|
providers[0]
|
||||||
homeserverAddress = providers[0]
|
|
||||||
} else {
|
} else {
|
||||||
homeserverAddress = authenticationService.homeserver.value.address
|
authenticationService.homeserver.value.accountProvider.serverNameOrBaseURL
|
||||||
}
|
}
|
||||||
let bindings = ServerSelectionScreenBindings(homeserverAddress: homeserverAddress)
|
let bindings = ServerSelectionScreenBindings(serverNameOrBaseURL: serverNameOrBaseURL)
|
||||||
super.init(initialViewState: ServerSelectionScreenViewState(mode: mode, authenticationFlow: authenticationFlow, bindings: bindings))
|
super.init(initialViewState: ServerSelectionScreenViewState(mode: mode, authenticationFlow: authenticationFlow, bindings: bindings))
|
||||||
|
|
||||||
context.viewState.textFieldAdapter.keystrokePublisher
|
context.viewState.textFieldAdapter.keystrokePublisher
|
||||||
@@ -80,40 +79,40 @@ class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, Server
|
|||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func pickServer() async {
|
private func pickServer() async {
|
||||||
let accountProvider = state.bindings.homeserverAddress
|
let serverNameOrBaseURL = state.bindings.serverNameOrBaseURL
|
||||||
guard accountProvider.isEmpty == false else {
|
guard serverNameOrBaseURL.isEmpty == false else {
|
||||||
fatalError("It shouldn't be possible to confirm without a selection.")
|
fatalError("It shouldn't be possible to confirm without a selection.")
|
||||||
}
|
}
|
||||||
|
|
||||||
startLoading()
|
startLoading()
|
||||||
|
|
||||||
switch await authenticationService.configure(for: accountProvider, flow: authenticationFlow) {
|
switch await authenticationService.configure(for: serverNameOrBaseURL, flow: authenticationFlow) {
|
||||||
case .success:
|
case .success:
|
||||||
MXLog.info("Selected server: \(accountProvider)")
|
MXLog.info("Selected server: \(serverNameOrBaseURL)")
|
||||||
await fetchLoginURLIfNeededAndContinue()
|
await fetchLoginURLIfNeededAndContinue()
|
||||||
stopLoading()
|
stopLoading()
|
||||||
case .failure:
|
case .failure:
|
||||||
MXLog.info("Invalid server: \(accountProvider)")
|
MXLog.info("Invalid server: \(serverNameOrBaseURL)")
|
||||||
stopLoading()
|
stopLoading()
|
||||||
// When the servers are hard-coded they should have a valid configuration, so show a generic error.
|
// When the servers are hard-coded they should have a valid configuration, so show a generic error.
|
||||||
state.bindings.alertInfo = AlertInfo(id: .unknownError)
|
state.bindings.alertInfo = AlertInfo(id: .unknownError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the login flow using the supplied homeserver address, or shows an error when this isn't possible.
|
/// Updates the login flow using the supplied server name or base URL, or shows an error when this isn't possible.
|
||||||
private func configureHomeserver() async {
|
private func configureHomeserver() async {
|
||||||
let userInput = state.bindings.homeserverAddress
|
let userInput = state.bindings.serverNameOrBaseURL
|
||||||
// People often enter their Matrix ID here, so use the server name from it when they do.
|
// People often enter their Matrix ID here, so use the server name from it when they do.
|
||||||
let homeserverAddress = (try? serverNameFromUserId(userId: userInput)) ?? userInput
|
let serverNameOrBaseURL = (try? serverNameFromUserId(userId: userInput)) ?? userInput
|
||||||
startLoading()
|
startLoading()
|
||||||
|
|
||||||
switch await authenticationService.configure(for: homeserverAddress, flow: authenticationFlow) {
|
switch await authenticationService.configure(for: serverNameOrBaseURL, flow: authenticationFlow) {
|
||||||
case .success:
|
case .success:
|
||||||
MXLog.info("Selected homeserver: \(homeserverAddress)")
|
MXLog.info("Selected homeserver: \(serverNameOrBaseURL)")
|
||||||
await fetchLoginURLIfNeededAndContinue()
|
await fetchLoginURLIfNeededAndContinue()
|
||||||
stopLoading()
|
stopLoading()
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
MXLog.info("Invalid homeserver: \(homeserverAddress)")
|
MXLog.info("Invalid homeserver: \(serverNameOrBaseURL)")
|
||||||
stopLoading()
|
stopLoading()
|
||||||
handleError(error)
|
handleError(error)
|
||||||
}
|
}
|
||||||
@@ -153,7 +152,7 @@ class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, Server
|
|||||||
/// Processes an error to either update the flow or display it to the user.
|
/// Processes an error to either update the flow or display it to the user.
|
||||||
private func handleError(_ error: AuthenticationServiceError) {
|
private func handleError(_ error: AuthenticationServiceError) {
|
||||||
switch error {
|
switch error {
|
||||||
case .invalidServer, .invalidHomeserverAddress:
|
case .invalidServer, .invalidServerNameOrBaseURL:
|
||||||
showFooterMessage(L10n.screenChangeServerErrorInvalidHomeserver)
|
showFooterMessage(L10n.screenChangeServerErrorInvalidHomeserver)
|
||||||
case .invalidWellKnown(let error):
|
case .invalidWellKnown(let error):
|
||||||
state.bindings.alertInfo = AlertInfo(id: .invalidWellKnownAlert(error),
|
state.bindings.alertInfo = AlertInfo(id: .invalidWellKnownAlert(error),
|
||||||
@@ -221,9 +220,9 @@ class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, Server
|
|||||||
debouncedAutocompleteTask = Task { @MainActor in
|
debouncedAutocompleteTask = Task { @MainActor in
|
||||||
try? await Task.sleep(for: .milliseconds(10))
|
try? await Task.sleep(for: .milliseconds(10))
|
||||||
guard Task.isCancelled == false else { return }
|
guard Task.isCancelled == false else { return }
|
||||||
context.homeserverAddress = newInput
|
context.serverNameOrBaseURL = newInput
|
||||||
context.viewState.textField?.text = newInput
|
context.viewState.textField?.text = newInput
|
||||||
context.homeserverSelection = TextSelection(range: selectionRange)
|
context.serverNameOrBaseURLSelection = TextSelection(range: selectionRange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -67,7 +67,7 @@ struct ServerSelectionScreen: View {
|
|||||||
VStack(alignment: .leading, spacing: 24) {
|
VStack(alignment: .leading, spacing: 24) {
|
||||||
switch context.viewState.mode {
|
switch context.viewState.mode {
|
||||||
case .userInput:
|
case .userInput:
|
||||||
TextField(L10n.screenChangeServerTextfieldPlaceholder, text: $context.homeserverAddress, selection: $context.homeserverSelection)
|
TextField(L10n.screenChangeServerTextfieldPlaceholder, text: $context.serverNameOrBaseURL, selection: $context.serverNameOrBaseURLSelection)
|
||||||
.textFieldStyle(.compound(labelText: Text(L10n.screenChangeServerTextfieldHeader),
|
.textFieldStyle(.compound(labelText: Text(L10n.screenChangeServerTextfieldHeader),
|
||||||
footerText: Text(context.viewState.footerMessage),
|
footerText: Text(context.viewState.footerMessage),
|
||||||
state: context.viewState.isShowingFooterError ? .error : .default,
|
state: context.viewState.isShowingFooterError ? .error : .default,
|
||||||
@@ -78,13 +78,13 @@ struct ServerSelectionScreen: View {
|
|||||||
.introspect(.textField, on: .supportedVersions) {
|
.introspect(.textField, on: .supportedVersions) {
|
||||||
context.send(viewAction: .updateTextField($0))
|
context.send(viewAction: .updateTextField($0))
|
||||||
}
|
}
|
||||||
.onChange(of: context.homeserverAddress) { context.send(viewAction: .clearFooterError) }
|
.onChange(of: context.serverNameOrBaseURL) { context.send(viewAction: .clearFooterError) }
|
||||||
.submitLabel(.done)
|
.submitLabel(.done)
|
||||||
.onSubmit(submit)
|
.onSubmit(submit)
|
||||||
case .picker(let providers):
|
case .picker(let providers):
|
||||||
FakeInlinePicker(items: providers,
|
FakeInlinePicker(items: providers,
|
||||||
icon: \.host,
|
icon: \.host,
|
||||||
selection: $context.homeserverAddress)
|
selection: $context.serverNameOrBaseURL)
|
||||||
.accessibilityIdentifier(A11yIdentifiers.serverConfirmationScreen.serverPicker)
|
.accessibilityIdentifier(A11yIdentifiers.serverConfirmationScreen.serverPicker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,10 +140,10 @@ private struct FakeInlinePicker: View {
|
|||||||
|
|
||||||
@available(iOS 26.0, *)
|
@available(iOS 26.0, *)
|
||||||
struct ServerSelection_Previews: PreviewProvider, TestablePreview {
|
struct ServerSelection_Previews: PreviewProvider, TestablePreview {
|
||||||
static let matrixViewModel = makeViewModel(mode: .userInput, homeserverAddress: "matrix.org")
|
static let matrixViewModel = makeViewModel(mode: .userInput, serverNameOrBaseURL: "matrix.org")
|
||||||
static let emptyViewModel = makeViewModel(mode: .userInput, homeserverAddress: "")
|
static let emptyViewModel = makeViewModel(mode: .userInput, serverNameOrBaseURL: "")
|
||||||
static let invalidViewModel = makeViewModel(mode: .userInput, homeserverAddress: "thisisbad")
|
static let invalidViewModel = makeViewModel(mode: .userInput, serverNameOrBaseURL: "thisisbad")
|
||||||
static let pickerViewModel = makeViewModel(mode: .picker(["matrix.org", "foo.bar", "baz.me"]), homeserverAddress: "foo.bar")
|
static let pickerViewModel = makeViewModel(mode: .picker(["matrix.org", "foo.bar", "baz.me"]), serverNameOrBaseURL: "foo.bar")
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ElementNavigationStack {
|
ElementNavigationStack {
|
||||||
@@ -168,7 +168,7 @@ struct ServerSelection_Previews: PreviewProvider, TestablePreview {
|
|||||||
.previewDisplayName("Picker")
|
.previewDisplayName("Picker")
|
||||||
}
|
}
|
||||||
|
|
||||||
static func makeViewModel(mode: ServerSelectionScreenMode = .userInput, homeserverAddress: String) -> ServerSelectionScreenViewModel {
|
static func makeViewModel(mode: ServerSelectionScreenMode = .userInput, serverNameOrBaseURL: String) -> ServerSelectionScreenViewModel {
|
||||||
let authenticationService = AuthenticationService.mock
|
let authenticationService = AuthenticationService.mock
|
||||||
|
|
||||||
let appSettings = AppSettings.volatile()
|
let appSettings = AppSettings.volatile()
|
||||||
@@ -179,8 +179,8 @@ struct ServerSelection_Previews: PreviewProvider, TestablePreview {
|
|||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
homeserverHistoryManager: HomeserverHistoryManager(appSettings: appSettings),
|
homeserverHistoryManager: HomeserverHistoryManager(appSettings: appSettings),
|
||||||
userIndicatorController: UserIndicatorControllerMock())
|
userIndicatorController: UserIndicatorControllerMock())
|
||||||
viewModel.context.homeserverAddress = homeserverAddress
|
viewModel.context.serverNameOrBaseURL = serverNameOrBaseURL
|
||||||
if case .userInput = mode, homeserverAddress == "thisisbad" {
|
if case .userInput = mode, serverNameOrBaseURL == "thisisbad" {
|
||||||
viewModel.context.send(viewAction: .confirm)
|
viewModel.context.send(viewAction: .confirm)
|
||||||
}
|
}
|
||||||
return viewModel
|
return viewModel
|
||||||
|
|||||||
+3
-3
@@ -24,7 +24,7 @@ struct AuthenticationStartScreenViewState: BindableState {
|
|||||||
/// The presentation anchor used for OAuth authentication.
|
/// The presentation anchor used for OAuth authentication.
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
|
|
||||||
let serverName: String?
|
let serverNameOrBaseURL: String?
|
||||||
let showCreateAccountButton: Bool
|
let showCreateAccountButton: Bool
|
||||||
let showQRCodeLoginButton: Bool
|
let showQRCodeLoginButton: Bool
|
||||||
|
|
||||||
@@ -36,8 +36,8 @@ struct AuthenticationStartScreenViewState: BindableState {
|
|||||||
var bindings = AuthenticationStartScreenViewStateBindings()
|
var bindings = AuthenticationStartScreenViewStateBindings()
|
||||||
|
|
||||||
var loginButtonTitle: String {
|
var loginButtonTitle: String {
|
||||||
if let serverName {
|
if let serverNameOrBaseURL {
|
||||||
L10n.screenOnboardingSignInTo(serverName)
|
L10n.screenOnboardingSignInTo(serverNameOrBaseURL)
|
||||||
} else if showQRCodeLoginButton {
|
} else if showQRCodeLoginButton {
|
||||||
L10n.screenOnboardingSignInManually
|
L10n.screenOnboardingSignInManually
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+15
-12
@@ -40,27 +40,30 @@ class AuthenticationStartScreenViewModel: AuthenticationStartScreenViewModelType
|
|||||||
canReportProblem = isBugReportServiceEnabled
|
canReportProblem = isBugReportServiceEnabled
|
||||||
|
|
||||||
let isQRCodeScanningSupported = !ProcessInfo.processInfo.isiOSAppOnMac
|
let isQRCodeScanningSupported = !ProcessInfo.processInfo.isiOSAppOnMac
|
||||||
let classicAppAccountProvider = authenticationService.classicAppAccount?.serverName
|
let isClassicAppAccountAllowed = if let classicAppAccount = authenticationService.classicAppAccount {
|
||||||
let isClassicAppAccountAllowed = classicAppAccountProvider.map { appSettings.accountProviders.contains($0) } ?? false
|
appSettings.accountProviders.contains(serverName: classicAppAccount.serverName, orBaseURL: classicAppAccount.homeserverURL)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
let initialViewState = if !appSettings.allowOtherAccountProviders {
|
let initialViewState = if !appSettings.allowOtherAccountProviders {
|
||||||
// We don't show the create account button when custom providers are disallowed.
|
// We don't show the create account button when custom providers are disallowed.
|
||||||
// The assumption here being that if you're running a custom app, your users will already be created.
|
// The assumption here being that if you're running a custom app, your users will already be created.
|
||||||
AuthenticationStartScreenViewState(serverName: appSettings.accountProviders.count == 1 ? appSettings.accountProviders[0] : nil,
|
AuthenticationStartScreenViewState(serverNameOrBaseURL: appSettings.accountProviders.count == 1 ? appSettings.accountProviders[0].serverNameOrBaseURL : nil,
|
||||||
showCreateAccountButton: false,
|
showCreateAccountButton: false,
|
||||||
showQRCodeLoginButton: isQRCodeScanningSupported,
|
showQRCodeLoginButton: isQRCodeScanningSupported,
|
||||||
classicAppMode: isClassicAppAccountAllowed ? authenticationService.classicAppAccount.map { .welcomeBack($0) } : nil,
|
classicAppMode: isClassicAppAccountAllowed ? authenticationService.classicAppAccount.map { .welcomeBack($0) } : nil,
|
||||||
hideBrandChrome: appSettings.hideBrandChrome)
|
hideBrandChrome: appSettings.hideBrandChrome)
|
||||||
} else if let provisioningParameters {
|
} else if let provisioningParameters {
|
||||||
// We only show the "Sign in to …" button when using a provisioning link.
|
// We only show the "Sign in to …" button when using a provisioning link.
|
||||||
AuthenticationStartScreenViewState(serverName: provisioningParameters.accountProvider,
|
AuthenticationStartScreenViewState(serverNameOrBaseURL: provisioningParameters.accountProvider,
|
||||||
showCreateAccountButton: false,
|
showCreateAccountButton: false,
|
||||||
showQRCodeLoginButton: false,
|
showQRCodeLoginButton: false,
|
||||||
classicAppMode: nil,
|
classicAppMode: nil,
|
||||||
hideBrandChrome: appSettings.hideBrandChrome)
|
hideBrandChrome: appSettings.hideBrandChrome)
|
||||||
} else {
|
} else {
|
||||||
// The default configuration.
|
// The default configuration.
|
||||||
AuthenticationStartScreenViewState(serverName: nil,
|
AuthenticationStartScreenViewState(serverNameOrBaseURL: nil,
|
||||||
showCreateAccountButton: appSettings.showCreateAccountButton,
|
showCreateAccountButton: appSettings.showCreateAccountButton,
|
||||||
showQRCodeLoginButton: isQRCodeScanningSupported,
|
showQRCodeLoginButton: isQRCodeScanningSupported,
|
||||||
classicAppMode: authenticationService.classicAppAccount.map { .welcomeBack($0) },
|
classicAppMode: authenticationService.classicAppAccount.map { .welcomeBack($0) },
|
||||||
@@ -114,22 +117,22 @@ class AuthenticationStartScreenViewModel: AuthenticationStartScreenViewModelType
|
|||||||
if classicAppAccount.state.availableSecrets == .requiresBackup {
|
if classicAppAccount.state.availableSecrets == .requiresBackup {
|
||||||
state.bindings.showClassicAppBackupInstructions = true
|
state.bindings.showClassicAppBackupInstructions = true
|
||||||
} else {
|
} else {
|
||||||
await configureAccountProvider(classicAppAccount.serverName,
|
await loginDirectly(using: classicAppAccount.serverName,
|
||||||
loginHint: "mxid:\(classicAppAccount.userID)",
|
loginHint: "mxid:\(classicAppAccount.userID)",
|
||||||
fallbackHomeserverURL: classicAppAccount.homeserverURL)
|
fallbackHomeserverURL: classicAppAccount.homeserverURL)
|
||||||
}
|
}
|
||||||
} else if let serverName = state.serverName {
|
} else if let serverNameOrBaseURL = state.serverNameOrBaseURL {
|
||||||
await configureAccountProvider(serverName, loginHint: provisioningParameters?.loginHint)
|
await loginDirectly(using: serverNameOrBaseURL, loginHint: provisioningParameters?.loginHint)
|
||||||
} else {
|
} else {
|
||||||
actionsSubject.send(.login) // No need to configure anything here, continue the flow.
|
actionsSubject.send(.login) // No need to configure anything here, continue the flow.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureAccountProvider(_ accountProvider: String, loginHint: String? = nil, fallbackHomeserverURL: URL? = nil) async {
|
private func loginDirectly(using serverNameOrBaseURL: String, loginHint: String? = nil, fallbackHomeserverURL: URL? = nil) async {
|
||||||
startLoading()
|
startLoading()
|
||||||
defer { stopLoading() }
|
defer { stopLoading() }
|
||||||
|
|
||||||
if case .failure = await authenticationService.configure(for: accountProvider, flow: .login) {
|
if case .failure = await authenticationService.configure(for: serverNameOrBaseURL, flow: .login) {
|
||||||
// Try the fallback URL before showing an error.
|
// Try the fallback URL before showing an error.
|
||||||
if let fallbackHomeserverURL,
|
if let fallbackHomeserverURL,
|
||||||
case .success = await authenticationService.configure(for: fallbackHomeserverURL.absoluteString, flow: .login) {
|
case .success = await authenticationService.configure(for: fallbackHomeserverURL.absoluteString, flow: .login) {
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
|||||||
private let appHooks: AppHooks
|
private let appHooks: AppHooks
|
||||||
|
|
||||||
private let homeserverSubject: CurrentValueSubject<LoginHomeserver, Never>
|
private let homeserverSubject: CurrentValueSubject<LoginHomeserver, Never>
|
||||||
|
/// The currently configured homeserver.
|
||||||
|
///
|
||||||
|
/// **Note:** The value's `accountProvider` will always be `.generic` after calling `configure(for:flow:)`.
|
||||||
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> {
|
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> {
|
||||||
homeserverSubject.asCurrentValuePublisher()
|
homeserverSubject.asCurrentValuePublisher()
|
||||||
}
|
}
|
||||||
@@ -64,17 +67,17 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// When updating these, don't forget to update the reset method too.
|
// When updating these, don't forget to update the reset method too.
|
||||||
homeserverSubject = .init(LoginHomeserver(address: appSettings.defaultServer, loginMode: .unknown))
|
homeserverSubject = .init(LoginHomeserver(accountProvider: appSettings.defaultAccountProvider, loginMode: .unknown))
|
||||||
flow = .login
|
flow = .login
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
func configure(for homeserverAddress: String, flow: AuthenticationFlow) async -> Result<Void, AuthenticationServiceError> {
|
func configure(for serverNameOrBaseURL: String, flow: AuthenticationFlow) async -> Result<Void, AuthenticationServiceError> {
|
||||||
do {
|
do {
|
||||||
var homeserver = LoginHomeserver(address: homeserverAddress, loginMode: .unknown)
|
var homeserver = LoginHomeserver(accountProvider: .generic(serverNameOrBaseURL), loginMode: .unknown)
|
||||||
|
|
||||||
let client = try await makeClient(homeserverAddress: homeserverAddress)
|
let client = try await makeClient(serverNameOrBaseURL: serverNameOrBaseURL)
|
||||||
let loginDetails = await client.homeserverLoginDetails()
|
let loginDetails = await client.homeserverLoginDetails()
|
||||||
|
|
||||||
homeserver.loginMode = if loginDetails.supportsOauthLogin() {
|
homeserver.loginMode = if loginDetails.supportsOauthLogin() {
|
||||||
@@ -106,7 +109,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
|||||||
return .failure(.elementProRequired(serverName: serverName))
|
return .failure(.elementProRequired(serverName: serverName))
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("Failed configuring a server: \(error)")
|
MXLog.error("Failed configuring a server: \(error)")
|
||||||
return .failure(.invalidHomeserverAddress)
|
return .failure(.invalidServerNameOrBaseURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,8 +195,8 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
|||||||
|
|
||||||
// n.b. We rely on the SDK checking that the intent of the QR is suitable for us to login with.
|
// n.b. We rely on the SDK checking that the intent of the QR is suitable for us to login with.
|
||||||
|
|
||||||
// Future versions of the QR should always give us the baseUrl
|
// Future versions of the QR should always give us the baseURL
|
||||||
guard let scannedServerNameOrBaseUrl = qrData.baseUrl() ?? qrData.serverName() else {
|
guard let scannedServerNameOrBaseURL = qrData.baseUrl() ?? qrData.serverName() else {
|
||||||
// With the older version of QR we treat the presence of serverName as meaning that the other device
|
// With the older version of QR we treat the presence of serverName as meaning that the other device
|
||||||
// is not signed in.
|
// is not signed in.
|
||||||
MXLog.error("The QR code is from a device that is not yet signed in.")
|
MXLog.error("The QR code is from a device that is not yet signed in.")
|
||||||
@@ -201,7 +204,20 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
|||||||
return progressSubject.asCurrentValuePublisher()
|
return progressSubject.asCurrentValuePublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
// n.b. We deliberatley don't check whether the received server is in our appSettings.accountProviders
|
// When account provider selection is restricted, make sure the scanned server is allowed.
|
||||||
|
// However, generic providers aren't detailed enough, so continue if one is present.
|
||||||
|
if !appSettings.allowOtherAccountProviders, !appSettings.accountProviders.contains(where: \.isGeneric) {
|
||||||
|
// A base URL from the QR code is expected to be a valid URL when present.
|
||||||
|
let scannedBaseURL = qrData.baseUrl().flatMap(URL.init(string:))
|
||||||
|
|
||||||
|
guard appSettings.accountProviders.contains(serverName: qrData.serverName(), orBaseURL: scannedBaseURL) else {
|
||||||
|
MXLog.error("The scanned QR code is for an account provider that isn't allowed.")
|
||||||
|
let error = QRCodeLoginError.providerNotAllowed(scannedProvider: scannedServerNameOrBaseURL,
|
||||||
|
allowedProviders: appSettings.accountProviders.map(\.serverNameOrBaseURL))
|
||||||
|
progressSubject.send(completion: .failure(.qrCodeError(error)))
|
||||||
|
return progressSubject.asCurrentValuePublisher()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The SDK calls the listener from arbitrary threads; onMainActor forwards the progress
|
// The SDK calls the listener from arbitrary threads; onMainActor forwards the progress
|
||||||
// updates on the main actor in FIFO order.
|
// updates on the main actor in FIFO order.
|
||||||
@@ -212,7 +228,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
|||||||
|
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let client = try await makeClient(homeserverAddress: scannedServerNameOrBaseUrl)
|
let client = try await makeClient(serverNameOrBaseURL: scannedServerNameOrBaseURL)
|
||||||
let qrCodeHandler = client.newLoginWithQrCodeHandler(oauthConfiguration: appSettings.oAuthConfiguration.rustValue)
|
let qrCodeHandler = client.newLoginWithQrCodeHandler(oauthConfiguration: appSettings.oAuthConfiguration.rustValue)
|
||||||
try await qrCodeHandler.scan(qrCodeData: qrData, progressListener: listener)
|
try await qrCodeHandler.scan(qrCodeData: qrData, progressListener: listener)
|
||||||
|
|
||||||
@@ -240,19 +256,19 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func reset() {
|
func reset() {
|
||||||
homeserverSubject.send(LoginHomeserver(address: appSettings.defaultServer, loginMode: .unknown))
|
homeserverSubject.send(LoginHomeserver(accountProvider: appSettings.defaultAccountProvider, loginMode: .unknown))
|
||||||
flow = .login
|
flow = .login
|
||||||
client = nil
|
client = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func makeClient(homeserverAddress: String) async throws -> ClientProtocol {
|
private func makeClient(serverNameOrBaseURL: String) async throws -> ClientProtocol {
|
||||||
// Use a fresh session directory each time the user enters a different server
|
// Use a fresh session directory each time the user enters a different server
|
||||||
// so that caches (e.g. server versions) are always fresh for the new server.
|
// so that caches (e.g. server versions) are always fresh for the new server.
|
||||||
rotateSessionDirectory()
|
rotateSessionDirectory()
|
||||||
|
|
||||||
let client = try await clientFactory.makeAuthenticationClient(homeserverAddress: homeserverAddress,
|
let client = try await clientFactory.makeAuthenticationClient(serverNameOrBaseURL: serverNameOrBaseURL,
|
||||||
sessionDirectories: sessionDirectories,
|
sessionDirectories: sessionDirectories,
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
clientSessionDelegate: userSessionStore.clientSessionDelegate,
|
clientSessionDelegate: userSessionStore.clientSessionDelegate,
|
||||||
@@ -286,7 +302,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
|||||||
MXLog.info("Checking Classic app account: \(classicAppAccount)")
|
MXLog.info("Checking Classic app account: \(classicAppAccount)")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let client = try await clientFactory.makeInMemoryClient(homeserverAddress: classicAppAccount.homeserverURL.absoluteString,
|
let client = try await clientFactory.makeInMemoryClient(serverNameOrBaseURL: classicAppAccount.homeserverURL.absoluteString,
|
||||||
clientSessionDelegate: userSessionStore.clientSessionDelegate,
|
clientSessionDelegate: userSessionStore.clientSessionDelegate,
|
||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
appHooks: appHooks)
|
appHooks: appHooks)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ enum AuthenticationServiceError: Error, Equatable {
|
|||||||
|
|
||||||
case invalidServer
|
case invalidServer
|
||||||
case invalidCredentials
|
case invalidCredentials
|
||||||
case invalidHomeserverAddress
|
case invalidServerNameOrBaseURL
|
||||||
case invalidWellKnown(String)
|
case invalidWellKnown(String)
|
||||||
case slidingSyncNotAvailable
|
case slidingSyncNotAvailable
|
||||||
case loginNotSupported
|
case loginNotSupported
|
||||||
@@ -39,12 +39,14 @@ enum AuthenticationServiceError: Error, Equatable {
|
|||||||
|
|
||||||
protocol AuthenticationServiceProtocol: QRCodeLoginServiceProtocol {
|
protocol AuthenticationServiceProtocol: QRCodeLoginServiceProtocol {
|
||||||
/// The currently configured homeserver.
|
/// The currently configured homeserver.
|
||||||
|
///
|
||||||
|
/// **Note:** The value's `accountProvider` will always be `.generic` after calling `configure(for:flow:)`.
|
||||||
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { get }
|
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { get }
|
||||||
/// The type of flow the service is currently configured with.
|
/// The type of flow the service is currently configured with.
|
||||||
var flow: AuthenticationFlow { get }
|
var flow: AuthenticationFlow { get }
|
||||||
|
|
||||||
/// Sets up the service for login on the specified homeserver address.
|
/// Sets up the service for login or registration using the supplied server name or base URL.
|
||||||
func configure(for homeserverAddress: String, flow: AuthenticationFlow) async -> Result<Void, AuthenticationServiceError>
|
func configure(for serverNameOrBaseURL: String, flow: AuthenticationFlow) async -> Result<Void, AuthenticationServiceError>
|
||||||
/// Performs login using OAuth for the current homeserver.
|
/// Performs login using OAuth for the current homeserver.
|
||||||
func urlForOAuthLogin(loginHint: String?) async -> Result<OAuthAuthorizationDataProxy, AuthenticationServiceError>
|
func urlForOAuthLogin(loginHint: String?) async -> Result<OAuthAuthorizationDataProxy, AuthenticationServiceError>
|
||||||
/// Asks the SDK to abort an ongoing OAuth login if we didn't get a callback to complete the request with.
|
/// Asks the SDK to abort an ongoing OAuth login if we didn't get a callback to complete the request with.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ nonisolated struct ClientFactory: ClientFactoryProtocol {
|
|||||||
// MARK: Authentication
|
// MARK: Authentication
|
||||||
|
|
||||||
#if IS_MAIN_APP
|
#if IS_MAIN_APP
|
||||||
func makeAuthenticationClient(homeserverAddress: String,
|
func makeAuthenticationClient(serverNameOrBaseURL: String,
|
||||||
sessionDirectories: SessionDirectories,
|
sessionDirectories: SessionDirectories,
|
||||||
passphrase: String,
|
passphrase: String,
|
||||||
clientSessionDelegate: ClientSessionDelegate,
|
clientSessionDelegate: ClientSessionDelegate,
|
||||||
@@ -29,12 +29,12 @@ nonisolated struct ClientFactory: ClientFactoryProtocol {
|
|||||||
.enableAutomaticBackPagination(enableAutomaticBackPagination: appSettings.automaticBackPaginationEnabled)
|
.enableAutomaticBackPagination(enableAutomaticBackPagination: appSettings.automaticBackPaginationEnabled)
|
||||||
.sqliteStore(config: .init(dataPath: sessionDirectories.dataPath, cachePath: sessionDirectories.cachePath)
|
.sqliteStore(config: .init(dataPath: sessionDirectories.dataPath, cachePath: sessionDirectories.cachePath)
|
||||||
.passphrase(passphrase: passphrase))
|
.passphrase(passphrase: passphrase))
|
||||||
.serverNameOrHomeserverUrl(serverNameOrUrl: homeserverAddress)
|
.serverNameOrHomeserverUrl(serverNameOrUrl: serverNameOrBaseURL)
|
||||||
|
|
||||||
return try await build(builder, for: .authentication, appHooks: appHooks)
|
return try await build(builder, for: .authentication, appHooks: appHooks)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeInMemoryClient(homeserverAddress: String,
|
func makeInMemoryClient(serverNameOrBaseURL: String,
|
||||||
clientSessionDelegate: ClientSessionDelegate,
|
clientSessionDelegate: ClientSessionDelegate,
|
||||||
appSettings: AppSettings,
|
appSettings: AppSettings,
|
||||||
appHooks: AppHooks) async throws -> ClientProtocol {
|
appHooks: AppHooks) async throws -> ClientProtocol {
|
||||||
@@ -45,7 +45,7 @@ nonisolated struct ClientFactory: ClientFactoryProtocol {
|
|||||||
enableOnlySignedDeviceIsolationMode: appSettings.enableOnlySignedDeviceIsolationMode,
|
enableOnlySignedDeviceIsolationMode: appSettings.enableOnlySignedDeviceIsolationMode,
|
||||||
threadsEnabled: appSettings.threadsEnabled)
|
threadsEnabled: appSettings.threadsEnabled)
|
||||||
.inMemoryStore()
|
.inMemoryStore()
|
||||||
.serverNameOrHomeserverUrl(serverNameOrUrl: homeserverAddress)
|
.serverNameOrHomeserverUrl(serverNameOrUrl: serverNameOrBaseURL)
|
||||||
|
|
||||||
return try await build(builder, for: .classicAppAccount, appHooks: appHooks)
|
return try await build(builder, for: .classicAppAccount, appHooks: appHooks)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ nonisolated protocol ClientFactoryProtocol {
|
|||||||
// MARK: Authentication
|
// MARK: Authentication
|
||||||
|
|
||||||
#if IS_MAIN_APP
|
#if IS_MAIN_APP
|
||||||
func makeAuthenticationClient(homeserverAddress: String,
|
func makeAuthenticationClient(serverNameOrBaseURL: String,
|
||||||
sessionDirectories: SessionDirectories,
|
sessionDirectories: SessionDirectories,
|
||||||
passphrase: String,
|
passphrase: String,
|
||||||
clientSessionDelegate: ClientSessionDelegate,
|
clientSessionDelegate: ClientSessionDelegate,
|
||||||
appSettings: AppSettings,
|
appSettings: AppSettings,
|
||||||
appHooks: AppHooks) async throws -> ClientProtocol
|
appHooks: AppHooks) async throws -> ClientProtocol
|
||||||
|
|
||||||
func makeInMemoryClient(homeserverAddress: String,
|
func makeInMemoryClient(serverNameOrBaseURL: String,
|
||||||
clientSessionDelegate: ClientSessionDelegate,
|
clientSessionDelegate: ClientSessionDelegate,
|
||||||
appSettings: AppSettings,
|
appSettings: AppSettings,
|
||||||
appHooks: AppHooks) async throws -> ClientProtocol
|
appHooks: AppHooks) async throws -> ClientProtocol
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class HomeserverHistoryManager {
|
|||||||
/// in all lowercase. This is the list that matches are made against.
|
/// in all lowercase. This is the list that matches are made against.
|
||||||
private func updateCachedServers() {
|
private func updateCachedServers() {
|
||||||
let previous = appSettings.previousServers.map { $0.lowercased() }
|
let previous = appSettings.previousServers.map { $0.lowercased() }
|
||||||
let defaultProviders = appSettings.accountProviders.map { $0.lowercased() }
|
let defaultProviders = appSettings.accountProviders.map { $0.serverNameOrBaseURL.lowercased() }
|
||||||
|
|
||||||
cachedServers = previous + defaultProviders
|
cachedServers = previous + defaultProviders
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,12 @@ class MockScreen: Identifiable {
|
|||||||
let appSettings: AppSettings! = appSettings
|
let appSettings: AppSettings! = appSettings
|
||||||
|
|
||||||
if id == .singleProviderAuthenticationFlow || id == .multipleProvidersAuthenticationFlow {
|
if id == .singleProviderAuthenticationFlow || id == .multipleProvidersAuthenticationFlow {
|
||||||
let accountProviders = id == .singleProviderAuthenticationFlow ? ["example.com"] : ["guest.example.com", "example.com"]
|
let accountProviders: [AccountProvider] = if id == .singleProviderAuthenticationFlow {
|
||||||
|
[.managed(serverName: "example.com", baseURL: "https://matrix.example.com")]
|
||||||
|
} else {
|
||||||
|
[.managed(serverName: "guest.example.com", baseURL: "https://matrix.guest.example.com"),
|
||||||
|
.managed(serverName: "example.com", baseURL: "https://matrix.example.com")]
|
||||||
|
}
|
||||||
appSettings.override(accountProviders: accountProviders,
|
appSettings.override(accountProviders: accountProviders,
|
||||||
allowOtherAccountProviders: false,
|
allowOtherAccountProviders: false,
|
||||||
hideBrandChrome: false,
|
hideBrandChrome: false,
|
||||||
|
|||||||
+1
-1
Submodule Enterprise updated: adca548afe...be536ca83c
@@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
@testable import ElementX
|
||||||
|
import Foundation
|
||||||
|
import Testing
|
||||||
|
|
||||||
|
struct AccountProviderTests {
|
||||||
|
@Test
|
||||||
|
func genericAddressIsSanitized() {
|
||||||
|
// Lowercased, whitespace trimmed, https:// dropped and trailing slashes removed.
|
||||||
|
#expect(AccountProvider.generic("Matrix.org").serverNameOrBaseURL == "matrix.org")
|
||||||
|
#expect(AccountProvider.generic(" matrix.org ").serverNameOrBaseURL == "matrix.org")
|
||||||
|
#expect(AccountProvider.generic("https://matrix.org").serverNameOrBaseURL == "matrix.org")
|
||||||
|
#expect(AccountProvider.generic("https://matrix.org/").serverNameOrBaseURL == "matrix.org")
|
||||||
|
#expect(AccountProvider.generic("HTTPS://Matrix.org///").serverNameOrBaseURL == "matrix.org")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func genericAddressKeepsHTTPScheme() {
|
||||||
|
#expect(AccountProvider.generic("http://localhost:8008").serverNameOrBaseURL == "http://localhost:8008",
|
||||||
|
"The http:// scheme should be preserved as an indicator")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func managedUsesServerNameWhenPresent() {
|
||||||
|
let provider = AccountProvider.managed(serverName: "Matrix.org",
|
||||||
|
baseURL: "https://matrix-client.matrix.org",
|
||||||
|
canUseServerName: true)
|
||||||
|
#expect(provider.serverNameOrBaseURL == "matrix.org")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func managedFallsBackToBaseURLWhenShouldNotUserServerName() {
|
||||||
|
let provider = AccountProvider.managed(serverName: "Matrix.org",
|
||||||
|
baseURL: "https://matrix-client.matrix.org",
|
||||||
|
canUseServerName: false)
|
||||||
|
#expect(provider.serverNameOrBaseURL == "matrix-client.matrix.org")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
@testable import ElementX
|
||||||
|
import Testing
|
||||||
|
|
||||||
|
struct AppSettingsTests {
|
||||||
|
@Test
|
||||||
|
func defaultAccountProvider() {
|
||||||
|
let appSettings = AppSettings.volatile()
|
||||||
|
#expect(appSettings.defaultAccountProvider == appSettings.accountProviders[0])
|
||||||
|
|
||||||
|
appSettings.previousServers = ["example.com"]
|
||||||
|
#expect(appSettings.defaultAccountProvider == .generic("example.com"))
|
||||||
|
|
||||||
|
appSettings.override(allowOtherAccountProviders: false)
|
||||||
|
#expect(appSettings.defaultAccountProvider == appSettings.accountProviders[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
private extension AppSettings {
|
||||||
|
func override(allowOtherAccountProviders: Bool) {
|
||||||
|
override(accountProviders: accountProviders,
|
||||||
|
allowOtherAccountProviders: allowOtherAccountProviders,
|
||||||
|
hideBrandChrome: hideBrandChrome,
|
||||||
|
pushGatewayBaseURL: pushGatewayBaseURL,
|
||||||
|
oAuthRedirectURL: oAuthRedirectURL,
|
||||||
|
oAuthClientURIPath: oAuthClientURIPath,
|
||||||
|
websiteURL: websiteURL,
|
||||||
|
logoURL: logoURL,
|
||||||
|
copyrightURL: copyrightURL,
|
||||||
|
acceptableUseURL: acceptableUseURL,
|
||||||
|
privacyURL: privacyURL,
|
||||||
|
encryptionURL: encryptionURL,
|
||||||
|
deviceVerificationURL: deviceVerificationURL,
|
||||||
|
chatBackupDetailsURL: chatBackupDetailsURL,
|
||||||
|
identityPinningViolationDetailsURL: identityPinningViolationDetailsURL,
|
||||||
|
historySharingDetailsURL: historySharingDetailsURL,
|
||||||
|
elementWebHosts: elementWebHosts,
|
||||||
|
accountProvisioningHost: accountProvisioningHost,
|
||||||
|
bugReportApplicationID: bugReportApplicationID,
|
||||||
|
analyticsTermsURL: analyticsTermsURL,
|
||||||
|
mapTilerConfiguration: mapTilerConfiguration.publisher.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ struct AuthenticationServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
mutating func passwordLogin() async throws {
|
mutating func passwordLogin() async throws {
|
||||||
try await setup(serverAddress: "example.com")
|
try await setup(serverNameOrBaseURL: "example.com")
|
||||||
|
|
||||||
switch await service.configure(for: "example.com", flow: .login) {
|
switch await service.configure(for: "example.com", flow: .login) {
|
||||||
case .success:
|
case .success:
|
||||||
@@ -65,21 +65,20 @@ struct AuthenticationServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MainActor
|
|
||||||
mutating func configureRegisterNoSupport() async throws {
|
mutating func configureRegisterNoSupport() async throws {
|
||||||
let homeserverAddress = "example.com"
|
let serverNameOrBaseURL = "example.com"
|
||||||
try await setup(serverAddress: homeserverAddress)
|
try await setup(serverNameOrBaseURL: serverNameOrBaseURL)
|
||||||
|
|
||||||
try await #require(throws: AuthenticationServiceError.registrationNotSupported) {
|
try await #require(throws: AuthenticationServiceError.registrationNotSupported) {
|
||||||
try await service.configure(for: homeserverAddress, flow: .register).get()
|
try await service.configure(for: serverNameOrBaseURL, flow: .register).get()
|
||||||
}
|
}
|
||||||
|
|
||||||
#expect(service.flow == .login)
|
#expect(service.flow == .login)
|
||||||
#expect(service.homeserver.value == .init(address: "matrix.org", loginMode: .unknown))
|
#expect(service.homeserver.value == .init(accountProvider: .managed(serverName: "matrix.org", baseURL: "https://matrix-client.matrix.org"),
|
||||||
|
loginMode: .unknown))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MainActor
|
|
||||||
mutating func classicAppAccountSecretsBundleIsUsed() async throws {
|
mutating func classicAppAccountSecretsBundleIsUsed() async throws {
|
||||||
// Given an authentication service with an Element Classic account for Alice.
|
// Given an authentication service with an Element Classic account for Alice.
|
||||||
try await setup(classicAppAccounts: [.mockAlice])
|
try await setup(classicAppAccounts: [.mockAlice])
|
||||||
@@ -96,7 +95,6 @@ struct AuthenticationServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MainActor
|
|
||||||
mutating func classicAppAccountSecretsBundleIsIgnoredWhenUnavailable() async throws {
|
mutating func classicAppAccountSecretsBundleIsIgnoredWhenUnavailable() async throws {
|
||||||
// Given an authentication service with an Element Classic account for Alice
|
// Given an authentication service with an Element Classic account for Alice
|
||||||
// which isn't configured with any available secrets.
|
// which isn't configured with any available secrets.
|
||||||
@@ -114,7 +112,6 @@ struct AuthenticationServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MainActor
|
|
||||||
mutating func classicAppAccountSecretsBundleIsIgnoredForDifferentUser() async throws {
|
mutating func classicAppAccountSecretsBundleIsIgnoredForDifferentUser() async throws {
|
||||||
// Given an authentication service with an Element Classic account for Dan.
|
// Given an authentication service with an Element Classic account for Dan.
|
||||||
try await setup(classicAppAccounts: [.mockDan])
|
try await setup(classicAppAccounts: [.mockDan])
|
||||||
@@ -132,13 +129,13 @@ struct AuthenticationServiceTests {
|
|||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
private mutating func setup(serverAddress: String = "matrix.org",
|
private mutating func setup(serverNameOrBaseURL: String = "matrix.org",
|
||||||
classicAppAccounts: [ClassicAppAccount] = [],
|
classicAppAccounts: [ClassicAppAccount] = [],
|
||||||
availableSecrets: ClassicAppAccount.AvailableSecrets = .complete) async throws {
|
availableSecrets: ClassicAppAccount.AvailableSecrets = .complete) async throws {
|
||||||
let configuration: ClientFactoryMock.Configuration = .init()
|
let configuration: ClientFactoryMock.Configuration = .init()
|
||||||
let clientFactory = ClientFactoryMock(configuration)
|
let clientFactory = ClientFactoryMock(configuration)
|
||||||
|
|
||||||
client = configuration.homeserverClients[serverAddress]
|
client = configuration.homeserverClients[serverNameOrBaseURL]
|
||||||
encryption = EncryptionSDKMock()
|
encryption = EncryptionSDKMock()
|
||||||
client.encryptionReturnValue = encryption
|
client.encryptionReturnValue = encryption
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then the authentication service should not be used yet.
|
// Then the authentication service should not be used yet.
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 0)
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 0)
|
||||||
#expect(authenticationService.homeserver.value.loginMode == .unknown)
|
#expect(authenticationService.homeserver.value.loginMode == .unknown)
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
context.send(viewAction: .login)
|
context.send(viewAction: .login)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 1)
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 1)
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.prompt == .consent)
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.prompt == .consent)
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.loginHint == "user@company.com")
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.loginHint == "user@company.com")
|
||||||
@@ -90,14 +90,14 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then a call to configure service should be made.
|
// Then a call to configure service should be made.
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
#expect(authenticationService.homeserver.value.loginMode == .password)
|
#expect(authenticationService.homeserver.value.loginMode == .password)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func singleProviderOAuthState() async throws {
|
func singleProviderOAuthState() async throws {
|
||||||
// Given a view model that for an app that only allows the use of a single provider that supports OAuth.
|
// Given a view model that for an app that only allows the use of a single provider that supports OAuth.
|
||||||
setAllowedAccountProviders(["company.com"])
|
setAllowedAccountProviders([.generic("company.com")])
|
||||||
await setupViewModel()
|
await setupViewModel()
|
||||||
#expect(authenticationService.homeserver.value.loginMode == .unknown)
|
#expect(authenticationService.homeserver.value.loginMode == .unknown)
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 0)
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 0)
|
||||||
@@ -108,7 +108,7 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
context.send(viewAction: .login)
|
context.send(viewAction: .login)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 1)
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 1)
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.prompt == .consent)
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.prompt == .consent)
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.loginHint == nil)
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.loginHint == nil)
|
||||||
@@ -118,7 +118,7 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
@Test
|
@Test
|
||||||
func singleProviderPasswordState() async throws {
|
func singleProviderPasswordState() async throws {
|
||||||
// Given a view model that for an app that only allows the use of a single provider that does not support OAuth.
|
// Given a view model that for an app that only allows the use of a single provider that does not support OAuth.
|
||||||
setAllowedAccountProviders(["company.com"])
|
setAllowedAccountProviders([.generic("company.com")])
|
||||||
await setupViewModel(supportsOAuth: false)
|
await setupViewModel(supportsOAuth: false)
|
||||||
#expect(authenticationService.homeserver.value.loginMode == .unknown)
|
#expect(authenticationService.homeserver.value.loginMode == .unknown)
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 0)
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 0)
|
||||||
@@ -130,7 +130,7 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then a call to configure service should be made.
|
// Then a call to configure service should be made.
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
#expect(authenticationService.homeserver.value.loginMode == .password)
|
#expect(authenticationService.homeserver.value.loginMode == .password)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,8 +153,8 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
context.send(viewAction: .continueWithClassic(classicAppAccount))
|
context.send(viewAction: .continueWithClassic(classicAppAccount))
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments?.homeserverAddress == "company.com")
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments?.serverNameOrBaseURL == "company.com")
|
||||||
#expect(authenticationService.homeserver.value.loginMode == .oAuth(supportsCreatePrompt: false))
|
#expect(authenticationService.homeserver.value.loginMode == .oAuth(supportsCreatePrompt: false))
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.loginHint == "mxid:\(classicAppAccount.userID)")
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.loginHint == "mxid:\(classicAppAccount.userID)")
|
||||||
}
|
}
|
||||||
@@ -177,8 +177,8 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
context.send(viewAction: .continueWithClassic(classicAppAccount))
|
context.send(viewAction: .continueWithClassic(classicAppAccount))
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 2)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 2)
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments?.homeserverAddress == "https://matrix.company.com")
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments?.serverNameOrBaseURL == "https://matrix.company.com")
|
||||||
#expect(authenticationService.homeserver.value.loginMode == .oAuth(supportsCreatePrompt: false))
|
#expect(authenticationService.homeserver.value.loginMode == .oAuth(supportsCreatePrompt: false))
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.loginHint == "mxid:\(classicAppAccount.userID)")
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.loginHint == "mxid:\(classicAppAccount.userID)")
|
||||||
}
|
}
|
||||||
@@ -214,7 +214,7 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
// Given a view model for an app that only allows a single provider that matches the Classic account's server.
|
// Given a view model for an app that only allows a single provider that matches the Classic account's server.
|
||||||
let classicAppAccount = makeClassicAppAccount(serverName: "company.com",
|
let classicAppAccount = makeClassicAppAccount(serverName: "company.com",
|
||||||
homeserverURL: "https://matrix.company.com")
|
homeserverURL: "https://matrix.company.com")
|
||||||
setAllowedAccountProviders(["company.com"])
|
setAllowedAccountProviders([.generic("company.com")])
|
||||||
await setupViewModel(classicAppAccount: classicAppAccount)
|
await setupViewModel(classicAppAccount: classicAppAccount)
|
||||||
|
|
||||||
// Then the Classic app account should be shown as a welcome-back option.
|
// Then the Classic app account should be shown as a welcome-back option.
|
||||||
@@ -230,7 +230,7 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
// Given a view model for an app that only allows a single provider that does NOT match the Classic account's server.
|
// Given a view model for an app that only allows a single provider that does NOT match the Classic account's server.
|
||||||
let classicAppAccount = makeClassicAppAccount(serverName: "other-server.org",
|
let classicAppAccount = makeClassicAppAccount(serverName: "other-server.org",
|
||||||
homeserverURL: "https://matrix.other-server.org")
|
homeserverURL: "https://matrix.other-server.org")
|
||||||
setAllowedAccountProviders(["company.com"])
|
setAllowedAccountProviders([.generic("company.com")])
|
||||||
await setupViewModel(classicAppAccount: classicAppAccount)
|
await setupViewModel(classicAppAccount: classicAppAccount)
|
||||||
|
|
||||||
// Then the Classic app account should not be shown since the server is not in the allowed providers.
|
// Then the Classic app account should not be shown since the server is not in the allowed providers.
|
||||||
@@ -278,7 +278,7 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
supportsOAuth: Bool = true,
|
supportsOAuth: Bool = true,
|
||||||
supportsPasswordLogin: Bool = true,
|
supportsPasswordLogin: Bool = true,
|
||||||
availableSecrets: ClassicAppAccount.AvailableSecrets = .complete) async {
|
availableSecrets: ClassicAppAccount.AvailableSecrets = .complete) async {
|
||||||
// Manually create a configuration as the default homeserver address setting is immutable.
|
// Manually create a configuration as the default account provider setting is immutable.
|
||||||
client = ClientSDKMock(.init(oAuthLoginURL: supportsOAuth ? "https://account.company.com/authorize" : nil,
|
client = ClientSDKMock(.init(oAuthLoginURL: supportsOAuth ? "https://account.company.com/authorize" : nil,
|
||||||
supportsOAuthCreatePrompt: false,
|
supportsOAuthCreatePrompt: false,
|
||||||
supportsPasswordLogin: supportsPasswordLogin))
|
supportsPasswordLogin: supportsPasswordLogin))
|
||||||
@@ -330,7 +330,7 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
accessToken: "accessToken")
|
accessToken: "accessToken")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setAllowedAccountProviders(_ providers: [String]) {
|
private func setAllowedAccountProviders(_ providers: [AccountProvider]) {
|
||||||
appSettings.override(accountProviders: providers,
|
appSettings.override(accountProviders: providers,
|
||||||
allowOtherAccountProviders: false,
|
allowOtherAccountProviders: false,
|
||||||
hideBrandChrome: false,
|
hideBrandChrome: false,
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ struct LoginScreenViewModelTests {
|
|||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
private mutating func setupViewModel(homeserverAddress: String = "example.com", loginHint: String? = nil) async {
|
private mutating func setupViewModel(serverNameOrBaseURL: String = "example.com", loginHint: String? = nil) async {
|
||||||
let appSettings = AppSettings.volatile()
|
let appSettings = AppSettings.volatile()
|
||||||
|
|
||||||
clientFactory = ClientFactoryMock(.init())
|
clientFactory = ClientFactoryMock(.init())
|
||||||
@@ -238,7 +238,7 @@ struct LoginScreenViewModelTests {
|
|||||||
appHooks: AppHooks())
|
appHooks: AppHooks())
|
||||||
|
|
||||||
guard case .success = await service
|
guard case .success = await service
|
||||||
.configure(for: homeserverAddress, flow: .login) else {
|
.configure(for: serverNameOrBaseURL, flow: .login) else {
|
||||||
Issue.record("A valid server should be configured for the test.")
|
Issue.record("A valid server should be configured for the test.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ nonisolated struct NotificationContentBuilderTests {
|
|||||||
var mediaProvider: MediaProviderMock
|
var mediaProvider: MediaProviderMock
|
||||||
var notificationContent: UNMutableNotificationContent
|
var notificationContent: UNMutableNotificationContent
|
||||||
|
|
||||||
init() {
|
init() async {
|
||||||
notificationContent = .init()
|
notificationContent = .init()
|
||||||
let stringBuilder = RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(mentionBuilder: PlainMentionBuilder()),
|
let stringBuilder = RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(mentionBuilder: PlainMentionBuilder()),
|
||||||
style: .plain)
|
style: .plain)
|
||||||
mediaProvider = MediaProviderMock(.init())
|
mediaProvider = MediaProviderMock(.init())
|
||||||
notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: stringBuilder,
|
notificationContentBuilder = await NotificationContentBuilder(messageEventStringBuilder: stringBuilder,
|
||||||
notificationSoundName: UNNotificationSoundName("message.caf"),
|
notificationSoundName: UNNotificationSoundName("message.caf"),
|
||||||
userSession: NSEUserSessionMock(.init()))
|
userSession: NSEUserSessionMock(.init()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import Testing
|
|||||||
@MainActor
|
@MainActor
|
||||||
struct ServerSelectionScreenViewModelTests {
|
struct ServerSelectionScreenViewModelTests {
|
||||||
private static let passwordOnlyServer = "example.com" // password only, no OAuth
|
private static let passwordOnlyServer = "example.com" // password only, no OAuth
|
||||||
private static let pickerProviders = ["matrix.org", "beta.matrix.org", passwordOnlyServer]
|
private static let pickerProviders: [String] = ["matrix.org", "beta.matrix.org", passwordOnlyServer]
|
||||||
|
|
||||||
var appSettings: AppSettings!
|
var appSettings: AppSettings!
|
||||||
var client: ClientSDKMock!
|
var client: ClientSDKMock!
|
||||||
@@ -31,16 +31,16 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
// Given a view model for login.
|
// Given a view model for login.
|
||||||
try setup(authenticationFlow: .login)
|
try setup(authenticationFlow: .login)
|
||||||
#expect(service.homeserver.value.loginMode == .unknown)
|
#expect(service.homeserver.value.loginMode == .unknown)
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
||||||
|
|
||||||
// When selecting matrix.org.
|
// When selecting matrix.org.
|
||||||
context.homeserverAddress = "matrix.org"
|
context.serverNameOrBaseURL = "matrix.org"
|
||||||
let deferred = deferFulfillment(viewModel.actions) { $0.isContinueWithOAuth }
|
let deferred = deferFulfillment(viewModel.actions) { $0.isContinueWithOAuth }
|
||||||
context.send(viewAction: .confirm)
|
context.send(viewAction: .confirm)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then selection should succeed.
|
// Then selection should succeed.
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
#expect(service.homeserver.value == .mockMatrixDotOrg)
|
#expect(service.homeserver.value == .mockMatrixDotOrg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,13 +51,13 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
#expect(service.homeserver.value.loginMode == .unknown)
|
#expect(service.homeserver.value.loginMode == .unknown)
|
||||||
|
|
||||||
// When entering a Matrix ID instead of an account provider.
|
// When entering a Matrix ID instead of an account provider.
|
||||||
context.homeserverAddress = "@alice:matrix.org"
|
context.serverNameOrBaseURL = "@alice:matrix.org"
|
||||||
let deferred = deferFulfillment(viewModel.actions) { $0.isContinueWithOAuth }
|
let deferred = deferFulfillment(viewModel.actions) { $0.isContinueWithOAuth }
|
||||||
context.send(viewAction: .confirm)
|
context.send(viewAction: .confirm)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then the homeserver from the ID should be used.
|
// Then the homeserver from the ID should be used.
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments?.homeserverAddress == "matrix.org")
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksReceivedArguments?.serverNameOrBaseURL == "matrix.org")
|
||||||
#expect(service.homeserver.value == .mockMatrixDotOrg)
|
#expect(service.homeserver.value == .mockMatrixDotOrg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,17 +66,17 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
// Given a view model for login.
|
// Given a view model for login.
|
||||||
try setup(authenticationFlow: .login)
|
try setup(authenticationFlow: .login)
|
||||||
#expect(service.homeserver.value.loginMode == .unknown)
|
#expect(service.homeserver.value.loginMode == .unknown)
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
||||||
#expect(context.alertInfo == nil)
|
#expect(context.alertInfo == nil)
|
||||||
|
|
||||||
// When selecting a server that doesn't support login.
|
// When selecting a server that doesn't support login.
|
||||||
context.homeserverAddress = "server.net"
|
context.serverNameOrBaseURL = "server.net"
|
||||||
let deferred = deferFulfillment(context.observe(\.alertInfo)) { $0 != nil }
|
let deferred = deferFulfillment(context.observe(\.alertInfo)) { $0 != nil }
|
||||||
context.send(viewAction: .confirm)
|
context.send(viewAction: .confirm)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then selection should fail with an alert about not supporting registration.
|
// Then selection should fail with an alert about not supporting registration.
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
#expect(context.alertInfo?.id == .loginAlert)
|
#expect(context.alertInfo?.id == .loginAlert)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,16 +85,16 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
// Given a view model for registration.
|
// Given a view model for registration.
|
||||||
try setup(authenticationFlow: .register)
|
try setup(authenticationFlow: .register)
|
||||||
#expect(service.homeserver.value.loginMode == .unknown)
|
#expect(service.homeserver.value.loginMode == .unknown)
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
||||||
|
|
||||||
// When selecting matrix.org.
|
// When selecting matrix.org.
|
||||||
context.homeserverAddress = "matrix.org"
|
context.serverNameOrBaseURL = "matrix.org"
|
||||||
let deferred = deferFulfillment(viewModel.actions) { $0.isContinueWithOAuth }
|
let deferred = deferFulfillment(viewModel.actions) { $0.isContinueWithOAuth }
|
||||||
context.send(viewAction: .confirm)
|
context.send(viewAction: .confirm)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then selection should succeed.
|
// Then selection should succeed.
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
#expect(service.homeserver.value == .mockMatrixDotOrg)
|
#expect(service.homeserver.value == .mockMatrixDotOrg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,17 +103,17 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
// Given a view model for registration.
|
// Given a view model for registration.
|
||||||
try setup(authenticationFlow: .register)
|
try setup(authenticationFlow: .register)
|
||||||
#expect(service.homeserver.value.loginMode == .unknown)
|
#expect(service.homeserver.value.loginMode == .unknown)
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
||||||
#expect(context.alertInfo == nil)
|
#expect(context.alertInfo == nil)
|
||||||
|
|
||||||
// When selecting a server that doesn't support registration.
|
// When selecting a server that doesn't support registration.
|
||||||
context.homeserverAddress = "example.com"
|
context.serverNameOrBaseURL = "example.com"
|
||||||
let deferred = deferFulfillment(context.observe(\.alertInfo)) { $0 != nil }
|
let deferred = deferFulfillment(context.observe(\.alertInfo)) { $0 != nil }
|
||||||
context.send(viewAction: .confirm)
|
context.send(viewAction: .confirm)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then selection should fail with an alert about not supporting registration.
|
// Then selection should fail with an alert about not supporting registration.
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
#expect(context.alertInfo?.id == .registrationAlert)
|
#expect(context.alertInfo?.id == .registrationAlert)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,17 +122,17 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
// Given a view model for login.
|
// Given a view model for login.
|
||||||
try setup(authenticationFlow: .login)
|
try setup(authenticationFlow: .login)
|
||||||
#expect(service.homeserver.value.loginMode == .unknown)
|
#expect(service.homeserver.value.loginMode == .unknown)
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
||||||
#expect(context.alertInfo == nil)
|
#expect(context.alertInfo == nil)
|
||||||
|
|
||||||
// When selecting a server that requires Element Pro
|
// When selecting a server that requires Element Pro
|
||||||
context.homeserverAddress = "secure.gov"
|
context.serverNameOrBaseURL = "secure.gov"
|
||||||
let deferred = deferFulfillment(context.observe(\.alertInfo)) { $0 != nil }
|
let deferred = deferFulfillment(context.observe(\.alertInfo)) { $0 != nil }
|
||||||
context.send(viewAction: .confirm)
|
context.send(viewAction: .confirm)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then selection should fail with an alert telling the user to download Element Pro.
|
// Then selection should fail with an alert telling the user to download Element Pro.
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
#expect(context.alertInfo?.id == .elementProAlert)
|
#expect(context.alertInfo?.id == .elementProAlert)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
|
|
||||||
// When attempting to discover an invalid server
|
// When attempting to discover an invalid server
|
||||||
var deferred = deferFulfillment(context.observe(\.viewState.isShowingFooterError)) { $0 }
|
var deferred = deferFulfillment(context.observe(\.viewState.isShowingFooterError)) { $0 }
|
||||||
context.homeserverAddress = "idontexist"
|
context.serverNameOrBaseURL = "idontexist"
|
||||||
context.send(viewAction: .confirm)
|
context.send(viewAction: .confirm)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
|
|
||||||
// And when clearing the error.
|
// And when clearing the error.
|
||||||
deferred = deferFulfillment(context.observe(\.viewState.isShowingFooterError)) { !$0 }
|
deferred = deferFulfillment(context.observe(\.viewState.isShowingFooterError)) { !$0 }
|
||||||
context.homeserverAddress = ""
|
context.serverNameOrBaseURL = ""
|
||||||
context.send(viewAction: .clearFooterError)
|
context.send(viewAction: .clearFooterError)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
@@ -175,9 +175,9 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
mutating func userInputPasswordLoginWithoutConfiguration() async throws {
|
mutating func userInputPasswordLoginWithoutConfiguration() async throws {
|
||||||
// Given a view model for login using a service that hasn't been configured against a server that doesn't support OAuth.
|
// Given a view model for login using a service that hasn't been configured against a server that doesn't support OAuth.
|
||||||
try setup(authenticationFlow: .login)
|
try setup(authenticationFlow: .login)
|
||||||
context.homeserverAddress = Self.passwordOnlyServer
|
context.serverNameOrBaseURL = Self.passwordOnlyServer
|
||||||
#expect(service.homeserver.value.loginMode == .unknown)
|
#expect(service.homeserver.value.loginMode == .unknown)
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
||||||
|
|
||||||
// When confirming from the server selection screen.
|
// When confirming from the server selection screen.
|
||||||
let deferred = deferFulfillment(viewModel.actions) { $0.isContinueWithPassword }
|
let deferred = deferFulfillment(viewModel.actions) { $0.isContinueWithPassword }
|
||||||
@@ -185,7 +185,7 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then the service should be configured but no OAuth URL fetched.
|
// Then the service should be configured but no OAuth URL fetched.
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
#expect(service.homeserver.value.loginMode == .password)
|
#expect(service.homeserver.value.loginMode == .password)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,13 +193,13 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
mutating func userInputPasswordLoginAfterConfiguration() async throws {
|
mutating func userInputPasswordLoginAfterConfiguration() async throws {
|
||||||
// Given a view model for login using a service that has already been configured against a server that doesn't support OAuth.
|
// Given a view model for login using a service that has already been configured against a server that doesn't support OAuth.
|
||||||
try setup(authenticationFlow: .login)
|
try setup(authenticationFlow: .login)
|
||||||
context.homeserverAddress = Self.passwordOnlyServer
|
context.serverNameOrBaseURL = Self.passwordOnlyServer
|
||||||
guard case .success = await service.configure(for: context.homeserverAddress, flow: .login) else {
|
guard case .success = await service.configure(for: context.serverNameOrBaseURL, flow: .login) else {
|
||||||
Issue.record("The configuration should succeed.")
|
Issue.record("The configuration should succeed.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
#expect(service.homeserver.value.loginMode == .password)
|
#expect(service.homeserver.value.loginMode == .password)
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
|
|
||||||
// When confirming from the server selection screen.
|
// When confirming from the server selection screen.
|
||||||
let deferred = deferFulfillment(viewModel.actions) { $0.isContinueWithPassword }
|
let deferred = deferFulfillment(viewModel.actions) { $0.isContinueWithPassword }
|
||||||
@@ -207,7 +207,7 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then the service should be re-configured but no OAuth URL fetched.
|
// Then the service should be re-configured but no OAuth URL fetched.
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 2)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Picker mode
|
// MARK: - Picker mode
|
||||||
@@ -218,7 +218,7 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
try setup(authenticationFlow: .login, mode: .picker(Self.pickerProviders))
|
try setup(authenticationFlow: .login, mode: .picker(Self.pickerProviders))
|
||||||
#expect(service.homeserver.value.loginMode == .unknown)
|
#expect(service.homeserver.value.loginMode == .unknown)
|
||||||
#expect(context.viewState.mode == .picker(Self.pickerProviders))
|
#expect(context.viewState.mode == .picker(Self.pickerProviders))
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 0)
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 0)
|
||||||
|
|
||||||
// When confirming from the picker.
|
// When confirming from the picker.
|
||||||
@@ -227,7 +227,7 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then the service should be configured and the OAuth URL fetched.
|
// Then the service should be configured and the OAuth URL fetched.
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 1)
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesCallsCount == 1)
|
||||||
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.prompt == .consent)
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.prompt == .consent)
|
||||||
#expect(service.homeserver.value.loginMode == .oAuth(supportsCreatePrompt: true))
|
#expect(service.homeserver.value.loginMode == .oAuth(supportsCreatePrompt: true))
|
||||||
@@ -237,9 +237,9 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
mutating func pickerForPasswordLoginWithoutConfiguration() async throws {
|
mutating func pickerForPasswordLoginWithoutConfiguration() async throws {
|
||||||
// Given a view model for login using a service that hasn't been configured against a server that doesn't support OAuth.
|
// Given a view model for login using a service that hasn't been configured against a server that doesn't support OAuth.
|
||||||
try setup(authenticationFlow: .login, mode: .picker(Self.pickerProviders))
|
try setup(authenticationFlow: .login, mode: .picker(Self.pickerProviders))
|
||||||
context.homeserverAddress = Self.passwordOnlyServer
|
context.serverNameOrBaseURL = Self.passwordOnlyServer
|
||||||
#expect(service.homeserver.value.loginMode == .unknown)
|
#expect(service.homeserver.value.loginMode == .unknown)
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 0)
|
||||||
|
|
||||||
// When confirming from the picker.
|
// When confirming from the picker.
|
||||||
let deferred = deferFulfillment(viewModel.actions) { $0.isContinueWithPassword }
|
let deferred = deferFulfillment(viewModel.actions) { $0.isContinueWithPassword }
|
||||||
@@ -247,7 +247,7 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then the service should be configured but no OAuth URL fetched.
|
// Then the service should be configured but no OAuth URL fetched.
|
||||||
#expect(clientFactory.makeAuthenticationClientHomeserverAddressSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
#expect(service.homeserver.value.loginMode == .password)
|
#expect(service.homeserver.value.loginMode == .password)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
// Given a view model with a previous server in history.
|
// Given a view model with a previous server in history.
|
||||||
try setup(authenticationFlow: .login)
|
try setup(authenticationFlow: .login)
|
||||||
appSettings.previousServers = ["myserver.com"]
|
appSettings.previousServers = ["myserver.com"]
|
||||||
context.homeserverAddress = ""
|
context.serverNameOrBaseURL = ""
|
||||||
let textField = UITextField()
|
let textField = UITextField()
|
||||||
context.send(viewAction: .updateTextField(textField))
|
context.send(viewAction: .updateTextField(textField))
|
||||||
|
|
||||||
@@ -268,14 +268,14 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
let typed = typedSoFar + nextCharacter
|
let typed = typedSoFar + nextCharacter
|
||||||
let expectedAddress = "myserver.com"
|
let expectedAddress = "myserver.com"
|
||||||
textField.text = typedSoFar
|
textField.text = typedSoFar
|
||||||
let deferred = deferFulfillment(context.observe(\.homeserverAddress)) { $0 == expectedAddress }
|
let deferred = deferFulfillment(context.observe(\.serverNameOrBaseURL)) { $0 == expectedAddress }
|
||||||
_ = textField.delegate?.textField?(textField, shouldChangeCharactersIn: NSRange(location: typedSoFar.count, length: 0), replacementString: nextCharacter)
|
_ = textField.delegate?.textField?(textField, shouldChangeCharactersIn: NSRange(location: typedSoFar.count, length: 0), replacementString: nextCharacter)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then the address should be completed and the appended portion selected.
|
// Then the address should be completed and the appended portion selected.
|
||||||
let selectionStart = expectedAddress.index(expectedAddress.startIndex, offsetBy: typed.count)
|
let selectionStart = expectedAddress.index(expectedAddress.startIndex, offsetBy: typed.count)
|
||||||
#expect(context.homeserverAddress == expectedAddress)
|
#expect(context.serverNameOrBaseURL == expectedAddress)
|
||||||
#expect(context.homeserverSelection == TextSelection(range: selectionStart..<expectedAddress.endIndex))
|
#expect(context.serverNameOrBaseURLSelection == TextSelection(range: selectionStart..<expectedAddress.endIndex))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -283,7 +283,7 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
// Given a view model with no previous server history, falling back to the default account providers.
|
// Given a view model with no previous server history, falling back to the default account providers.
|
||||||
try setup(authenticationFlow: .login)
|
try setup(authenticationFlow: .login)
|
||||||
#expect(appSettings.previousServers.isEmpty)
|
#expect(appSettings.previousServers.isEmpty)
|
||||||
context.homeserverAddress = ""
|
context.serverNameOrBaseURL = ""
|
||||||
let textField = UITextField()
|
let textField = UITextField()
|
||||||
context.send(viewAction: .updateTextField(textField))
|
context.send(viewAction: .updateTextField(textField))
|
||||||
|
|
||||||
@@ -293,14 +293,14 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
let typed = typedSoFar + nextCharacter
|
let typed = typedSoFar + nextCharacter
|
||||||
let expectedAddress = "matrix.org"
|
let expectedAddress = "matrix.org"
|
||||||
textField.text = typedSoFar
|
textField.text = typedSoFar
|
||||||
let deferred = deferFulfillment(context.observe(\.homeserverAddress)) { $0 == expectedAddress }
|
let deferred = deferFulfillment(context.observe(\.serverNameOrBaseURL)) { $0 == expectedAddress }
|
||||||
_ = textField.delegate?.textField?(textField, shouldChangeCharactersIn: NSRange(location: typedSoFar.count, length: 0), replacementString: nextCharacter)
|
_ = textField.delegate?.textField?(textField, shouldChangeCharactersIn: NSRange(location: typedSoFar.count, length: 0), replacementString: nextCharacter)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
// Then the address should be completed and the appended portion selected.
|
// Then the address should be completed and the appended portion selected.
|
||||||
let selectionStart = expectedAddress.index(expectedAddress.startIndex, offsetBy: typed.count)
|
let selectionStart = expectedAddress.index(expectedAddress.startIndex, offsetBy: typed.count)
|
||||||
#expect(context.homeserverAddress == expectedAddress)
|
#expect(context.serverNameOrBaseURL == expectedAddress)
|
||||||
#expect(context.homeserverSelection == TextSelection(range: selectionStart..<expectedAddress.endIndex))
|
#expect(context.serverNameOrBaseURLSelection == TextSelection(range: selectionStart..<expectedAddress.endIndex))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -308,7 +308,7 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
// Given a view model with a known set of servers.
|
// Given a view model with a known set of servers.
|
||||||
try setup(authenticationFlow: .login)
|
try setup(authenticationFlow: .login)
|
||||||
appSettings.previousServers = ["myserver.com"]
|
appSettings.previousServers = ["myserver.com"]
|
||||||
context.homeserverAddress = ""
|
context.serverNameOrBaseURL = ""
|
||||||
let textField = UITextField()
|
let textField = UITextField()
|
||||||
context.send(viewAction: .updateTextField(textField))
|
context.send(viewAction: .updateTextField(textField))
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
try await Task.sleep(for: .milliseconds(50))
|
try await Task.sleep(for: .milliseconds(50))
|
||||||
|
|
||||||
// Then the address should remain unchanged.
|
// Then the address should remain unchanged.
|
||||||
#expect(context.homeserverAddress == "")
|
#expect(context.serverNameOrBaseURL == "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -326,30 +326,30 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
// Given a view model with an ordered server history.
|
// Given a view model with an ordered server history.
|
||||||
try setup(authenticationFlow: .login)
|
try setup(authenticationFlow: .login)
|
||||||
appSettings.previousServers = ["abc.foo", "matrix.org", "mantis.asdf"]
|
appSettings.previousServers = ["abc.foo", "matrix.org", "mantis.asdf"]
|
||||||
context.homeserverAddress = ""
|
context.serverNameOrBaseURL = ""
|
||||||
let textField = UITextField()
|
let textField = UITextField()
|
||||||
context.send(viewAction: .updateTextField(textField))
|
context.send(viewAction: .updateTextField(textField))
|
||||||
|
|
||||||
// Step 1: type "m" — first "m" match in history is "matrix.org".
|
// Step 1: type "m" — first "m" match in history is "matrix.org".
|
||||||
textField.text = ""
|
textField.text = ""
|
||||||
var deferred = deferFulfillment(context.observe(\.homeserverAddress)) { $0 == "matrix.org" }
|
var deferred = deferFulfillment(context.observe(\.serverNameOrBaseURL)) { $0 == "matrix.org" }
|
||||||
_ = textField.delegate?.textField?(textField, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "m")
|
_ = textField.delegate?.textField?(textField, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "m")
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
#expect(context.homeserverAddress == "matrix.org")
|
#expect(context.serverNameOrBaseURL == "matrix.org")
|
||||||
|
|
||||||
// Step 2: type "a" replacing the highlighted suffix — still matches "matrix.org".
|
// Step 2: type "a" replacing the highlighted suffix — still matches "matrix.org".
|
||||||
// textField.text = "matrix.org", "atrix.org" highlighted (loc: 1, len: 9).
|
// textField.text = "matrix.org", "atrix.org" highlighted (loc: 1, len: 9).
|
||||||
deferred = deferFulfillment(context.observe(\.homeserverAddress)) { $0 == "matrix.org" }
|
deferred = deferFulfillment(context.observe(\.serverNameOrBaseURL)) { $0 == "matrix.org" }
|
||||||
_ = textField.delegate?.textField?(textField, shouldChangeCharactersIn: NSRange(location: 1, length: 9), replacementString: "a")
|
_ = textField.delegate?.textField?(textField, shouldChangeCharactersIn: NSRange(location: 1, length: 9), replacementString: "a")
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
#expect(context.homeserverAddress == "matrix.org")
|
#expect(context.serverNameOrBaseURL == "matrix.org")
|
||||||
|
|
||||||
// Step 3: type "n" replacing the highlighted suffix — "man" now matches "mantis.asdf".
|
// Step 3: type "n" replacing the highlighted suffix — "man" now matches "mantis.asdf".
|
||||||
// textField.text = "matrix.org", "trix.org" highlighted (loc: 2, len: 8).
|
// textField.text = "matrix.org", "trix.org" highlighted (loc: 2, len: 8).
|
||||||
deferred = deferFulfillment(context.observe(\.homeserverAddress)) { $0 == "mantis.asdf" }
|
deferred = deferFulfillment(context.observe(\.serverNameOrBaseURL)) { $0 == "mantis.asdf" }
|
||||||
_ = textField.delegate?.textField?(textField, shouldChangeCharactersIn: NSRange(location: 2, length: 8), replacementString: "n")
|
_ = textField.delegate?.textField?(textField, shouldChangeCharactersIn: NSRange(location: 2, length: 8), replacementString: "n")
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
#expect(context.homeserverAddress == "mantis.asdf")
|
#expect(context.serverNameOrBaseURL == "mantis.asdf")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|||||||
Reference in New Issue
Block a user