diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index cf1ba8eb37..4f0268611d 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -67,7 +67,6 @@ 09713669577CDA8D012EE380 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 6647C55D93508C7CE9D954A5 /* MatrixRustSDK */; }; 09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */; }; 09D3D7D115318CAD131B4FE7 /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57084488B03BDB33C7B7CA0E /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift */; }; - 0A0625A271EE5B06D2AAA069 /* HomeScreenSlidingSyncMigrationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4691B8DE1D51DE152680098A /* HomeScreenSlidingSyncMigrationBanner.swift */; }; 0A194F5E70B5A628C1BF4476 /* AdvancedSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4999B5FD50AED7CB0F590FF8 /* AdvancedSettingsScreenModels.swift */; }; 0ACAA31FD0399CEEBA3ECC21 /* UserDetailsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85149F56BA333619900E2410 /* UserDetailsEditScreenViewModelProtocol.swift */; }; 0AD8EF040A60D62F488C18B5 /* KnockRequestProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F957320D0EB7D7B4E30C79D /* KnockRequestProxyMock.swift */; }; @@ -1399,6 +1398,7 @@ 0B0E0B55E2EE75AF67029924 /* SwipeToReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToReplyView.swift; sourceTree = ""; }; 0B32BBA8887BD7A5C4ECF16F /* RoomModerationRole.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomModerationRole.swift; sourceTree = ""; }; 0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineItem.swift; sourceTree = ""; }; + 0BA7D6C94A50428463D09AF0 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/InfoPlist.strings; sourceTree = ""; }; 0BB05221D7D941CC82DC8480 /* LogViewerScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenViewModel.swift; sourceTree = ""; }; 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModel.swift; sourceTree = ""; }; 0BD116096CAA9139B95EEA9C /* UserProfileScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileScreenViewModel.swift; sourceTree = ""; }; @@ -1694,7 +1694,6 @@ 4629710C0337ADD9C8909542 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/Localizable.strings; sourceTree = ""; }; 466C71A0FED9BFF287613C82 /* RoomDetailsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenModels.swift; sourceTree = ""; }; 467498BEA681758BE2F80826 /* TimelineMediaPreviewDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaPreviewDetailsView.swift; sourceTree = ""; }; - 4691B8DE1D51DE152680098A /* HomeScreenSlidingSyncMigrationBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenSlidingSyncMigrationBanner.swift; sourceTree = ""; }; 46A2AD86F7E618F468F6FAF5 /* VoiceMessageRecordingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingButton.swift; sourceTree = ""; }; 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = ""; }; 46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreen.swift; sourceTree = ""; }; @@ -1752,6 +1751,7 @@ 52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenModels.swift; sourceTree = ""; }; 5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreen.swift; sourceTree = ""; }; 5281C5CDC4A712265A0B5FBF /* PollRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollRoomTimelineItem.swift; sourceTree = ""; }; + 529513218340CC8419273165 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedString.swift; sourceTree = ""; }; 52F5EE5DE3B55D59299DB5BC /* AppLockSetupBiometricsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenViewModelTests.swift; sourceTree = ""; }; 53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewAdapter.swift; sourceTree = ""; }; @@ -2315,6 +2315,7 @@ C9F893F4A111CB7BA5C96949 /* AppLockSetupBiometricsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenViewModel.swift; sourceTree = ""; }; CA28F29C9F93E93CC3C2C715 /* NavigationRootCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRootCoordinator.swift; sourceTree = ""; }; CA29952595B804DA221A0C1D /* ComposerToolbarViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModelTests.swift; sourceTree = ""; }; + CA46BDF38F87118939BDF659 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = ""; }; CA4F6D7000EDCD187E0989E7 /* PinnedEventsTimelineScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineScreen.swift; sourceTree = ""; }; CA89A2DD51B6BBE1DA55E263 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; CA90BD288E5AE6BC643AFDDF /* TemplateScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenCoordinator.swift; sourceTree = ""; }; @@ -2409,6 +2410,7 @@ E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = ""; }; E0FF9CB3EFA753277291F609 /* EncryptionResetScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreenCoordinator.swift; sourceTree = ""; }; E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileListRow.swift; sourceTree = ""; }; + E157152B11E347F735C3FD6E /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = tr; path = tr.lproj/Localizable.stringsdict; sourceTree = ""; }; E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenCoordinator.swift; sourceTree = ""; }; E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = ""; }; E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = ""; }; @@ -3764,7 +3766,6 @@ 05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */, ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */, C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */, - 4691B8DE1D51DE152680098A /* HomeScreenSlidingSyncMigrationBanner.swift */, 84AF32E4136FD6F159D86C2C /* RoomDirectorySearchView.swift */, 037A5661B26EC6BE068188D7 /* Filters */, ); @@ -6296,6 +6297,7 @@ ru, sk, sv, + tr, uk, uz, "zh-Hans", @@ -7077,7 +7079,6 @@ B04E9EB589CE99C3929E817A /* HomeScreenRecoveryKeyConfirmationBanner.swift in Sources */, 0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */, A10D6CCDE2010C09EEA1A593 /* HomeScreenRoomList.swift in Sources */, - 0A0625A271EE5B06D2AAA069 /* HomeScreenSlidingSyncMigrationBanner.swift in Sources */, DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */, 56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */, 2BBE320EE426A347AAE5C7DA /* IdentityConfirmationScreen.swift in Sources */, @@ -7832,6 +7833,7 @@ E5F2B6443D1ED8602F328539 /* ru */, 667DD3A9D932D7D9EB380CAA /* sk */, 0EE9EAF0309A2A1D67D8FAF5 /* sv */, + E157152B11E347F735C3FD6E /* tr */, 5F12E996BFBEB43815189ABF /* uk */, DFFB0E7C6D8E190AFA0176DC /* uz */, AB26D5444A4A7E095222DE8B /* zh-Hans */, @@ -7868,6 +7870,7 @@ E8294DB9E95C0C0630418466 /* ru */, AD378D580A41E42560C60E9C /* sk */, ACA11F7F50A4A3887A18CA5A /* sv */, + 529513218340CC8419273165 /* tr */, ADCB8A232D3A8FB3E16A7303 /* uk */, 475EB595D7527E9A8A14043E /* uz */, 284FEEB0789B8894E52A7F34 /* zh-Hans */, @@ -7892,6 +7895,7 @@ 1D652E78832289CD9EB64488 /* hu */, 7199693797B66245EF97BCF5 /* id */, 44C314C00533E2C297796B60 /* it */, + 0BA7D6C94A50428463D09AF0 /* ka */, E60757AFE04391B43EA568B8 /* nl */, 997BF045585AF6DB2EBC5755 /* pl */, A8DF55467ED4CE76B7AE9A33 /* pt */, @@ -7900,6 +7904,7 @@ 9B7D8D3638864B7482E148CC /* ru */, 7D39AF1F659923D77778511E /* sk */, 969694F67E844FCA51F7E051 /* sv */, + CA46BDF38F87118939BDF659 /* tr */, D66B5D86A9AB95E0E01BED82 /* uk */, FF720BA68256297680980481 /* zh-Hans */, 0545AC444BEEA89FF8C509FD /* zh-Hant-TW */, @@ -8521,7 +8526,7 @@ repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = 25.02.11; + version = 25.02.17; }; }; 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b3da9489d2..5f4cb1c920 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -149,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/element-hq/matrix-rust-components-swift", "state" : { - "revision" : "cc010fc6971370d1df2c0eb67cc5cfd577465b62", - "version" : "25.2.11" + "revision" : "422d7bef3ffd3edcb3e30afb410ee523f5659adc", + "version" : "25.2.17" } }, { diff --git a/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings b/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings index 27004131cb..4c62bba790 100644 --- a/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings @@ -120,6 +120,7 @@ "action_yes" = "Yes"; "action_yes_try_again" = "Yes, try again"; "banner_migrate_to_native_sliding_sync_action" = "Log Out & Upgrade"; +"banner_migrate_to_native_sliding_sync_app_force_logout_title" = "Element X no longer supports the old protocol. Please log out and log back in to continue using the app."; "banner_migrate_to_native_sliding_sync_description" = "Your server now supports a new, faster protocol. Log out and log back in to upgrade now. Doing this now will help you avoid a forced logout when the old protocol is removed later."; "banner_migrate_to_native_sliding_sync_force_logout_title" = "Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app."; "banner_migrate_to_native_sliding_sync_title" = "Upgrade available"; diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index 6ac61740de..8d5480c949 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -120,6 +120,7 @@ "action_yes" = "Yes"; "action_yes_try_again" = "Yes, try again"; "banner_migrate_to_native_sliding_sync_action" = "Log Out & Upgrade"; +"banner_migrate_to_native_sliding_sync_app_force_logout_title" = "Element X no longer supports the old protocol. Please log out and log back in to continue using the app."; "banner_migrate_to_native_sliding_sync_description" = "Your server now supports a new, faster protocol. Log out and log back in to upgrade now. Doing this now will help you avoid a forced logout when the old protocol is removed later."; "banner_migrate_to_native_sliding_sync_force_logout_title" = "Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app."; "banner_migrate_to_native_sliding_sync_title" = "Upgrade available"; diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index b52c0a108f..0f998a65c9 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -44,7 +44,6 @@ final class AppSettings { case elementCallBaseURLOverride // Feature flags - case slidingSyncDiscovery case publicSearchEnabled case fuzzyRoomListSearchEnabled case enableOnlySignedDeviceIsolationMode @@ -283,10 +282,6 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.fuzzyRoomListSearchEnabled, defaultValue: false, storageType: .userDefaults(store)) var fuzzyRoomListSearchEnabled - enum SlidingSyncDiscovery: Codable { case proxy, native, forceNative } - @UserPreference(key: UserDefaultsKeys.slidingSyncDiscovery, defaultValue: .native, storageType: .userDefaults(store)) - var slidingSyncDiscovery: SlidingSyncDiscovery - @UserPreference(key: UserDefaultsKeys.knockingEnabled, defaultValue: false, storageType: .userDefaults(store)) var knockingEnabled diff --git a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift index 83de2da9ae..add35a62ff 100644 --- a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift @@ -230,7 +230,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { } private func presentDeveloperOptions() { - let coordinator = DeveloperOptionsScreenCoordinator(isUsingNativeSlidingSync: parameters.userSession.clientProxy.slidingSyncVersion == .native) + let coordinator = DeveloperOptionsScreenCoordinator() coordinator.actions .sink { [weak self] action in diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index ffba954483..7a40a677e1 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -274,6 +274,8 @@ internal enum L10n { internal static var actionYesTryAgain: String { return L10n.tr("Localizable", "action_yes_try_again") } /// Log Out & Upgrade internal static var bannerMigrateToNativeSlidingSyncAction: String { return L10n.tr("Localizable", "banner_migrate_to_native_sliding_sync_action") } + /// Element X no longer supports the old protocol. Please log out and log back in to continue using the app. + internal static var bannerMigrateToNativeSlidingSyncAppForceLogoutTitle: String { return L10n.tr("Localizable", "banner_migrate_to_native_sliding_sync_app_force_logout_title") } /// Your server now supports a new, faster protocol. Log out and log back in to upgrade now. Doing this now will help you avoid a forced logout when the old protocol is removed later. internal static var bannerMigrateToNativeSlidingSyncDescription: String { return L10n.tr("Localizable", "banner_migrate_to_native_sliding_sync_description") } /// Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app. diff --git a/ElementX/Sources/Mocks/ClientProxyMock.swift b/ElementX/Sources/Mocks/ClientProxyMock.swift index cc96967d09..6525c2131d 100644 --- a/ElementX/Sources/Mocks/ClientProxyMock.swift +++ b/ElementX/Sources/Mocks/ClientProxyMock.swift @@ -69,10 +69,8 @@ extension ClientProxyMock { ignoreUserReturnValue = .success(()) unignoreUserReturnValue = .success(()) + needsSlidingSyncMigration = false slidingSyncVersion = .native - availableSlidingSyncVersionsClosure = { - [] - } trackRecentlyVisitedRoomReturnValue = .success(()) recentlyVisitedRoomsReturnValue = .success([]) diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 35403c618c..0c57901e6f 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -2235,28 +2235,16 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable { set(value) { underlyingHomeserver = value } } var underlyingHomeserver: String! + var needsSlidingSyncMigration: Bool { + get { return underlyingNeedsSlidingSyncMigration } + set(value) { underlyingNeedsSlidingSyncMigration = value } + } + var underlyingNeedsSlidingSyncMigration: Bool! var slidingSyncVersion: SlidingSyncVersion { get { return underlyingSlidingSyncVersion } set(value) { underlyingSlidingSyncVersion = value } } var underlyingSlidingSyncVersion: SlidingSyncVersion! - var availableSlidingSyncVersionsCallsCount = 0 - var availableSlidingSyncVersionsCalled: Bool { - return availableSlidingSyncVersionsCallsCount > 0 - } - - var availableSlidingSyncVersions: [SlidingSyncVersion] { - get async { - availableSlidingSyncVersionsCallsCount += 1 - if let availableSlidingSyncVersionsClosure = availableSlidingSyncVersionsClosure { - return await availableSlidingSyncVersionsClosure() - } else { - return underlyingAvailableSlidingSyncVersions - } - } - } - var underlyingAvailableSlidingSyncVersions: [SlidingSyncVersion]! - var availableSlidingSyncVersionsClosure: (() async -> [SlidingSyncVersion])? var canDeactivateAccount: Bool { get { return underlyingCanDeactivateAccount } set(value) { underlyingCanDeactivateAccount = value } diff --git a/ElementX/Sources/Other/Extensions/ClientBuilder.swift b/ElementX/Sources/Other/Extensions/ClientBuilder.swift index 2cce00e9ea..59e8472c03 100644 --- a/ElementX/Sources/Other/Extensions/ClientBuilder.swift +++ b/ElementX/Sources/Other/Extensions/ClientBuilder.swift @@ -27,9 +27,7 @@ extension ClientBuilder { builder = switch slidingSync { case .restored: builder - case .discoverProxy: builder.slidingSyncVersionBuilder(versionBuilder: .discoverProxy) - case .discoverNative: builder.slidingSyncVersionBuilder(versionBuilder: .discoverNative) - case .forceNative: builder.slidingSyncVersionBuilder(versionBuilder: .native) + case .discover: builder.slidingSyncVersionBuilder(versionBuilder: .discoverNative) } if setupEncryption { @@ -58,12 +56,8 @@ extension ClientBuilder { } enum ClientBuilderSlidingSync { - /// The proxy will be supplied when restoring the Session. + /// Sliding sync will be configured when restoring the Session. case restored - /// A proxy must be discovered whilst building the session. - case discoverProxy - /// Native sliding sync must be discovered whilst building the session. - case discoverNative - /// Forces native sliding sync without discovering it. - case forceNative + /// Sliding sync must be discovered whilst building the session. + case discover } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index ea1851b82f..0e57a98aef 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -36,8 +36,6 @@ enum HomeScreenViewAction { case confirmRecoveryKey case resetEncryption case skipRecoveryKeyConfirmation - case confirmSlidingSyncUpgrade - case skipSlidingSyncUpgrade case updateVisibleItemRange(Range) case globalSearch case markRoomAsUnread(roomIdentifier: String) @@ -86,17 +84,12 @@ enum HomeScreenSecurityBannerMode: Equatable { } } -enum HomeScreenMigrationBannerMode { - case none, show, dismissed -} - struct HomeScreenViewState: BindableState { let userID: String var userDisplayName: String? var userAvatarURL: URL? var securityBannerMode = HomeScreenSecurityBannerMode.none - var slidingSyncMigrationBannerMode = HomeScreenMigrationBannerMode.none var requiresExtraAccountSetup = false diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 6bcbd210e4..7d7bcd5fd0 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -145,11 +145,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol actionsSubject.send(.presentEncryptionResetScreen) case .skipRecoveryKeyConfirmation: state.securityBannerMode = .dismissed - case .confirmSlidingSyncUpgrade: - appSettings.slidingSyncDiscovery = .native - actionsSubject.send(.logout) - case .skipSlidingSyncUpgrade: - state.slidingSyncMigrationBannerMode = .dismissed case .updateVisibleItemRange(let range): roomSummaryProvider?.updateVisibleRange(range) case .startChat: @@ -307,30 +302,18 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol /// Check whether we can inform the user about potential migrations /// or have him logout as his proxy is no longer available private func checkSlidingSyncMigration() async { - // Not logged in with a proxy, don't need to do anything - guard userSession.clientProxy.slidingSyncVersion.isProxy else { + guard userSession.clientProxy.needsSlidingSyncMigration else { return } - let versions = await userSession.clientProxy.availableSlidingSyncVersions - - // Native not available, nothing we can do - guard versions.contains(.native) else { - return - } - - if versions.contains(where: \.isProxy) { // Both available, prompt for migration - state.slidingSyncMigrationBannerMode = .show - } else { // The proxy has been removed and logout is needed - // Delay setting the alert otherwise it automatically gets dismissed. Same as the crashed last run one - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.state.bindings.alertInfo = AlertInfo(id: UUID(), - title: L10n.bannerMigrateToNativeSlidingSyncForceLogoutTitle, - primaryButton: .init(title: L10n.bannerMigrateToNativeSlidingSyncAction) { [weak self] in - self?.appSettings.slidingSyncDiscovery = .native - self?.actionsSubject.send(.logoutWithoutConfirmation) - }) - } + // The proxy is no longer supported so a logout is needed. + // Delay setting the alert otherwise it automatically gets dismissed. Same as the crashed last run one + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.state.bindings.alertInfo = AlertInfo(id: UUID(), + title: L10n.bannerMigrateToNativeSlidingSyncAppForceLogoutTitle, + primaryButton: .init(title: L10n.bannerMigrateToNativeSlidingSyncAction) { [weak self] in + self?.actionsSubject.send(.logoutWithoutConfirmation) + }) } } @@ -460,14 +443,3 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol message: L10n.errorUnknown) } } - -extension SlidingSyncVersion { - var isProxy: Bool { - switch self { - case .proxy: - return true - default: - return false - } - } -} diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift index f8302b6893..3f7fa98bab 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift @@ -119,17 +119,13 @@ struct HomeScreenContent: View { @ViewBuilder private var topSection: some View { // An empty VStack causes glitches within the room list - if context.viewState.shouldShowFilters || - context.viewState.securityBannerMode.isShown || - context.viewState.slidingSyncMigrationBannerMode == .show { + if context.viewState.shouldShowFilters || context.viewState.securityBannerMode.isShown { VStack(spacing: 0) { if context.viewState.shouldShowFilters { RoomListFiltersView(state: $context.filtersState) } - if context.viewState.slidingSyncMigrationBannerMode == .show { - HomeScreenSlidingSyncMigrationBanner(context: context) - } else if case let .show(state) = context.viewState.securityBannerMode { + if case let .show(state) = context.viewState.securityBannerMode { HomeScreenRecoveryKeyConfirmationBanner(state: state, context: context) } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenSlidingSyncMigrationBanner.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenSlidingSyncMigrationBanner.swift deleted file mode 100644 index f6742569ab..0000000000 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenSlidingSyncMigrationBanner.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright 2024 New Vector Ltd. -// -// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial -// Please see LICENSE files in the repository root for full details. -// - -import Combine -import SwiftUI - -struct HomeScreenSlidingSyncMigrationBanner: View { - var context: HomeScreenViewModel.Context - - var body: some View { - VStack(alignment: .leading, spacing: 16) { - VStack(alignment: .leading, spacing: 4) { - HStack(spacing: 16) { - Text(L10n.bannerMigrateToNativeSlidingSyncTitle) - .font(.compound.bodyLGSemibold) - .foregroundColor(.compound.textPrimary) - - Spacer() - - Button { - context.send(viewAction: .skipSlidingSyncUpgrade) - } label: { - Image(systemName: "xmark") - .foregroundColor(.compound.iconSecondary) - .frame(width: 12, height: 12) - } - } - Text(L10n.bannerMigrateToNativeSlidingSyncDescription) - .font(.compound.bodyMD) - .foregroundColor(.compound.textSecondary) - } - - Button(L10n.bannerMigrateToNativeSlidingSyncAction) { - context.send(viewAction: .confirmSlidingSyncUpgrade) - } - .frame(maxWidth: .infinity) - .buttonStyle(.compound(.primary, size: .medium)) - } - .padding(16) - .background(Color.compound.bgSubtleSecondary) - .cornerRadius(14) - .padding(.horizontal, 16) - } -} - -struct HomeScreenSlidingSyncMigrationBanner_Previews: PreviewProvider, TestablePreview { - static let viewModel = buildViewModel() - - static var previews: some View { - HomeScreenSlidingSyncMigrationBanner(context: viewModel.context) - } - - static func buildViewModel() -> HomeScreenViewModel { - let clientProxy = ClientProxyMock(.init()) - - let userSession = UserSessionMock(.init(clientProxy: clientProxy)) - - return HomeScreenViewModel(userSession: userSession, - analyticsService: ServiceLocator.shared.analytics, - appSettings: ServiceLocator.shared.settings, - selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), - userIndicatorController: ServiceLocator.shared.userIndicatorController) - } -} diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift index b047572c6d..415b47d5c8 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift @@ -22,10 +22,9 @@ final class DeveloperOptionsScreenCoordinator: CoordinatorProtocol { actionsSubject.eraseToAnyPublisher() } - init(isUsingNativeSlidingSync: Bool) { + init() { viewModel = DeveloperOptionsScreenViewModel(developerOptions: ServiceLocator.shared.settings, - elementCallBaseURL: ServiceLocator.shared.settings.elementCallBaseURL, - isUsingNativeSlidingSync: isUsingNativeSlidingSync) + elementCallBaseURL: ServiceLocator.shared.settings.elementCallBaseURL) viewModel.actions .sink { [weak self] action in diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index 80afeb5f97..743f6564a1 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -13,12 +13,7 @@ enum DeveloperOptionsScreenViewModelAction { struct DeveloperOptionsScreenViewState: BindableState { let elementCallBaseURL: URL - let isUsingNativeSlidingSync: Bool var bindings: DeveloperOptionsScreenViewStateBindings - - var slidingSyncFooter: String { - "The method used to configure sliding sync when signing in. Changing this setting has no effect until you sign out.\n\nYour current session is using \(isUsingNativeSlidingSync ? "native sliding sync." : "a sliding sync proxy.")" - } } // periphery: ignore - subscripts are seen as false positive @@ -42,7 +37,6 @@ enum DeveloperOptionsScreenViewAction { protocol DeveloperOptionsProtocol: AnyObject { var logLevel: LogLevel { get set } - var slidingSyncDiscovery: AppSettings.SlidingSyncDiscovery { get set } var publicSearchEnabled: Bool { get set } var hideUnreadMessagesBadge: Bool { get set } var fuzzyRoomListSearchEnabled: Bool { get set } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift index 2cfe60612b..a774975dce 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift @@ -17,9 +17,9 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve actionsSubject.eraseToAnyPublisher() } - init(developerOptions: DeveloperOptionsProtocol, elementCallBaseURL: URL, isUsingNativeSlidingSync: Bool) { + init(developerOptions: DeveloperOptionsProtocol, elementCallBaseURL: URL) { let bindings = DeveloperOptionsScreenViewStateBindings(developerOptions: developerOptions) - let state = DeveloperOptionsScreenViewState(elementCallBaseURL: elementCallBaseURL, isUsingNativeSlidingSync: isUsingNativeSlidingSync, bindings: bindings) + let state = DeveloperOptionsScreenViewState(elementCallBaseURL: elementCallBaseURL, bindings: bindings) super.init(initialViewState: state) } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index b1018943c1..308fa9b03e 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -32,18 +32,6 @@ struct DeveloperOptionsScreen: View { } } - Section { - Picker("Discovery", selection: $context.slidingSyncDiscovery) { - Text("Proxy only").tag(AppSettings.SlidingSyncDiscovery.proxy) - Text("Automatic").tag(AppSettings.SlidingSyncDiscovery.native) - Text("Force Native ⚠️").tag(AppSettings.SlidingSyncDiscovery.forceNative) - } - } header: { - Text("Sliding Sync") - } footer: { - Text(context.viewState.slidingSyncFooter) - } - Section("Room List") { Toggle(isOn: $context.publicSearchEnabled) { Text("Public search") @@ -175,8 +163,7 @@ private struct LogLevelConfigurationView: View { struct DeveloperOptionsScreen_Previews: PreviewProvider { static let viewModel = DeveloperOptionsScreenViewModel(developerOptions: ServiceLocator.shared.settings, - elementCallBaseURL: ServiceLocator.shared.settings.elementCallBaseURL, - isUsingNativeSlidingSync: true) + elementCallBaseURL: ServiceLocator.shared.settings.elementCallBaseURL) static var previews: some View { NavigationStack { DeveloperOptionsScreen(context: viewModel.context) diff --git a/ElementX/Sources/Services/Authentication/AuthenticationClientBuilder.swift b/ElementX/Sources/Services/Authentication/AuthenticationClientBuilder.swift index 0e63882170..233ac93877 100644 --- a/ElementX/Sources/Services/Authentication/AuthenticationClientBuilder.swift +++ b/ElementX/Sources/Services/Authentication/AuthenticationClientBuilder.swift @@ -27,55 +27,25 @@ struct AuthenticationClientBuilder: AuthenticationClientBuilderProtocol { /// Builds a Client for login using OIDC or password authentication. func build(homeserverAddress: String) async throws -> ClientProtocol { - if appSettings.slidingSyncDiscovery == .forceNative { - return try await makeClientBuilder(slidingSync: .forceNative).serverNameOrHomeserverUrl(serverNameOrUrl: homeserverAddress).build() - } - - if appSettings.slidingSyncDiscovery == .native { - do { - return try await makeClientBuilder(slidingSync: .discoverNative).serverNameOrHomeserverUrl(serverNameOrUrl: homeserverAddress).build() - } catch { - MXLog.warning("Native sliding sync not available: \(error)") - MXLog.info("Falling back to a sliding sync proxy.") - } - } - - return try await makeClientBuilder(slidingSync: .discoverProxy).serverNameOrHomeserverUrl(serverNameOrUrl: homeserverAddress).build() + try await makeClientBuilder().serverNameOrHomeserverUrl(serverNameOrUrl: homeserverAddress).build() } /// Builds a Client, authenticating with the given QR code data. func buildWithQRCode(qrCodeData: QrCodeData, oidcConfiguration: OIDCConfigurationProxy, progressListener: QrLoginProgressListenerProxy) async throws -> ClientProtocol { - if appSettings.slidingSyncDiscovery == .forceNative { - return try await makeClientBuilder(slidingSync: .forceNative).buildWithQrCode(qrCodeData: qrCodeData, - oidcConfiguration: oidcConfiguration.rustValue, - progressListener: progressListener) - } - - if appSettings.slidingSyncDiscovery == .native { - do { - return try await makeClientBuilder(slidingSync: .discoverNative).buildWithQrCode(qrCodeData: qrCodeData, - oidcConfiguration: oidcConfiguration.rustValue, - progressListener: progressListener) - } catch HumanQrLoginError.SlidingSyncNotAvailable { - MXLog.warning("Native sliding sync not available") - MXLog.info("Falling back to a sliding sync proxy.") - } - } - - return try await makeClientBuilder(slidingSync: .discoverProxy).buildWithQrCode(qrCodeData: qrCodeData, - oidcConfiguration: oidcConfiguration.rustValue, - progressListener: progressListener) + try await makeClientBuilder().buildWithQrCode(qrCodeData: qrCodeData, + oidcConfiguration: oidcConfiguration.rustValue, + progressListener: progressListener) } // MARK: - Private /// The base builder configuration used for authentication within the app. - private func makeClientBuilder(slidingSync: ClientBuilderSlidingSync) -> ClientBuilder { + private func makeClientBuilder() -> ClientBuilder { ClientBuilder .baseBuilder(httpProxy: appSettings.websiteURL.globalProxy, - slidingSync: slidingSync, + slidingSync: .discover, sessionDelegate: clientSessionDelegate, appHooks: appHooks, enableOnlySignedDeviceIsolationMode: appSettings.enableOnlySignedDeviceIsolationMode, diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 37e5a5a83b..10fced0d8c 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -19,7 +19,7 @@ class ClientProxy: ClientProxyProtocol { private let mediaLoader: MediaLoaderProtocol private let clientQueue: DispatchQueue - + private var roomListService: RoomListService? // periphery: ignore - only for retain private var roomListStateUpdateTaskHandle: TaskHandle? @@ -135,6 +135,7 @@ class ClientProxy: ClientProxyProtocol { private let sendQueueStatusSubject = CurrentValueSubject(false) init(client: ClientProtocol, + needsSlidingSyncMigration: Bool, networkMonitor: NetworkMonitorProtocol, appSettings: AppSettings) async { self.client = client @@ -148,6 +149,8 @@ class ClientProxy: ClientProxyProtocol { notificationSettings = NotificationSettingsProxy(notificationSettings: client.getNotificationSettings()) secureBackupController = SecureBackupController(encryption: client.encryption()) + + self.needsSlidingSyncMigration = needsSlidingSyncMigration delegateHandle = client.setDelegate(delegate: ClientDelegateWrapper { [weak self] isSoftLogout in self?.hasEncounteredAuthError = true @@ -221,16 +224,11 @@ class ClientProxy: ClientProxyProtocol { client.homeserver() } + let needsSlidingSyncMigration: Bool var slidingSyncVersion: SlidingSyncVersion { client.slidingSyncVersion() } - var availableSlidingSyncVersions: [SlidingSyncVersion] { - get async { - await client.availableSlidingSyncVersions() - } - } - var canDeactivateAccount: Bool { client.canDeactivateAccount() } @@ -263,6 +261,11 @@ class ClientProxy: ClientProxyProtocol { } func startSync() { + guard !needsSlidingSyncMigration else { + MXLog.warning("Ignoring request, this client needs to be migrated to native sliding sync.") + return + } + guard !hasEncounteredAuthError else { MXLog.warning("Ignoring request, this client has an unknown token.") return diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index acbe47284b..f659f5f3c6 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -75,9 +75,11 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { var deviceID: String? { get } var homeserver: String { get } - + + // TODO: This is a temporary value, in the future we should throw a migration error + // when decoding a session that contains a sliding sync proxy URL instead of restoring it. + var needsSlidingSyncMigration: Bool { get } var slidingSyncVersion: SlidingSyncVersion { get } - var availableSlidingSyncVersions: [SlidingSyncVersion] { get async } var canDeactivateAccount: Bool { get } diff --git a/ElementX/Sources/Services/Keychain/KeychainController.swift b/ElementX/Sources/Services/Keychain/KeychainController.swift index e137c0a3f4..ae03a46d8b 100644 --- a/ElementX/Sources/Services/Keychain/KeychainController.swift +++ b/ElementX/Sources/Services/Keychain/KeychainController.swift @@ -112,7 +112,8 @@ class KeychainController: KeychainControllerProtocol { let restorationToken = RestorationToken(session: session, sessionDirectories: oldToken.sessionDirectories, passphrase: oldToken.passphrase, - pusherNotificationClientIdentifier: oldToken.pusherNotificationClientIdentifier) + pusherNotificationClientIdentifier: oldToken.pusherNotificationClientIdentifier, + slidingSyncProxyURLString: oldToken.slidingSyncProxyURLString) setRestorationToken(restorationToken, forUsername: session.userId) } diff --git a/ElementX/Sources/Services/UserSession/RestorationToken.swift b/ElementX/Sources/Services/UserSession/RestorationToken.swift index cd145d8d63..e6e727df38 100644 --- a/ElementX/Sources/Services/UserSession/RestorationToken.swift +++ b/ElementX/Sources/Services/UserSession/RestorationToken.swift @@ -14,6 +14,13 @@ struct RestorationToken: Equatable { let passphrase: String? let pusherNotificationClientIdentifier: String? + /// The sliding sync proxy URL that was previously encoded in the Session. + /// This is temporary to help make a nicer user migration flow. In the future + /// we will throw when decoding sessions with a sliding sync proxy URL. + let slidingSyncProxyURLString: String? + /// Whether the token is for a session that is using the now unsupported sliding sync proxy. + var needsSlidingSyncMigration: Bool { slidingSyncProxyURLString != nil } + enum CodingKeys: CodingKey { case session case sessionDirectory @@ -27,7 +34,7 @@ extension RestorationToken: Codable { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let session = try container.decode(MatrixRustSDK.Session.self, forKey: .session) + let sessionWrapper = try container.decode(SessionWrapper.self, forKey: .session) let dataDirectory = try container.decodeIfPresent(URL.self, forKey: .sessionDirectory) let cacheDirectory = try container.decodeIfPresent(URL.self, forKey: .cacheDirectory) @@ -38,18 +45,19 @@ extension RestorationToken: Codable { SessionDirectories(dataDirectory: dataDirectory) } } else { - SessionDirectories(userID: session.userId) + SessionDirectories(userID: sessionWrapper.session.userId) } - self = try .init(session: session, + self = try .init(session: sessionWrapper.session, sessionDirectories: sessionDirectories, passphrase: container.decodeIfPresent(String.self, forKey: .passphrase), - pusherNotificationClientIdentifier: container.decodeIfPresent(String.self, forKey: .pusherNotificationClientIdentifier)) + pusherNotificationClientIdentifier: container.decodeIfPresent(String.self, forKey: .pusherNotificationClientIdentifier), + slidingSyncProxyURLString: sessionWrapper.slidingSyncProxyURLString) } func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(session, forKey: .session) + try container.encode(SessionWrapper(session: session, slidingSyncProxyURLString: slidingSyncProxyURLString), forKey: .session) try container.encode(sessionDirectories.dataDirectory, forKey: .sessionDirectory) try container.encode(sessionDirectories.cacheDirectory, forKey: .cacheDirectory) try container.encode(passphrase, forKey: .passphrase) @@ -57,17 +65,40 @@ extension RestorationToken: Codable { } } +/// Temporary struct to smooth the forced migration by keeping a user session. +/// In the future we can remove this and throw a migration error when the URL +/// is decoded to a non-nil value. +private struct SessionWrapper { + let session: MatrixRustSDK.Session + let slidingSyncProxyURLString: String? +} + +extension SessionWrapper: Codable { + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: MatrixRustSDK.Session.CodingKeys.self) + session = try Session(from: decoder) + + // TODO: In the future we should decode this in the Session and throw a migration error if it contains a value. + slidingSyncProxyURLString = try container.decodeIfPresent(String.self, forKey: .slidingSyncProxy) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: MatrixRustSDK.Session.CodingKeys.self) + try session.encode(to: encoder) + try container.encode(slidingSyncProxyURLString, forKey: .slidingSyncProxy) + } +} + extension MatrixRustSDK.Session: Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let slidingSyncProxy = try container.decodeIfPresent(String.self, forKey: .slidingSyncProxy) self = try .init(accessToken: container.decode(String.self, forKey: .accessToken), refreshToken: container.decodeIfPresent(String.self, forKey: .refreshToken), userId: container.decode(String.self, forKey: .userId), deviceId: container.decode(String.self, forKey: .deviceId), homeserverUrl: container.decode(String.self, forKey: .homeserverUrl), oidcData: container.decodeIfPresent(String.self, forKey: .oidcData), - slidingSyncVersion: slidingSyncProxy.map { .proxy(url: $0) } ?? .native) + slidingSyncVersion: .native) } public func encode(to encoder: Encoder) throws { @@ -78,17 +109,9 @@ extension MatrixRustSDK.Session: Codable { try container.encode(deviceId, forKey: .deviceId) try container.encode(homeserverUrl, forKey: .homeserverUrl) try container.encode(oidcData, forKey: .oidcData) - try container.encode(slidingSyncVersion.proxyURL, forKey: .slidingSyncProxy) } enum CodingKeys: String, CodingKey { case accessToken, refreshToken, userId, deviceId, homeserverUrl, oidcData, slidingSyncProxy } } - -private extension SlidingSyncVersion { - var proxyURL: String? { - guard case let .proxy(url) = self else { return nil } - return url - } -} diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index 85f90a474d..2d3cd6fa31 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -64,12 +64,13 @@ class UserSessionStore: UserSessionStoreProtocol { do { let session = try client.session() let userID = try client.userId() - let clientProxy = await setupProxyForClient(client) + let clientProxy = await setupProxyForClient(client, needsSlidingSyncMigration: false) keychainController.setRestorationToken(RestorationToken(session: session, sessionDirectories: sessionDirectories, passphrase: passphrase, - pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier), + pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier, + slidingSyncProxyURLString: nil), forUsername: userID) MXLog.info("Set up session for user \(userID) at: \(sessionDirectories)") @@ -145,15 +146,16 @@ class UserSessionStore: UserSessionStoreProtocol { MXLog.info("Set up session for user \(credentials.userID) at: \(credentials.restorationToken.sessionDirectories)") - return await .success(setupProxyForClient(client)) + return await .success(setupProxyForClient(client, needsSlidingSyncMigration: credentials.restorationToken.needsSlidingSyncMigration)) } catch { MXLog.error("Failed restoring login with error: \(error)") return .failure(.failedRestoringLogin) } } - private func setupProxyForClient(_ client: ClientProtocol) async -> ClientProxyProtocol { + private func setupProxyForClient(_ client: ClientProtocol, needsSlidingSyncMigration: Bool) async -> ClientProxyProtocol { await ClientProxy(client: client, + needsSlidingSyncMigration: needsSlidingSyncMigration, networkMonitor: networkMonitor, appSettings: appSettings) } diff --git a/Package.resolved b/Package.resolved index 74af098dcf..84a94fb1d3 100644 --- a/Package.resolved +++ b/Package.resolved @@ -22,8 +22,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/jpsim/Yams", "state" : { - "revision" : "db9ff235cf800bc657c3ed0961078fc231d0f28b", - "version" : "5.2.0" + "revision" : "2688707e563b44d7d87c29ba6c5ca04ce86ae58b", + "version" : "5.3.0" } } ], diff --git a/PreviewTests/Sources/GeneratedPreviewTests.swift b/PreviewTests/Sources/GeneratedPreviewTests.swift index 2d169007f4..32e3624d0a 100644 --- a/PreviewTests/Sources/GeneratedPreviewTests.swift +++ b/PreviewTests/Sources/GeneratedPreviewTests.swift @@ -275,12 +275,6 @@ extension PreviewTests { } } - func test_homeScreenSlidingSyncMigrationBanner() async throws { - for preview in HomeScreenSlidingSyncMigrationBanner_Previews._allPreviews { - try await assertSnapshots(matching: preview) - } - } - func test_homeScreen() async throws { for preview in HomeScreen_Previews._allPreviews { try await assertSnapshots(matching: preview) diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_homeScreenSlidingSyncMigrationBanner-iPad-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_homeScreenSlidingSyncMigrationBanner-iPad-en-GB.1.png deleted file mode 100644 index 090039dbd2..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_homeScreenSlidingSyncMigrationBanner-iPad-en-GB.1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed37c148a5a16aea018c2592948c53c4b3029f3fc6b7d618aa212192e7bc42e5 -size 108471 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_homeScreenSlidingSyncMigrationBanner-iPad-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_homeScreenSlidingSyncMigrationBanner-iPad-pseudo.1.png deleted file mode 100644 index a7607a8c65..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_homeScreenSlidingSyncMigrationBanner-iPad-pseudo.1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18cce2502021c0927c3abbc975a24134257f23963a9197c7e6746d9745d34beb -size 130832 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_homeScreenSlidingSyncMigrationBanner-iPhone-16-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_homeScreenSlidingSyncMigrationBanner-iPhone-16-en-GB.1.png deleted file mode 100644 index b872e23766..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_homeScreenSlidingSyncMigrationBanner-iPhone-16-en-GB.1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6abeb702cab50a0ef39cea1f34ebe5346692a5510f643415c2edfdac8a503bab -size 70709 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_homeScreenSlidingSyncMigrationBanner-iPhone-16-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_homeScreenSlidingSyncMigrationBanner-iPhone-16-pseudo.1.png deleted file mode 100644 index 195267b3f5..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_homeScreenSlidingSyncMigrationBanner-iPhone-16-pseudo.1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:edda50f8721edf0c8772896b4e1c6b855f5ca0e72c7363a887045e8e4742bfe2 -size 105929 diff --git a/UnitTests/Sources/KeychainControllerTests.swift b/UnitTests/Sources/KeychainControllerTests.swift index d5bcc599d7..a13593f9db 100644 --- a/UnitTests/Sources/KeychainControllerTests.swift +++ b/UnitTests/Sources/KeychainControllerTests.swift @@ -30,10 +30,11 @@ class KeychainControllerTests: XCTestCase { deviceId: "deviceId", homeserverUrl: "homeserverUrl", oidcData: "oidcData", - slidingSyncVersion: .proxy(url: "https://my.sync.proxy")), + slidingSyncVersion: .native), sessionDirectories: .init(), passphrase: "passphrase", - pusherNotificationClientIdentifier: "pusherClientID") + pusherNotificationClientIdentifier: "pusherClientID", + slidingSyncProxyURLString: "https://my.sync.proxy") keychain.setRestorationToken(restorationToken, forUsername: username) // Then the restoration token should be stored in the keychain. @@ -49,10 +50,11 @@ class KeychainControllerTests: XCTestCase { deviceId: "deviceId", homeserverUrl: "homeserverUrl", oidcData: "oidcData", - slidingSyncVersion: .proxy(url: "https://my.sync.proxy")), + slidingSyncVersion: .native), sessionDirectories: .init(), passphrase: "passphrase", - pusherNotificationClientIdentifier: "pusherClientID") + pusherNotificationClientIdentifier: "pusherClientID", + slidingSyncProxyURLString: "https://my.sync.proxy") keychain.setRestorationToken(restorationToken, forUsername: username) XCTAssertEqual(keychain.restorationTokens().count, 1, "The keychain should have 1 restoration token.") XCTAssertEqual(keychain.restorationTokenForUsername(username), restorationToken, "The initial restoration token should match the value that was stored.") @@ -74,10 +76,11 @@ class KeychainControllerTests: XCTestCase { deviceId: "deviceId", homeserverUrl: "homeserverUrl", oidcData: "oidcData", - slidingSyncVersion: .proxy(url: "https://my.sync.proxy")), + slidingSyncVersion: .native), sessionDirectories: .init(), passphrase: "passphrase", - pusherNotificationClientIdentifier: "pusherClientID") + pusherNotificationClientIdentifier: "pusherClientID", + slidingSyncProxyURLString: "https://my.sync.proxy") keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com") } XCTAssertEqual(keychain.restorationTokens().count, 5, "The keychain should have 5 restoration tokens.") @@ -98,10 +101,11 @@ class KeychainControllerTests: XCTestCase { deviceId: "deviceId", homeserverUrl: "homeserverUrl", oidcData: "oidcData", - slidingSyncVersion: .proxy(url: "https://my.sync.proxy")), + slidingSyncVersion: .native), sessionDirectories: .init(), passphrase: "passphrase", - pusherNotificationClientIdentifier: "pusherClientID") + pusherNotificationClientIdentifier: "pusherClientID", + slidingSyncProxyURLString: "https://my.sync.proxy") keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com") } XCTAssertEqual(keychain.restorationTokens().count, 5, "The keychain should have 5 restoration tokens.") @@ -133,7 +137,8 @@ class KeychainControllerTests: XCTestCase { slidingSyncVersion: .native), sessionDirectories: .init(), passphrase: "passphrase", - pusherNotificationClientIdentifier: "pusherClientID") + pusherNotificationClientIdentifier: "pusherClientID", + slidingSyncProxyURLString: nil) keychain.setRestorationToken(restorationToken, forUsername: username) // Then decoding the restoration token from the keychain should still work. diff --git a/UnitTests/Sources/RestorationTokenTests.swift b/UnitTests/Sources/RestorationTokenTests.swift index 8d992d9990..95fb2b943b 100644 --- a/UnitTests/Sources/RestorationTokenTests.swift +++ b/UnitTests/Sources/RestorationTokenTests.swift @@ -13,38 +13,42 @@ import MatrixRustSDK class RestorationTokenTests: XCTestCase { func testDecodeFromTokenV1() throws { // Given an encoded restoration token in the original format that only contains a Session from the SDK. - let originalToken = RestorationTokenV1(session: Session(accessToken: "1234", - refreshToken: nil, - userId: "@user:example.com", - deviceId: "D3V1C3", - homeserverUrl: "https://matrix.example.com", - oidcData: nil, - slidingSyncVersion: .proxy(url: "https://sync.example.com"))) + let originalToken = RestorationTokenV1(session: SessionV1(accessToken: "1234", + refreshToken: nil, + userId: "@user:example.com", + deviceId: "D3V1C3", + homeserverUrl: "https://matrix.example.com", + oidcData: nil, + slidingSyncVersion: .proxy(url: "https://sync.example.com"))) let data = try JSONEncoder().encode(originalToken) // When decoding the data to the current restoration token format. let decodedToken = try JSONDecoder().decode(RestorationToken.self, from: data) // Then the output should be a valid token with the expected store directories. - XCTAssertEqual(decodedToken.session, originalToken.session, "The session should not be changed.") + assertEqual(session: decodedToken.session, originalSession: originalToken.session) XCTAssertNil(decodedToken.passphrase, "There should not be a passphrase.") XCTAssertNil(decodedToken.pusherNotificationClientIdentifier, "There should not be a push notification client ID.") XCTAssertEqual(decodedToken.sessionDirectories.dataDirectory, .sessionsBaseDirectory.appending(component: "@user_example.com"), "The session directory should match the original location set by the Rust SDK from our base directory.") XCTAssertEqual(decodedToken.sessionDirectories.cacheDirectory, .sessionCachesBaseDirectory.appending(component: "@user_example.com"), "The cache directory should be derived from the session directory but in the caches directory.") + + XCTAssertEqual(decodedToken.slidingSyncProxyURLString, "https://sync.example.com", + "The original sliding sync URL should be preserved in order to trigger the migration prompt.") + XCTAssertTrue(decodedToken.needsSlidingSyncMigration, "The migration flag should be set to true.") } func testDecodeFromTokenV4() throws { // Given an encoded restoration token in the 4th format that contains a stored session directory. let sessionDirectoryName = UUID().uuidString - let originalToken = RestorationTokenV4(session: Session(accessToken: "1234", - refreshToken: "5678", - userId: "@user:example.com", - deviceId: "D3V1C3", - homeserverUrl: "https://matrix.example.com", - oidcData: "data-from-mas", - slidingSyncVersion: .proxy(url: "https://sync.example.com")), + let originalToken = RestorationTokenV4(session: SessionV1(accessToken: "1234", + refreshToken: "5678", + userId: "@user:example.com", + deviceId: "D3V1C3", + homeserverUrl: "https://matrix.example.com", + oidcData: "data-from-mas", + slidingSyncVersion: .proxy(url: "https://sync.example.com")), sessionDirectory: .sessionsBaseDirectory.appending(component: sessionDirectoryName), passphrase: "passphrase", pusherNotificationClientIdentifier: "pusher-identifier") @@ -54,7 +58,7 @@ class RestorationTokenTests: XCTestCase { let decodedToken = try JSONDecoder().decode(RestorationToken.self, from: data) // Then the output should be a valid token with the expected store directories. - XCTAssertEqual(decodedToken.session, originalToken.session, "The session should not be changed.") + assertEqual(session: decodedToken.session, originalSession: originalToken.session) XCTAssertEqual(decodedToken.passphrase, originalToken.passphrase, "The passphrase should not be changed.") XCTAssertEqual(decodedToken.pusherNotificationClientIdentifier, originalToken.pusherNotificationClientIdentifier, "The push notification client identifier should not be changed.") @@ -62,18 +66,22 @@ class RestorationTokenTests: XCTestCase { "The session directory should not be changed.") XCTAssertEqual(decodedToken.sessionDirectories.cacheDirectory, .sessionCachesBaseDirectory.appending(component: sessionDirectoryName), "The cache directory should be derived from the session directory but in the caches directory.") + + XCTAssertEqual(decodedToken.slidingSyncProxyURLString, "https://sync.example.com", + "The original sliding sync URL should be preserved in order to trigger the migration prompt.") + XCTAssertTrue(decodedToken.needsSlidingSyncMigration, "The migration flag should be set to true.") } func testDecodeFromTokenV5() throws { // Given an encoded restoration token in the 5th format that contains separate directories for session data and caches. let sessionDirectoryName = UUID().uuidString - let originalToken = RestorationTokenV5(session: Session(accessToken: "1234", - refreshToken: "5678", - userId: "@user:example.com", - deviceId: "D3V1C3", - homeserverUrl: "https://matrix.example.com", - oidcData: "data-from-mas", - slidingSyncVersion: .native), + let originalToken = RestorationTokenV5(session: SessionV1(accessToken: "1234", + refreshToken: "5678", + userId: "@user:example.com", + deviceId: "D3V1C3", + homeserverUrl: "https://matrix.example.com", + oidcData: "data-from-mas", + slidingSyncVersion: .native), sessionDirectory: .sessionsBaseDirectory.appending(component: sessionDirectoryName), cacheDirectory: .sessionCachesBaseDirectory.appending(component: sessionDirectoryName), passphrase: "passphrase", @@ -84,7 +92,7 @@ class RestorationTokenTests: XCTestCase { let decodedToken = try JSONDecoder().decode(RestorationToken.self, from: data) // Then the output should be a valid token. - XCTAssertEqual(decodedToken.session, originalToken.session, "The session should not be changed.") + assertEqual(session: decodedToken.session, originalSession: originalToken.session) XCTAssertEqual(decodedToken.passphrase, originalToken.passphrase, "The passphrase should not be changed.") XCTAssertEqual(decodedToken.pusherNotificationClientIdentifier, originalToken.pusherNotificationClientIdentifier, "The push notification client identifier should not be changed.") @@ -92,6 +100,9 @@ class RestorationTokenTests: XCTestCase { "The session directory should not be changed.") XCTAssertEqual(decodedToken.sessionDirectories.cacheDirectory, originalToken.cacheDirectory, "The cache directory should not be changed.") + + XCTAssertNil(decodedToken.slidingSyncProxyURLString, "No sliding sync proxy URL should be decoded for native sliding sync.") + XCTAssertFalse(decodedToken.needsSlidingSyncMigration, "The migration flag should not be set.") } func testDecodeFromCurrentToken() throws { @@ -105,7 +116,8 @@ class RestorationTokenTests: XCTestCase { slidingSyncVersion: .native), sessionDirectories: .init(), passphrase: "passphrase", - pusherNotificationClientIdentifier: "pusher-identifier") + pusherNotificationClientIdentifier: "pusher-identifier", + slidingSyncProxyURLString: nil) let data = try JSONEncoder().encode(originalToken) // When decoding the data. @@ -114,23 +126,86 @@ class RestorationTokenTests: XCTestCase { // Then the output should be a valid token. XCTAssertEqual(decodedToken, originalToken, "The token should remain identical.") } + + func assertEqual(session: Session, originalSession: SessionV1) { + XCTAssertEqual(session.accessToken, originalSession.accessToken, "The access token should not be changed.") + XCTAssertEqual(session.refreshToken, originalSession.refreshToken, "The refresh token should not be changed.") + XCTAssertEqual(session.userId, originalSession.userId, "The user ID should not be changed.") + XCTAssertEqual(session.deviceId, originalSession.deviceId, "The device ID should not be changed.") + XCTAssertEqual(session.homeserverUrl, originalSession.homeserverUrl, "The homeserver URL should not be changed.") + XCTAssertEqual(session.oidcData, originalSession.oidcData, "The OIDC data should not be changed.") + } } +// MARK: - Token formats + struct RestorationTokenV1: Equatable, Codable { - let session: MatrixRustSDK.Session + let session: SessionV1 } struct RestorationTokenV4: Equatable, Codable { - let session: MatrixRustSDK.Session + let session: SessionV1 let sessionDirectory: URL let passphrase: String? let pusherNotificationClientIdentifier: String? } struct RestorationTokenV5: Equatable, Codable { - let session: MatrixRustSDK.Session + let session: SessionV1 let sessionDirectory: URL let cacheDirectory: URL let passphrase: String? let pusherNotificationClientIdentifier: String? } + +// MARK: - Session formats + +struct SessionV1: Equatable { + var accessToken: String + var refreshToken: String? + var userId: String + var deviceId: String + var homeserverUrl: String + var oidcData: String? + var slidingSyncVersion: SlidingSyncVersionV1 +} + +enum SlidingSyncVersionV1: Equatable { + case none + case proxy(url: String) + case native + + var proxyURL: String? { + guard case let .proxy(url) = self else { return nil } + return url + } +} + +extension SessionV1: Codable { + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let slidingSyncProxy = try container.decodeIfPresent(String.self, forKey: .slidingSyncProxy) + self = try .init(accessToken: container.decode(String.self, forKey: .accessToken), + refreshToken: container.decodeIfPresent(String.self, forKey: .refreshToken), + userId: container.decode(String.self, forKey: .userId), + deviceId: container.decode(String.self, forKey: .deviceId), + homeserverUrl: container.decode(String.self, forKey: .homeserverUrl), + oidcData: container.decodeIfPresent(String.self, forKey: .oidcData), + slidingSyncVersion: slidingSyncProxy.map { .proxy(url: $0) } ?? .native) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(accessToken, forKey: .accessToken) + try container.encode(refreshToken, forKey: .refreshToken) + try container.encode(userId, forKey: .userId) + try container.encode(deviceId, forKey: .deviceId) + try container.encode(homeserverUrl, forKey: .homeserverUrl) + try container.encode(oidcData, forKey: .oidcData) + try container.encode(slidingSyncVersion.proxyURL, forKey: .slidingSyncProxy) + } + + enum CodingKeys: String, CodingKey { + case accessToken, refreshToken, userId, deviceId, homeserverUrl, oidcData, slidingSyncProxy + } +} diff --git a/project.yml b/project.yml index 724458aa97..7238cf59b4 100644 --- a/project.yml +++ b/project.yml @@ -61,7 +61,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/element-hq/matrix-rust-components-swift - exactVersion: 25.02.11 + exactVersion: 25.02.17 # path: ../matrix-rust-sdk Compound: url: https://github.com/element-hq/compound-ios