Update the SDK (#3196)

* Update the SDK.

* Fix API breaks on send failures and propagate the new type.

* Handle new SDK ShieldState.

* Set up the ClientBuilder's `cachePath` option.

* Delete the cacheDirectory during logout/clearCache.

* Add unit tests for RestorationToken decoding and SessionDirectories generation.
This commit is contained in:
Doug
2024-08-27 11:06:26 +01:00
committed by GitHub
parent ebef0d1fbe
commit 812c5d5c61
26 changed files with 403 additions and 82 deletions
@@ -257,6 +257,7 @@
"error_some_messages_have_not_been_sent" = "Some messages have not been sent";
"error_unknown" = "Sorry, an error occurred";
"event_shield_reason_authenticity_not_guaranteed" = "The authenticity of this encrypted message can't be guaranteed on this device.";
"event_shield_reason_previously_verified" = "Encrypted by a previously-verified user.";
"event_shield_reason_sent_in_clear" = "Not encrypted.";
"event_shield_reason_unknown_device" = "Encrypted by an unknown or deleted device.";
"event_shield_reason_unsigned_device" = "Encrypted by a device not verified by its owner.";
+2
View File
@@ -572,6 +572,8 @@ internal enum L10n {
internal static var errorUnknown: String { return L10n.tr("Localizable", "error_unknown") }
/// The authenticity of this encrypted message can't be guaranteed on this device.
internal static var eventShieldReasonAuthenticityNotGuaranteed: String { return L10n.tr("Localizable", "event_shield_reason_authenticity_not_guaranteed") }
/// Encrypted by a previously-verified user.
internal static var eventShieldReasonPreviouslyVerified: String { return L10n.tr("Localizable", "event_shield_reason_previously_verified") }
/// Not encrypted.
internal static var eventShieldReasonSentInClear: String { return L10n.tr("Localizable", "event_shield_reason_sent_in_clear") }
/// Encrypted by an unknown or deleted device.
@@ -4804,17 +4804,17 @@ open class ClientBuilderSDKMock: MatrixRustSDK.ClientBuilder {
}
}
//MARK: - sessionPath
//MARK: - sessionPaths
var sessionPathPathUnderlyingCallsCount = 0
open var sessionPathPathCallsCount: Int {
var sessionPathsDataPathCachePathUnderlyingCallsCount = 0
open var sessionPathsDataPathCachePathCallsCount: Int {
get {
if Thread.isMainThread {
return sessionPathPathUnderlyingCallsCount
return sessionPathsDataPathCachePathUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = sessionPathPathUnderlyingCallsCount
returnValue = sessionPathsDataPathCachePathUnderlyingCallsCount
}
return returnValue!
@@ -4822,29 +4822,29 @@ open class ClientBuilderSDKMock: MatrixRustSDK.ClientBuilder {
}
set {
if Thread.isMainThread {
sessionPathPathUnderlyingCallsCount = newValue
sessionPathsDataPathCachePathUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
sessionPathPathUnderlyingCallsCount = newValue
sessionPathsDataPathCachePathUnderlyingCallsCount = newValue
}
}
}
}
open var sessionPathPathCalled: Bool {
return sessionPathPathCallsCount > 0
open var sessionPathsDataPathCachePathCalled: Bool {
return sessionPathsDataPathCachePathCallsCount > 0
}
open var sessionPathPathReceivedPath: String?
open var sessionPathPathReceivedInvocations: [String] = []
open var sessionPathsDataPathCachePathReceivedArguments: (dataPath: String, cachePath: String?)?
open var sessionPathsDataPathCachePathReceivedInvocations: [(dataPath: String, cachePath: String?)] = []
var sessionPathPathUnderlyingReturnValue: ClientBuilder!
open var sessionPathPathReturnValue: ClientBuilder! {
var sessionPathsDataPathCachePathUnderlyingReturnValue: ClientBuilder!
open var sessionPathsDataPathCachePathReturnValue: ClientBuilder! {
get {
if Thread.isMainThread {
return sessionPathPathUnderlyingReturnValue
return sessionPathsDataPathCachePathUnderlyingReturnValue
} else {
var returnValue: ClientBuilder? = nil
DispatchQueue.main.sync {
returnValue = sessionPathPathUnderlyingReturnValue
returnValue = sessionPathsDataPathCachePathUnderlyingReturnValue
}
return returnValue!
@@ -4852,26 +4852,26 @@ open class ClientBuilderSDKMock: MatrixRustSDK.ClientBuilder {
}
set {
if Thread.isMainThread {
sessionPathPathUnderlyingReturnValue = newValue
sessionPathsDataPathCachePathUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
sessionPathPathUnderlyingReturnValue = newValue
sessionPathsDataPathCachePathUnderlyingReturnValue = newValue
}
}
}
}
open var sessionPathPathClosure: ((String) -> ClientBuilder)?
open var sessionPathsDataPathCachePathClosure: ((String, String?) -> ClientBuilder)?
open override func sessionPath(path: String) -> ClientBuilder {
sessionPathPathCallsCount += 1
sessionPathPathReceivedPath = path
open override func sessionPaths(dataPath: String, cachePath: String?) -> ClientBuilder {
sessionPathsDataPathCachePathCallsCount += 1
sessionPathsDataPathCachePathReceivedArguments = (dataPath: dataPath, cachePath: cachePath)
DispatchQueue.main.async {
self.sessionPathPathReceivedInvocations.append(path)
self.sessionPathsDataPathCachePathReceivedInvocations.append((dataPath: dataPath, cachePath: cachePath))
}
if let sessionPathPathClosure = sessionPathPathClosure {
return sessionPathPathClosure(path)
if let sessionPathsDataPathCachePathClosure = sessionPathsDataPathCachePathClosure {
return sessionPathsDataPathCachePathClosure(dataPath, cachePath)
} else {
return sessionPathPathReturnValue
return sessionPathsDataPathCachePathReturnValue
}
}
@@ -68,6 +68,22 @@ extension URL: ExpressibleByStringLiteral {
return url
}
/// The base directory where all application support data is stored.
static var cachesBaseDirectory: URL {
let url = appGroupContainerDirectory
.appendingPathComponent("Library", isDirectory: true)
.appendingPathComponent("Caches", isDirectory: true)
.appendingPathComponent(InfoPlistReader.main.baseBundleIdentifier, isDirectory: true)
.appendingPathComponent("Sessions", isDirectory: true)
try? FileManager.default.createDirectoryIfNeeded(at: url)
// Caches are excluded from backups automatically anyway.
// https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html
return url
}
var globalProxy: String? {
if let proxySettingsUnmanaged = CFNetworkCopySystemProxySettings() {
let proxySettings = proxySettingsUnmanaged.takeRetainedValue()
@@ -559,7 +559,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
fatalError("Only events can have send info.")
}
if eventTimelineItem.properties.deliveryStatus == .sendingFailed {
if case .sendingFailed = eventTimelineItem.properties.deliveryStatus {
// In the future we will show different errors for the various failure reasons.
displayAlert(.sendingFailed)
} else if let authenticityMessage = eventTimelineItem.properties.encryptionAuthenticity?.message {
displayAlert(.encryptionAuthenticity(authenticityMessage))
@@ -150,7 +150,7 @@ private extension TimelineItemSendInfo {
itemID = timelineItem.id
localizedString = timelineItem.localizedSendInfo
status = if adjustedDeliveryStatus == .sendingFailed {
status = if case .sendingFailed = adjustedDeliveryStatus {
.sendingFailed
} else if let authenticity = timelineItem.properties.encryptionAuthenticity {
.encryptionAuthenticity(authenticity)
@@ -35,11 +35,13 @@ struct TimelineStyler<Content: View>: View {
var body: some View {
mainContent
.onChange(of: timelineItem.properties.deliveryStatus) { newStatus in
if newStatus == .sendingFailed {
if case .sendingFailed = newStatus {
guard task == nil else {
return
}
task = Task {
// Add a short delay so that an immediate failure when retrying
// shows as sending for long enough to be visible to the user.
try? await Task.sleep(for: .milliseconds(700))
if !Task.isCancelled {
adjustedDeliveryStatus = newStatus
@@ -101,7 +103,7 @@ struct TimelineItemStyler_Previews: PreviewProvider, TestablePreview {
static let failed: TextRoomTimelineItem = {
var result = base
result.properties.deliveryStatus = .sendingFailed
result.properties.deliveryStatus = .sendingFailed(.unknown)
return result
}()
@@ -20,7 +20,7 @@ import MatrixRustSDK
class AuthenticationService: AuthenticationServiceProtocol {
private var client: Client?
private var sessionDirectory: URL
private var sessionDirectories: SessionDirectories
private let passphrase: String
private let userSessionStore: UserSessionStoreProtocol
@@ -31,7 +31,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { homeserverSubject.asCurrentValuePublisher() }
init(userSessionStore: UserSessionStoreProtocol, encryptionKeyProvider: EncryptionKeyProviderProtocol, appSettings: AppSettings, appHooks: AppHooks) {
sessionDirectory = .sessionsBaseDirectory.appending(component: UUID().uuidString)
sessionDirectories = .init()
passphrase = encryptionKeyProvider.generateKey().base64EncodedString()
self.userSessionStore = userSessionStore
self.appSettings = appSettings
@@ -149,20 +149,24 @@ class AuthenticationService: AuthenticationServiceProtocol {
slidingSyncProxy: appSettings.slidingSyncProxyURL,
sessionDelegate: userSessionStore.clientSessionDelegate,
appHooks: appHooks)
.sessionPath(path: sessionDirectory.path(percentEncoded: false))
.sessionPaths(dataPath: sessionDirectories.dataPath,
cachePath: sessionDirectories.cachePath)
.passphrase(passphrase: passphrase)
}
private func rotateSessionDirectory() {
if FileManager.default.directoryExists(at: sessionDirectory) {
try? FileManager.default.removeItem(at: sessionDirectory)
if FileManager.default.directoryExists(at: sessionDirectories.dataDirectory) {
try? FileManager.default.removeItem(at: sessionDirectories.dataDirectory)
}
if FileManager.default.directoryExists(at: sessionDirectories.cacheDirectory) {
try? FileManager.default.removeItem(at: sessionDirectories.cacheDirectory)
}
sessionDirectory = .sessionsBaseDirectory.appending(component: UUID().uuidString)
sessionDirectories = .init()
}
private func userSession(for client: Client) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
switch await userSessionStore.userSession(for: client, sessionDirectory: sessionDirectory, passphrase: passphrase) {
switch await userSessionStore.userSession(for: client, sessionDirectories: sessionDirectories, passphrase: passphrase) {
case .success(let clientProxy):
return .success(clientProxy)
case .failure:
@@ -120,6 +120,7 @@ class KeychainController: KeychainControllerProtocol {
}
let restorationToken = RestorationToken(session: session,
sessionDirectory: oldToken.sessionDirectory,
cacheDirectory: oldToken.cacheDirectory,
passphrase: oldToken.passphrase,
pusherNotificationClientIdentifier: oldToken.pusherNotificationClientIdentifier)
setRestorationToken(restorationToken, forUsername: session.userId)
@@ -20,7 +20,7 @@ import Foundation
import MatrixRustSDK
final class QRCodeLoginService: QRCodeLoginServiceProtocol {
private var sessionDirectory: URL
private var sessionDirectories: SessionDirectories
private let passphrase: String
private let userSessionStore: UserSessionStoreProtocol
@@ -36,7 +36,7 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
userSessionStore: UserSessionStoreProtocol,
appSettings: AppSettings,
appHooks: AppHooks) {
sessionDirectory = .sessionsBaseDirectory.appending(component: UUID().uuidString)
sessionDirectories = .init()
passphrase = encryptionKeyProvider.generateKey().base64EncodedString()
self.userSessionStore = userSessionStore
self.appSettings = appSettings
@@ -83,20 +83,24 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
slidingSyncProxy: appSettings.slidingSyncProxyURL,
sessionDelegate: userSessionStore.clientSessionDelegate,
appHooks: appHooks)
.sessionPath(path: sessionDirectory.path(percentEncoded: false))
.sessionPaths(dataPath: sessionDirectories.dataPath,
cachePath: sessionDirectories.cachePath)
.passphrase(passphrase: passphrase)
}
private func rotateSessionDirectory() {
if FileManager.default.directoryExists(at: sessionDirectory) {
try? FileManager.default.removeItem(at: sessionDirectory)
if FileManager.default.directoryExists(at: sessionDirectories.dataDirectory) {
try? FileManager.default.removeItem(at: sessionDirectories.dataDirectory)
}
if FileManager.default.directoryExists(at: sessionDirectories.cacheDirectory) {
try? FileManager.default.removeItem(at: sessionDirectories.cacheDirectory)
}
sessionDirectory = .sessionsBaseDirectory.appending(component: UUID().uuidString)
sessionDirectories = .init()
}
private func userSession(for client: Client) async -> Result<UserSessionProtocol, QRCodeLoginServiceError> {
switch await userSessionStore.userSession(for: client, sessionDirectory: sessionDirectory, passphrase: passphrase) {
switch await userSessionStore.userSession(for: client, sessionDirectories: sessionDirectories, passphrase: passphrase) {
case .success(let session):
return .success(session)
case .failure(let error):
@@ -28,6 +28,7 @@ enum EncryptionAuthenticity: Hashable {
case unknownDevice(color: Color)
case unsignedDevice(color: Color)
case unverifiedIdentity(color: Color)
case previouslyVerified(color: Color)
case sentInClear(color: Color)
var message: String {
@@ -40,6 +41,8 @@ enum EncryptionAuthenticity: Hashable {
L10n.eventShieldReasonUnsignedDevice
case .unverifiedIdentity:
L10n.eventShieldReasonUnverifiedIdentity
case .previouslyVerified:
L10n.eventShieldReasonPreviouslyVerified
case .sentInClear:
L10n.eventShieldReasonSentInClear
}
@@ -51,6 +54,7 @@ enum EncryptionAuthenticity: Hashable {
.unknownDevice(let color),
.unsignedDevice(let color),
.unverifiedIdentity(let color),
.previouslyVerified(let color),
.sentInClear(let color):
color
}
@@ -59,7 +63,7 @@ enum EncryptionAuthenticity: Hashable {
var icon: KeyPath<CompoundIcons, Image> {
switch self {
case .notGuaranteed: \.info
case .unknownDevice, .unsignedDevice, .unverifiedIdentity: \.helpSolid
case .unknownDevice, .unsignedDevice, .unverifiedIdentity, .previouslyVerified: \.helpSolid
case .sentInClear: \.lockOff
}
}
@@ -87,6 +91,8 @@ extension EncryptionAuthenticity {
self = .unsignedDevice(color: color)
case .unverifiedIdentity:
self = .unverifiedIdentity(color: color)
case .previouslyVerified:
self = .previouslyVerified(color: color)
case .sentInClear:
self = .sentInClear(color: color)
}
@@ -68,7 +68,20 @@ enum TimelineItemProxy {
enum TimelineItemDeliveryStatus: Hashable {
case sending
case sent
case sendingFailed
case sendingFailed(SendFailureReason)
enum SendFailureReason: Hashable {
case verifiedUserHasUnsignedDevice(devices: [String: [String]])
case verifiedUserChangedIdentity(users: [String])
case unknown
}
var isSendingFailed: Bool {
switch self {
case .sending, .sent: false
case .sendingFailed: true
}
}
}
/// A light wrapper around event timeline items returned from Rust.
@@ -88,11 +101,15 @@ class EventTimelineItemProxy {
switch localSendState {
case .sendingFailed(_, let isRecoverable):
return isRecoverable ? .sending : .sendingFailed
return isRecoverable ? .sending : .sendingFailed(.unknown)
case .notSentYet:
return .sending
case .sent:
return .sent
case .verifiedUserHasUnsignedDevice(devices: let devices):
return .sendingFailed(.verifiedUserHasUnsignedDevice(devices: devices))
case .verifiedUserChangedIdentity(users: let users):
return .sendingFailed(.verifiedUserChangedIdentity(users: users))
}
}()
@@ -56,7 +56,7 @@ extension EventBasedTimelineItemProtocol {
}
var hasFailedToSend: Bool {
properties.deliveryStatus == .sendingFailed
properties.deliveryStatus?.isSendingFailed == true
}
var hasFailedDecryption: Bool {
@@ -20,6 +20,7 @@ import MatrixRustSDK
struct RestorationToken: Equatable {
let session: MatrixRustSDK.Session
let sessionDirectory: URL
let cacheDirectory: URL
let passphrase: String?
let pusherNotificationClientIdentifier: String?
}
@@ -29,10 +30,22 @@ extension RestorationToken: Codable {
let container = try decoder.container(keyedBy: CodingKeys.self)
let session = try container.decode(MatrixRustSDK.Session.self, forKey: .session)
let sessionDirectory = try container.decodeIfPresent(URL.self, forKey: .sessionDirectory)
let dataDirectory = try container.decodeIfPresent(URL.self, forKey: .sessionDirectory)
let cacheDirectory = try container.decodeIfPresent(URL.self, forKey: .cacheDirectory)
let sessionDirectories = if let dataDirectory {
if let cacheDirectory {
SessionDirectories(dataDirectory: dataDirectory, cacheDirectory: cacheDirectory)
} else {
SessionDirectories(dataDirectory: dataDirectory)
}
} else {
SessionDirectories(userID: session.userId)
}
self = try .init(session: session,
sessionDirectory: sessionDirectory ?? .legacySessionDirectory(for: session.userId),
sessionDirectory: sessionDirectories.dataDirectory,
cacheDirectory: sessionDirectories.cacheDirectory,
passphrase: container.decodeIfPresent(String.self, forKey: .passphrase),
pusherNotificationClientIdentifier: container.decodeIfPresent(String.self, forKey: .pusherNotificationClientIdentifier))
}
@@ -66,18 +79,3 @@ extension MatrixRustSDK.Session: Codable {
case accessToken, refreshToken, userId, deviceId, homeserverUrl, oidcData, slidingSyncProxy
}
}
// MARK: Migrations
private extension URL {
/// Gets the store directory of a legacy session that hasn't been migrated to the new token format.
///
/// This should only be used to fill in the missing value when restoring a token as older versions of
/// the SDK set the session directory for us, based on the user's ID. Newer sessions now use a UUID,
/// which is generated app side during authentication.
static func legacySessionDirectory(for userID: String) -> URL {
// Rust sanitises the user ID replacing invalid characters with an _
let sanitisedUserID = userID.replacingOccurrences(of: ":", with: "_")
return .sessionsBaseDirectory.appendingPathComponent(sanitisedUserID)
}
}
@@ -0,0 +1,61 @@
//
// Copyright 2024 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
struct SessionDirectories: Hashable, Codable {
let dataDirectory: URL
let cacheDirectory: URL
var dataPath: String { dataDirectory.path(percentEncoded: false) }
var cachePath: String { cacheDirectory.path(percentEncoded: false) }
}
extension SessionDirectories {
/// Creates a fresh set of session directories for a new user.
init() {
let sessionDirectoryName = UUID().uuidString
dataDirectory = .sessionsBaseDirectory.appending(component: sessionDirectoryName)
cacheDirectory = .cachesBaseDirectory.appending(component: sessionDirectoryName)
}
/// Creates the session directories for a user who signed in before the data directory was stored.
init(userID: String) {
dataDirectory = .legacySessionDirectory(for: userID)
cacheDirectory = .cachesBaseDirectory.appending(component: dataDirectory.lastPathComponent)
}
/// Creates the session directories for a user who has a single session directory stored without a separate caches directory.
init(dataDirectory: URL) {
self.dataDirectory = dataDirectory
cacheDirectory = .cachesBaseDirectory.appending(component: dataDirectory.lastPathComponent)
}
}
// MARK: Migrations
private extension URL {
/// Gets the store directory of a legacy session that hasn't been migrated to the new token format.
///
/// This should only be used to fill in the missing value when restoring a token as older versions of
/// the SDK set the session directory for us, based on the user's ID. Newer sessions now use a UUID,
/// which is generated app side during authentication.
static func legacySessionDirectory(for userID: String) -> URL {
// Rust sanitises the user ID replacing invalid characters with an _
let sanitisedUserID = userID.replacingOccurrences(of: ":", with: "_")
return .sessionsBaseDirectory.appendingPathComponent(sanitisedUserID)
}
}
@@ -64,20 +64,21 @@ class UserSessionStore: UserSessionStoreProtocol {
// On any restoration failure reset the token and restart
keychainController.removeRestorationTokenForUsername(credentials.userID)
deleteSessionDirectory(for: credentials)
deleteSessionDirectories(for: credentials)
return .failure(error)
}
}
func userSession(for client: Client, sessionDirectory: URL, passphrase: String?) async -> Result<UserSessionProtocol, UserSessionStoreError> {
func userSession(for client: Client, sessionDirectories: SessionDirectories, passphrase: String?) async -> Result<UserSessionProtocol, UserSessionStoreError> {
do {
let session = try client.session()
let userID = try client.userId()
let clientProxy = await setupProxyForClient(client)
keychainController.setRestorationToken(RestorationToken(session: session,
sessionDirectory: sessionDirectory,
sessionDirectory: sessionDirectories.dataDirectory,
cacheDirectory: sessionDirectories.cacheDirectory,
passphrase: passphrase,
pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier),
forUsername: userID)
@@ -95,7 +96,7 @@ class UserSessionStore: UserSessionStoreProtocol {
keychainController.removeRestorationTokenForUsername(userID)
if let credentials {
deleteSessionDirectory(for: credentials)
deleteSessionDirectories(for: credentials)
}
}
@@ -133,7 +134,8 @@ class UserSessionStore: UserSessionStoreProtocol {
slidingSync: appSettings.simplifiedSlidingSyncEnabled ? .simplified : .restored,
sessionDelegate: keychainController,
appHooks: appHooks)
.sessionPath(path: credentials.restorationToken.sessionDirectory.path(percentEncoded: false))
.sessionPaths(dataPath: credentials.restorationToken.sessionDirectory.path(percentEncoded: false),
cachePath: credentials.restorationToken.cacheDirectory.path(percentEncoded: false))
.username(username: credentials.userID)
.homeserverUrl(url: homeserverURL)
.passphrase(passphrase: credentials.restorationToken.passphrase)
@@ -156,22 +158,36 @@ class UserSessionStore: UserSessionStoreProtocol {
appSettings: appSettings)
}
private func deleteSessionDirectory(for credentials: KeychainCredentials) {
private func deleteSessionDirectories(for credentials: KeychainCredentials) {
do {
try FileManager.default.removeItem(at: credentials.restorationToken.sessionDirectory)
} catch {
MXLog.failure("Failed deleting the session data: \(error)")
}
do {
try FileManager.default.removeItem(at: credentials.restorationToken.cacheDirectory)
} catch {
MXLog.failure("Failed deleting the session caches: \(error)")
}
}
private func deleteCaches(for credentials: KeychainCredentials) {
do {
let sessionDirectoryContents = try FileManager.default.contentsOfDirectory(at: credentials.restorationToken.sessionDirectory, includingPropertiesForKeys: nil)
for url in sessionDirectoryContents where url.path.contains(matrixSDKStateKey) {
try FileManager.default.removeItem(at: url)
}
try deleteContentsOfDirectory(at: credentials.restorationToken.sessionDirectory)
} catch {
MXLog.failure("Failed clearing caches: \(error)")
MXLog.failure("Failed clearing state store: \(error)")
}
do {
try deleteContentsOfDirectory(at: credentials.restorationToken.cacheDirectory)
} catch {
MXLog.failure("Failed clearing event cache store: \(error)")
}
}
private func deleteContentsOfDirectory(at url: URL) throws {
let sessionDirectoryContents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
for url in sessionDirectoryContents where url.path.contains(matrixSDKStateKey) {
try FileManager.default.removeItem(at: url)
}
}
}
@@ -40,7 +40,7 @@ protocol UserSessionStoreProtocol {
func restoreUserSession() async -> Result<UserSessionProtocol, UserSessionStoreError>
/// Creates a user session for a new client from the SDK along with the passphrase used for the data stores.
func userSession(for client: Client, sessionDirectory: URL, passphrase: String?) async -> Result<UserSessionProtocol, UserSessionStoreError>
func userSession(for client: Client, sessionDirectories: SessionDirectories, passphrase: String?) async -> Result<UserSessionProtocol, UserSessionStoreError>
/// Logs out of the specified session.
func logout(userSession: UserSessionProtocol)