From 5762e1c537e4be4e2d8ccd194a8a31223d09db75 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 30 Jul 2025 12:18:10 +0100 Subject: [PATCH] Stop showing the sliding sync proxy alert to any remaining users. The session will simply fail to restore and the restoration token deleted. --- ElementX/Sources/Mocks/ClientProxyMock.swift | 1 - .../Mocks/Generated/GeneratedMocks.swift | 5 -- .../HomeScreen/HomeScreenViewModel.swift | 22 ------- .../Sources/Services/Client/ClientProxy.swift | 9 --- .../Services/Client/ClientProxyProtocol.swift | 3 - .../Keychain/KeychainController.swift | 9 ++- .../UserSession/RestorationToken.swift | 57 +++++++----------- .../UserSession/UserSessionStore.swift | 10 ++-- .../Sources/KeychainControllerTests.swift | 58 ++++++++++--------- UnitTests/Sources/RestorationTokenTests.swift | 43 +++++++++----- 10 files changed, 89 insertions(+), 128 deletions(-) diff --git a/ElementX/Sources/Mocks/ClientProxyMock.swift b/ElementX/Sources/Mocks/ClientProxyMock.swift index 9410384fe..69d583f05 100644 --- a/ElementX/Sources/Mocks/ClientProxyMock.swift +++ b/ElementX/Sources/Mocks/ClientProxyMock.swift @@ -74,7 +74,6 @@ extension ClientProxyMock { ignoreUserReturnValue = .success(()) unignoreUserReturnValue = .success(()) - needsSlidingSyncMigration = false slidingSyncVersion = .native trackRecentlyVisitedRoomReturnValue = .success(()) diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 4724b2a39..f7d0180cc 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -2093,11 +2093,6 @@ 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 } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index c8803a95c..b7ac69f94 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -133,10 +133,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol setupRoomListSubscriptions() updateRooms() - - Task { - await checkSlidingSyncMigration() - } } // MARK: - Public @@ -318,24 +314,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol state.rooms = rooms } - /// 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 { - guard userSession.clientProxy.needsSlidingSyncMigration else { - return - } - - // 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(InfoPlistReader.main.bundleDisplayName), - primaryButton: .init(title: L10n.bannerMigrateToNativeSlidingSyncAction) { [weak self] in - self?.actionsSubject.send(.logoutWithoutConfirmation) - }) - } - } - private func markRoomAsFavourite(_ roomID: String, isFavourite: Bool) async { guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else { MXLog.error("Failed retrieving room for identifier: \(roomID)") diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 5afc7a4ae..21b8ee2e9 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -150,7 +150,6 @@ class ClientProxy: ClientProxyProtocol { private let sendQueueStatusSubject = CurrentValueSubject(false) init(client: ClientProtocol, - needsSlidingSyncMigration: Bool, networkMonitor: NetworkMonitorProtocol, appSettings: AppSettings) async throws { self.client = client @@ -165,8 +164,6 @@ class ClientProxy: ClientProxyProtocol { secureBackupController = SecureBackupController(encryption: client.encryption()) - self.needsSlidingSyncMigration = needsSlidingSyncMigration - let configuredAppService = try await ClientProxyServices(client: client, actionsSubject: actionsSubject, notificationSettings: notificationSettings, @@ -271,7 +268,6 @@ class ClientProxy: ClientProxyProtocol { client.homeserver() } - let needsSlidingSyncMigration: Bool var slidingSyncVersion: SlidingSyncVersion { client.slidingSyncVersion() } @@ -341,11 +337,6 @@ 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 563c3145e..4d2378c2e 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -86,9 +86,6 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { 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 canDeactivateAccount: Bool { get } diff --git a/ElementX/Sources/Services/Keychain/KeychainController.swift b/ElementX/Sources/Services/Keychain/KeychainController.swift index d9a658582..15389f43e 100644 --- a/ElementX/Sources/Services/Keychain/KeychainController.swift +++ b/ElementX/Sources/Services/Keychain/KeychainController.swift @@ -54,8 +54,12 @@ class KeychainController: KeychainControllerProtocol { guard let tokenData = try restorationTokenKeychain.getData(username) else { return nil } - + return try JSONDecoder().decode(RestorationToken.self, from: tokenData) + } catch RestorationTokenError.slidingSyncProxyNotSupported { + MXLog.error("Unsupported user restore token (contains sliding sync proxy). Deleting token.") + removeRestorationTokenForUsername(username) + return nil } catch { MXLog.error("Failed retrieving user restore token") return nil @@ -112,8 +116,7 @@ class KeychainController: KeychainControllerProtocol { let restorationToken = RestorationToken(session: session, sessionDirectories: oldToken.sessionDirectories, passphrase: oldToken.passphrase, - pusherNotificationClientIdentifier: oldToken.pusherNotificationClientIdentifier, - slidingSyncProxyURLString: oldToken.slidingSyncProxyURLString) + pusherNotificationClientIdentifier: oldToken.pusherNotificationClientIdentifier) setRestorationToken(restorationToken, forUsername: session.userId) } diff --git a/ElementX/Sources/Services/UserSession/RestorationToken.swift b/ElementX/Sources/Services/UserSession/RestorationToken.swift index e6e727df3..b1e39c666 100644 --- a/ElementX/Sources/Services/UserSession/RestorationToken.swift +++ b/ElementX/Sources/Services/UserSession/RestorationToken.swift @@ -8,19 +8,16 @@ import Foundation import MatrixRustSDK +enum RestorationTokenError: Error { + case slidingSyncProxyNotSupported +} + struct RestorationToken: Equatable { let session: MatrixRustSDK.Session let sessionDirectories: SessionDirectories 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 @@ -34,7 +31,7 @@ extension RestorationToken: Codable { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let sessionWrapper = try container.decode(SessionWrapper.self, forKey: .session) + let session = try container.decode(Session.self, forKey: .session) let dataDirectory = try container.decodeIfPresent(URL.self, forKey: .sessionDirectory) let cacheDirectory = try container.decodeIfPresent(URL.self, forKey: .cacheDirectory) @@ -45,19 +42,18 @@ extension RestorationToken: Codable { SessionDirectories(dataDirectory: dataDirectory) } } else { - SessionDirectories(userID: sessionWrapper.session.userId) + SessionDirectories(userID: session.userId) } - self = try .init(session: sessionWrapper.session, + self = try .init(session: session, sessionDirectories: sessionDirectories, passphrase: container.decodeIfPresent(String.self, forKey: .passphrase), - pusherNotificationClientIdentifier: container.decodeIfPresent(String.self, forKey: .pusherNotificationClientIdentifier), - slidingSyncProxyURLString: sessionWrapper.slidingSyncProxyURLString) + pusherNotificationClientIdentifier: container.decodeIfPresent(String.self, forKey: .pusherNotificationClientIdentifier)) } func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(SessionWrapper(session: session, slidingSyncProxyURLString: slidingSyncProxyURLString), forKey: .session) + try container.encode(session, forKey: .session) try container.encode(sessionDirectories.dataDirectory, forKey: .sessionDirectory) try container.encode(sessionDirectories.cacheDirectory, forKey: .cacheDirectory) try container.encode(passphrase, forKey: .passphrase) @@ -65,33 +61,20 @@ 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) + + // The SDK no longer supports sliding sync proxies. We must bail here otherwise the + // session would be restored using native sliding sync, but if the proxy was still + // active its sync loop would be stealing keys away from us. + // + // Note: As tempting as it is to use container.contains(.slidingSyncProxy), that will always + // return true as our coding keys have resulted in the key being encoded with a nil value 🤦‍♂️. + if (try? container.decodeIfPresent(String.self, forKey: .slidingSyncProxy)) != nil { + throw RestorationTokenError.slidingSyncProxyNotSupported + } + self = try .init(accessToken: container.decode(String.self, forKey: .accessToken), refreshToken: container.decodeIfPresent(String.self, forKey: .refreshToken), userId: container.decode(String.self, forKey: .userId), diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index dcc981417..9122d62f3 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -64,13 +64,12 @@ class UserSessionStore: UserSessionStoreProtocol { do { let session = try client.session() let userID = try client.userId() - let clientProxy = try await setupProxyForClient(client, needsSlidingSyncMigration: false) + let clientProxy = try await setupProxyForClient(client) keychainController.setRestorationToken(RestorationToken(session: session, sessionDirectories: sessionDirectories, passphrase: passphrase, - pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier, - slidingSyncProxyURLString: nil), + pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier), forUsername: userID) MXLog.info("Set up session for user \(userID) at: \(sessionDirectories)") @@ -141,7 +140,7 @@ class UserSessionStore: UserSessionStoreProtocol { Task(priority: .low) { await appHooks.remoteSettingsHook.updateCache(using: client) } - return try await .success(setupProxyForClient(client, needsSlidingSyncMigration: credentials.restorationToken.needsSlidingSyncMigration)) + return try await .success(setupProxyForClient(client)) } catch UserSessionStoreError.failedSettingUpClientProxy(let error) { // If this has failed, there is likely something wrong with the creation of the sync service // There is nothing we can do, but at the same time we don't want the user to the get logged out @@ -153,10 +152,9 @@ class UserSessionStore: UserSessionStoreProtocol { } } - private func setupProxyForClient(_ client: ClientProtocol, needsSlidingSyncMigration: Bool) async throws -> ClientProxyProtocol { + private func setupProxyForClient(_ client: ClientProtocol) async throws -> ClientProxyProtocol { do { return try await ClientProxy(client: client, - needsSlidingSyncMigration: needsSlidingSyncMigration, networkMonitor: networkMonitor, appSettings: appSettings) } catch { diff --git a/UnitTests/Sources/KeychainControllerTests.swift b/UnitTests/Sources/KeychainControllerTests.swift index a13593f9d..dca2f9f18 100644 --- a/UnitTests/Sources/KeychainControllerTests.swift +++ b/UnitTests/Sources/KeychainControllerTests.swift @@ -6,6 +6,7 @@ // @testable import ElementX +import KeychainAccess import XCTest class KeychainControllerTests: XCTestCase { @@ -33,8 +34,7 @@ class KeychainControllerTests: XCTestCase { slidingSyncVersion: .native), sessionDirectories: .init(), passphrase: "passphrase", - pusherNotificationClientIdentifier: "pusherClientID", - slidingSyncProxyURLString: "https://my.sync.proxy") + pusherNotificationClientIdentifier: "pusherClientID") keychain.setRestorationToken(restorationToken, forUsername: username) // Then the restoration token should be stored in the keychain. @@ -53,8 +53,7 @@ class KeychainControllerTests: XCTestCase { slidingSyncVersion: .native), sessionDirectories: .init(), passphrase: "passphrase", - pusherNotificationClientIdentifier: "pusherClientID", - slidingSyncProxyURLString: "https://my.sync.proxy") + pusherNotificationClientIdentifier: "pusherClientID") 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.") @@ -79,8 +78,7 @@ class KeychainControllerTests: XCTestCase { slidingSyncVersion: .native), sessionDirectories: .init(), passphrase: "passphrase", - pusherNotificationClientIdentifier: "pusherClientID", - slidingSyncProxyURLString: "https://my.sync.proxy") + pusherNotificationClientIdentifier: "pusherClientID") keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com") } XCTAssertEqual(keychain.restorationTokens().count, 5, "The keychain should have 5 restoration tokens.") @@ -104,8 +102,7 @@ class KeychainControllerTests: XCTestCase { slidingSyncVersion: .native), sessionDirectories: .init(), passphrase: "passphrase", - pusherNotificationClientIdentifier: "pusherClientID", - slidingSyncProxyURLString: "https://my.sync.proxy") + pusherNotificationClientIdentifier: "pusherClientID") keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com") } XCTAssertEqual(keychain.restorationTokens().count, 5, "The keychain should have 5 restoration tokens.") @@ -122,27 +119,34 @@ class KeychainControllerTests: XCTestCase { XCTAssertNotNil(keychain.restorationTokenForUsername("@test4:example.com"), "The restoration token should not have been deleted.") } - func testSimplifiedSlidingSyncRestorationToken() { - // Given an empty keychain. - XCTAssertTrue(keychain.restorationTokens().isEmpty, "The keychain should be empty to begin with.") + func testUnsupportedRestorationToken() { + // Given a keychain with an unsupported restoration token with a sliding sync proxy URL value. + let underlyingKeychain = Keychain(service: KeychainControllerService.tests.restorationTokenID, + accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier) + // Note: We assert with this underlying keychain's keys as keychain.restorationTokens() triggers the deletion that we're testing. + XCTAssertTrue(underlyingKeychain.allKeys().isEmpty, "The keychain should be empty to begin with.") - // When adding an restoration token that doesn't contain a sliding sync proxy (e.g. for SSS). - let username = "@test:example.com" - let restorationToken = RestorationToken(session: .init(accessToken: "accessToken", - refreshToken: "refreshToken", - userId: "userId", - deviceId: "deviceId", - homeserverUrl: "homeserverUrl", - oidcData: "oidcData", - slidingSyncVersion: .native), - sessionDirectories: .init(), - passphrase: "passphrase", - pusherNotificationClientIdentifier: "pusherClientID", - slidingSyncProxyURLString: nil) - keychain.setRestorationToken(restorationToken, forUsername: username) + do { + let unsupportedToken = RestorationTokenV1(session: SessionV1(accessToken: "1234", + refreshToken: nil, + userId: "@test:example.com", + deviceId: "D3V1C3", + homeserverUrl: "https://matrix.example.com", + oidcData: nil, + slidingSyncVersion: .proxy(url: "https://sync.example.com"))) + let tokenData = try JSONEncoder().encode(unsupportedToken) + try underlyingKeychain.set(tokenData, key: "@test:example.com") + XCTAssertEqual(underlyingKeychain.allKeys().count, 1) + } catch { + XCTFail("Failed storing user restore token with error: \(error)") + } - // Then decoding the restoration token from the keychain should still work. - XCTAssertEqual(keychain.restorationTokenForUsername(username), restorationToken, "The retrieved restoration token should match the value that was stored.") + // When attempting to retrieve the unsupported token. + let retrievedToken = keychain.restorationTokenForUsername("@test:example.com") + + // Then nothing should be returned and the restoration token should be automatically removed. + XCTAssertNil(retrievedToken, "The token should not be decoded.") + XCTAssertTrue(underlyingKeychain.allKeys().isEmpty, "The keychain should be empty again.") } func testAddPINCode() throws { diff --git a/UnitTests/Sources/RestorationTokenTests.swift b/UnitTests/Sources/RestorationTokenTests.swift index c9281ad9a..dbde7774c 100644 --- a/UnitTests/Sources/RestorationTokenTests.swift +++ b/UnitTests/Sources/RestorationTokenTests.swift @@ -11,6 +11,31 @@ import XCTest import MatrixRustSDK class RestorationTokenTests: XCTestCase { + func testDecodeTokenV1WithSlidingSyncProxy() throws { + // Given an encoded restoration token that contains a session with a sliding sync proxy. + 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. + XCTAssertThrowsError(try JSONDecoder().decode(RestorationToken.self, from: data)) { error in + // Then an error should be thrown as it is no longer supported. + switch error { + case RestorationTokenError.slidingSyncProxyNotSupported: + break + default: + XCTFail("Unexpected error thrown: \(error)") + } + } + } + + /// Honestly not sure if this test is needed or not – it is quite possible that `RestorationTokenV1` was only encoded at a time + /// when the sliding sync proxy was the only option. But lets keep the test for completeness of the session directory migration coverage. func testDecodeFromTokenV1() throws { // Given an encoded restoration token in the original format that only contains a Session from the SDK. let originalToken = RestorationTokenV1(session: SessionV1(accessToken: "1234", @@ -19,7 +44,7 @@ class RestorationTokenTests: XCTestCase { deviceId: "D3V1C3", homeserverUrl: "https://matrix.example.com", oidcData: nil, - slidingSyncVersion: .proxy(url: "https://sync.example.com"))) + slidingSyncVersion: .native)) let data = try JSONEncoder().encode(originalToken) // When decoding the data to the current restoration token format. @@ -33,10 +58,6 @@ class RestorationTokenTests: XCTestCase { "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 { @@ -48,7 +69,7 @@ class RestorationTokenTests: XCTestCase { deviceId: "D3V1C3", homeserverUrl: "https://matrix.example.com", oidcData: "data-from-mas", - slidingSyncVersion: .proxy(url: "https://sync.example.com")), + slidingSyncVersion: .native), sessionDirectory: .sessionsBaseDirectory.appending(component: sessionDirectoryName), passphrase: "passphrase", pusherNotificationClientIdentifier: "pusher-identifier") @@ -66,10 +87,6 @@ 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 { @@ -100,9 +117,6 @@ 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 { @@ -116,8 +130,7 @@ class RestorationTokenTests: XCTestCase { slidingSyncVersion: .native), sessionDirectories: .init(), passphrase: "passphrase", - pusherNotificationClientIdentifier: "pusher-identifier", - slidingSyncProxyURLString: nil) + pusherNotificationClientIdentifier: "pusher-identifier") let data = try JSONEncoder().encode(originalToken) // When decoding the data.