mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 13:37:11 +00:00
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:
parent
ebef0d1fbe
commit
812c5d5c61
@ -509,6 +509,7 @@
|
||||
755395927DDD6EBDDA5E217A /* SettingsFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F7A6CEEA4A2815B0F0F55 /* SettingsFlowCoordinator.swift */; };
|
||||
755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */; };
|
||||
756EA0D663261889EF64E6D4 /* VoiceMessageRecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E9CBF577B9711CFBB4FA40D /* VoiceMessageRecordingView.swift */; };
|
||||
7573D682F089205F7F1D96CF /* SessionDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2067FF58B4996323EB40C /* SessionDirectories.swift */; };
|
||||
762DAF94846C7AC8550F1CC1 /* MediaPlayerProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E23D8EE6CBACF32F1EC874 /* MediaPlayerProviderProtocol.swift */; };
|
||||
762DB0973865293F0C3D3D7B /* SessionVerificationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */; };
|
||||
763D69741D58D2B650BC1FC9 /* CallScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37FA1A5D55633E1942B153B /* CallScreenCoordinator.swift */; };
|
||||
@ -642,6 +643,7 @@
|
||||
90733645AE76FB33DAD28C2B /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE40D4A5DD857AC16EED945A /* URLSession.swift */; };
|
||||
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; };
|
||||
90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; };
|
||||
914BDF61447C723F104BCE33 /* SessionDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2067FF58B4996323EB40C /* SessionDirectories.swift */; };
|
||||
915B4CDAF220D9AEB4047D45 /* PollInteractionHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */; };
|
||||
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
|
||||
91C6AC0E9D2B9C0C76CC6AD4 /* RoomDirectorySearchScreenScreenModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.swift */; };
|
||||
@ -870,6 +872,7 @@
|
||||
C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E508AB0EDEE017FF4F6F8D1 /* DTHTMLElement+AttributedStringBuilder.swift */; };
|
||||
C4FE0E11A907C8999F92D5A8 /* TimelineStartRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F5F9E02B1AB5350B1815E7 /* TimelineStartRoomTimelineItem.swift */; };
|
||||
C55A44C99F64A479ABA85B46 /* RoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */; };
|
||||
C5627BCC3EBBB96A943B6D93 /* RestorationTokenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7978C9EFBDD7DE39BD86726 /* RestorationTokenTests.swift */; };
|
||||
C58E305C380D3ADDF7912180 /* StickerRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 818695BED971753243FEF897 /* StickerRoomTimelineItem.swift */; };
|
||||
C5A07E2D88BE7D51DCECD166 /* LoginScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */; };
|
||||
C67FCC854F3A6FC7A2EC04D0 /* MediaUploadPreviewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C86696AC9521F8ED88FBEB /* MediaUploadPreviewScreen.swift */; };
|
||||
@ -899,6 +902,7 @@
|
||||
CBD2ABE4C1A47ECD99E1488E /* NotificationSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421FA93BCC2840E66E4F306F /* NotificationSettingsScreenViewModelProtocol.swift */; };
|
||||
CBFF4F1BFA90B46241B8106C /* Strings+SAS.swift in Sources */ = {isa = PBXBuildFile; fileRef = B172057567E049007A5C4D92 /* Strings+SAS.swift */; };
|
||||
CC0D088F505F33A20DC5590F /* RoomStateEventStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEEAFB646E583655652C3D04 /* RoomStateEventStringBuilderTests.swift */; };
|
||||
CC1C948F67A5510A340FD7F0 /* SessionDirectoriesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0825EAFD47332DD459DE893F /* SessionDirectoriesTests.swift */; };
|
||||
CC961529F9F1854BEC3272C9 /* LayoutMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC8AA23D4F37CC26564F63C5 /* LayoutMocks.swift */; };
|
||||
CCBEC2100CAF2EEBE9DB4156 /* TemplateScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA40B98B098B6F0371B750B3 /* TemplateScreenModels.swift */; };
|
||||
CD0088B763CD970CF1CBF8CB /* DateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5E97E9615A158C76B2AB77 /* DateTests.swift */; };
|
||||
@ -1215,6 +1219,7 @@
|
||||
07579F9C29001E40715F3014 /* NotificationSettingsChatType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsChatType.swift; sourceTree = "<group>"; };
|
||||
077D7C3BE199B6E5DDEC07EC /* AppCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorStateMachine.swift; sourceTree = "<group>"; };
|
||||
07C6B0B087FE6601C3F77816 /* JoinedRoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinedRoomProxy.swift; sourceTree = "<group>"; };
|
||||
0825EAFD47332DD459DE893F /* SessionDirectoriesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionDirectoriesTests.swift; sourceTree = "<group>"; };
|
||||
08283301736A6FE9D558B2CB /* AppLockScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
0833F51229E166BCA141D004 /* RoomRolesAndPermissionsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsFlowCoordinator.swift; sourceTree = "<group>"; };
|
||||
086B997409328F091EBA43CE /* RoomScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenUITests.swift; sourceTree = "<group>"; };
|
||||
@ -1478,6 +1483,7 @@
|
||||
42C8C368A611B9CB79C7F5FA /* RoomPollsHistoryScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreen.swift; sourceTree = "<group>"; };
|
||||
436A0D98D372B17EAE9AA999 /* GlobalSearchScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearchScreenModels.swift; sourceTree = "<group>"; };
|
||||
43A84EE187D0C772E18A4E39 /* VoiceMessageCacheProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageCacheProtocol.swift; sourceTree = "<group>"; };
|
||||
43C2067FF58B4996323EB40C /* SessionDirectories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionDirectories.swift; sourceTree = "<group>"; };
|
||||
4481799F455B3DA243BDA2AC /* ShareToMapsAppActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareToMapsAppActivity.swift; sourceTree = "<group>"; };
|
||||
44ABA63DBE7F76C58260B43B /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
44C314C00533E2C297796B60 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
@ -1878,6 +1884,7 @@
|
||||
A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriverProtocol.swift; sourceTree = "<group>"; };
|
||||
A6EA0D8B0BBD8805F7D5A133 /* TextBasedRoomTimelineViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineViewProtocol.swift; sourceTree = "<group>"; };
|
||||
A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = "<group>"; };
|
||||
A7978C9EFBDD7DE39BD86726 /* RestorationTokenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationTokenTests.swift; sourceTree = "<group>"; };
|
||||
A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = "<group>"; };
|
||||
A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
A7E37072597F67C4DD8CC2DB /* ComposerDraftServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerDraftServiceProtocol.swift; sourceTree = "<group>"; };
|
||||
@ -3763,6 +3770,7 @@
|
||||
347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */,
|
||||
25E7E9B7FEAB6169D960C206 /* QRCodeLoginScreenViewModelTests.swift */,
|
||||
086C19086DD16E9B38E25954 /* ReportContentViewModelTests.swift */,
|
||||
A7978C9EFBDD7DE39BD86726 /* RestorationTokenTests.swift */,
|
||||
41D041A857614A9AE13C7795 /* RoomChangePermissionsScreenViewModelTests.swift */,
|
||||
8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */,
|
||||
00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */,
|
||||
@ -3786,6 +3794,7 @@
|
||||
277C20CDD5B64510401B6D0D /* ServerConfigurationScreenViewStateTests.swift */,
|
||||
F08776C48FFB47CACF64ED10 /* ServerConfirmationScreenViewModelTests.swift */,
|
||||
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */,
|
||||
0825EAFD47332DD459DE893F /* SessionDirectoriesTests.swift */,
|
||||
A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */,
|
||||
DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */,
|
||||
3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */,
|
||||
@ -4916,6 +4925,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3558A15CFB934F9229301527 /* RestorationToken.swift */,
|
||||
43C2067FF58B4996323EB40C /* SessionDirectories.swift */,
|
||||
0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */,
|
||||
BEBA759D1347CFFB3D84ED1F /* UserSessionStoreProtocol.swift */,
|
||||
);
|
||||
@ -5932,6 +5942,7 @@
|
||||
414F50CFCFEEE2611127DCFB /* RestorationToken.swift in Sources */,
|
||||
17BC15DA08A52587466698C5 /* RoomMessageEventStringBuilder.swift in Sources */,
|
||||
7354D094A4C59B555F407FA1 /* RustTracing.swift in Sources */,
|
||||
7573D682F089205F7F1D96CF /* SessionDirectories.swift in Sources */,
|
||||
422E8D182CA688D4565CD1E1 /* String.swift in Sources */,
|
||||
CBFF4F1BFA90B46241B8106C /* Strings+SAS.swift in Sources */,
|
||||
ECA636DAF071C611FDC2BB57 /* Strings+Untranslated.swift in Sources */,
|
||||
@ -6020,6 +6031,7 @@
|
||||
D415764645491F10344FC6AC /* Publisher.swift in Sources */,
|
||||
BDC4EB54CC3036730475CB8B /* QRCodeLoginScreenViewModelTests.swift in Sources */,
|
||||
D53B80EF02C1062E68659EDD /* ReportContentViewModelTests.swift in Sources */,
|
||||
C5627BCC3EBBB96A943B6D93 /* RestorationTokenTests.swift in Sources */,
|
||||
9B03943616A1147539DF7F08 /* RoomChangePermissionsScreenViewModelTests.swift in Sources */,
|
||||
D2825E013A8ECFB66D9A1DE6 /* RoomChangeRolesScreenViewModelTests.swift in Sources */,
|
||||
9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */,
|
||||
@ -6043,6 +6055,7 @@
|
||||
53A55748D5F587C9061F98BF /* ServerConfigurationScreenViewStateTests.swift in Sources */,
|
||||
89658A44C9FC19B58FD1C226 /* ServerConfirmationScreenViewModelTests.swift in Sources */,
|
||||
93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */,
|
||||
CC1C948F67A5510A340FD7F0 /* SessionDirectoriesTests.swift in Sources */,
|
||||
86675910612A12409262DFBD /* SessionVerificationStateMachineTests.swift in Sources */,
|
||||
755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */,
|
||||
206F0DBAB6AF042CA1FF2C0D /* SettingsViewModelTests.swift in Sources */,
|
||||
@ -6718,6 +6731,7 @@
|
||||
85F89F3F320F4FADCFFFE68B /* ServerSelectionScreenViewModel.swift in Sources */,
|
||||
0C47AE2CA7929CB3B0E2D793 /* ServerSelectionScreenViewModelProtocol.swift in Sources */,
|
||||
BD782053BE4C3D2F0BDE5699 /* ServiceLocator.swift in Sources */,
|
||||
914BDF61447C723F104BCE33 /* SessionDirectories.swift in Sources */,
|
||||
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */,
|
||||
AE1A73B24D63DA3D63DC4EE3 /* SessionVerificationControllerProxyMock.swift in Sources */,
|
||||
94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */,
|
||||
@ -7665,7 +7679,7 @@
|
||||
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 1.0.40;
|
||||
version = 1.0.42;
|
||||
};
|
||||
};
|
||||
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
||||
|
@ -149,8 +149,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
||||
"state" : {
|
||||
"revision" : "5d9f1865a71badfe6d9f7c3232b6cf23b12f8add",
|
||||
"version" : "1.0.40"
|
||||
"revision" : "ccae0615642728bbadcd051e4851d96ab298bab2",
|
||||
"version" : "1.0.42"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -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.";
|
||||
|
@ -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)
|
||||
|
@ -39,7 +39,8 @@ final class NSEUserSession {
|
||||
slidingSync: simplifiedSlidingSyncEnabled ? .simplified : .restored,
|
||||
sessionDelegate: clientSessionDelegate,
|
||||
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)
|
||||
|
@ -111,3 +111,4 @@ targets:
|
||||
- path: ../../ElementX/Sources/Services/Notification/Proxy
|
||||
- path: ../../ElementX/Sources/Services/Room/RoomSummary/RoomMessageEventStringBuilder.swift
|
||||
- path: ../../ElementX/Sources/Services/UserSession/RestorationToken.swift
|
||||
- path: ../../ElementX/Sources/Services/UserSession/SessionDirectories.swift
|
||||
|
@ -41,6 +41,7 @@ class KeychainControllerTests: XCTestCase {
|
||||
oidcData: "oidcData",
|
||||
slidingSyncProxy: "https://my.sync.proxy"),
|
||||
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||
passphrase: "passphrase",
|
||||
pusherNotificationClientIdentifier: "pusherClientID")
|
||||
keychain.setRestorationToken(restorationToken, forUsername: username)
|
||||
@ -60,6 +61,7 @@ class KeychainControllerTests: XCTestCase {
|
||||
oidcData: "oidcData",
|
||||
slidingSyncProxy: "https://my.sync.proxy"),
|
||||
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||
passphrase: "passphrase",
|
||||
pusherNotificationClientIdentifier: "pusherClientID")
|
||||
keychain.setRestorationToken(restorationToken, forUsername: username)
|
||||
@ -85,6 +87,7 @@ class KeychainControllerTests: XCTestCase {
|
||||
oidcData: "oidcData",
|
||||
slidingSyncProxy: "https://my.sync.proxy"),
|
||||
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||
passphrase: "passphrase",
|
||||
pusherNotificationClientIdentifier: "pusherClientID")
|
||||
keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com")
|
||||
@ -109,6 +112,7 @@ class KeychainControllerTests: XCTestCase {
|
||||
oidcData: "oidcData",
|
||||
slidingSyncProxy: "https://my.sync.proxy"),
|
||||
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||
passphrase: "passphrase",
|
||||
pusherNotificationClientIdentifier: "pusherClientID")
|
||||
keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com")
|
||||
@ -141,6 +145,7 @@ class KeychainControllerTests: XCTestCase {
|
||||
oidcData: "oidcData",
|
||||
slidingSyncProxy: nil),
|
||||
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||
passphrase: "passphrase",
|
||||
pusherNotificationClientIdentifier: "pusherClientID")
|
||||
keychain.setRestorationToken(restorationToken, forUsername: username)
|
||||
|
108
UnitTests/Sources/RestorationTokenTests.swift
Normal file
108
UnitTests/Sources/RestorationTokenTests.swift
Normal file
@ -0,0 +1,108 @@
|
||||
//
|
||||
// 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 XCTest
|
||||
|
||||
@testable import ElementX
|
||||
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,
|
||||
slidingSyncProxy: "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.")
|
||||
XCTAssertNil(decodedToken.passphrase, "There should not be a passphrase.")
|
||||
XCTAssertNil(decodedToken.pusherNotificationClientIdentifier, "There should not be a push notification client ID.")
|
||||
XCTAssertEqual(decodedToken.sessionDirectory, .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.cacheDirectory, .cachesBaseDirectory.appending(component: "@user_example.com"),
|
||||
"The cache directory should be derived from the session directory but in the caches directory.")
|
||||
}
|
||||
|
||||
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",
|
||||
slidingSyncProxy: "https://sync.example.com"),
|
||||
sessionDirectory: .sessionsBaseDirectory.appending(component: sessionDirectoryName),
|
||||
passphrase: "passphrase",
|
||||
pusherNotificationClientIdentifier: "pusher-identifier")
|
||||
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.")
|
||||
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.")
|
||||
XCTAssertEqual(decodedToken.sessionDirectory, originalToken.sessionDirectory, "The session directory should not be changed.")
|
||||
XCTAssertEqual(decodedToken.cacheDirectory, .cachesBaseDirectory.appending(component: sessionDirectoryName),
|
||||
"The cache directory should be derived from the session directory but in the caches directory.")
|
||||
}
|
||||
|
||||
func testDecodeFromCurrentToken() throws {
|
||||
// Given an encoded restoration token in the current format.
|
||||
let sessionDirectoryName = UUID().uuidString
|
||||
let originalToken = RestorationToken(session: Session(accessToken: "1234",
|
||||
refreshToken: "5678",
|
||||
userId: "@user:example.com",
|
||||
deviceId: "D3V1C3",
|
||||
homeserverUrl: "https://matrix.example.com",
|
||||
oidcData: "data-from-mas",
|
||||
slidingSyncProxy: nil),
|
||||
sessionDirectory: .sessionsBaseDirectory.appending(component: sessionDirectoryName),
|
||||
cacheDirectory: .cachesBaseDirectory.appending(component: sessionDirectoryName),
|
||||
passphrase: "passphrase",
|
||||
pusherNotificationClientIdentifier: "pusher-identifier")
|
||||
let data = try JSONEncoder().encode(originalToken)
|
||||
|
||||
// When decoding the data.
|
||||
let decodedToken = try JSONDecoder().decode(RestorationToken.self, from: data)
|
||||
|
||||
// Then the output should be a valid token.
|
||||
XCTAssertEqual(decodedToken, originalToken, "The token should remain identical.")
|
||||
}
|
||||
}
|
||||
|
||||
struct RestorationTokenV1: Equatable, Codable {
|
||||
let session: MatrixRustSDK.Session
|
||||
}
|
||||
|
||||
struct RestorationTokenV4: Equatable, Codable {
|
||||
let session: MatrixRustSDK.Session
|
||||
let sessionDirectory: URL
|
||||
let passphrase: String?
|
||||
let pusherNotificationClientIdentifier: String?
|
||||
}
|
63
UnitTests/Sources/SessionDirectoriesTests.swift
Normal file
63
UnitTests/Sources/SessionDirectoriesTests.swift
Normal file
@ -0,0 +1,63 @@
|
||||
//
|
||||
// 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 XCTest
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
class SessionDirectoriesTests: XCTestCase {
|
||||
func testInitWithUserID() {
|
||||
// Given only a user ID.
|
||||
let userID = "@user:matrix.org"
|
||||
|
||||
// When creating the session directories using this.
|
||||
let sessionDirectories = SessionDirectories(userID: userID)
|
||||
|
||||
// Then the directories should be generated in the correct location, using an escaped version of the user ID
|
||||
XCTAssertEqual(sessionDirectories.dataDirectory, .sessionsBaseDirectory.appending(component: "@user_matrix.org"))
|
||||
XCTAssertEqual(sessionDirectories.cacheDirectory, .cachesBaseDirectory.appending(component: "@user_matrix.org"))
|
||||
}
|
||||
|
||||
func testInitWithDataDirectory() {
|
||||
// Given only a session directory without a caches directory.
|
||||
let sessionDirectoryName = UUID().uuidString
|
||||
let sessionDirectory = URL.applicationSupportBaseDirectory.appending(component: sessionDirectoryName)
|
||||
|
||||
// When creating the session directories using this.
|
||||
let sessionDirectories = SessionDirectories(dataDirectory: sessionDirectory)
|
||||
|
||||
// Then the data directory should remain unchanged and the caches directory should be generated.
|
||||
XCTAssertEqual(sessionDirectories.dataDirectory, sessionDirectory)
|
||||
XCTAssertEqual(sessionDirectories.cacheDirectory, .cachesBaseDirectory.appending(component: sessionDirectoryName))
|
||||
}
|
||||
|
||||
func testPathOutput() {
|
||||
// Given session directories created from paths with spaces in them.
|
||||
let originalDataPath = "/Users/John Smith/Data"
|
||||
let originalCachePath = "/Users/John Smith/Caches"
|
||||
let dataDirectory = URL(filePath: originalDataPath)
|
||||
let cacheDirectory = URL(filePath: originalCachePath)
|
||||
let sessionDirectories = SessionDirectories(dataDirectory: dataDirectory, cacheDirectory: cacheDirectory)
|
||||
|
||||
// When getting the paths from the session directories struct.
|
||||
let returnedDataPath = sessionDirectories.dataPath
|
||||
let returnedCachePath = sessionDirectories.cachePath
|
||||
|
||||
// Then the paths should not be escaped.
|
||||
XCTAssertEqual(returnedDataPath, originalDataPath)
|
||||
XCTAssertEqual(returnedCachePath, originalCachePath)
|
||||
}
|
||||
}
|
@ -70,7 +70,7 @@ final class TextBasedRoomTimelineTests: XCTestCase {
|
||||
sender: .init(id: UUID().uuidString),
|
||||
content: .init(body: "Test"))
|
||||
timelineItem.properties.isEdited = true
|
||||
timelineItem.properties.deliveryStatus = .sendingFailed
|
||||
timelineItem.properties.deliveryStatus = .sendingFailed(.unknown)
|
||||
let editedCount = L10n.commonEditedSuffix.count
|
||||
XCTAssertEqual(timelineItem.additionalWhitespaces(), timestamp.count + editedCount + 5)
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ packages:
|
||||
# Element/Matrix dependencies
|
||||
MatrixRustSDK:
|
||||
url: https://github.com/element-hq/matrix-rust-components-swift
|
||||
exactVersion: 1.0.40
|
||||
exactVersion: 1.0.42
|
||||
# path: ../matrix-rust-sdk
|
||||
Compound:
|
||||
url: https://github.com/element-hq/compound-ios
|
||||
|
Loading…
x
Reference in New Issue
Block a user