mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +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 */; };
|
755395927DDD6EBDDA5E217A /* SettingsFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F7A6CEEA4A2815B0F0F55 /* SettingsFlowCoordinator.swift */; };
|
||||||
755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */; };
|
755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */; };
|
||||||
756EA0D663261889EF64E6D4 /* VoiceMessageRecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E9CBF577B9711CFBB4FA40D /* VoiceMessageRecordingView.swift */; };
|
756EA0D663261889EF64E6D4 /* VoiceMessageRecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E9CBF577B9711CFBB4FA40D /* VoiceMessageRecordingView.swift */; };
|
||||||
|
7573D682F089205F7F1D96CF /* SessionDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2067FF58B4996323EB40C /* SessionDirectories.swift */; };
|
||||||
762DAF94846C7AC8550F1CC1 /* MediaPlayerProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E23D8EE6CBACF32F1EC874 /* MediaPlayerProviderProtocol.swift */; };
|
762DAF94846C7AC8550F1CC1 /* MediaPlayerProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E23D8EE6CBACF32F1EC874 /* MediaPlayerProviderProtocol.swift */; };
|
||||||
762DB0973865293F0C3D3D7B /* SessionVerificationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */; };
|
762DB0973865293F0C3D3D7B /* SessionVerificationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D452AF7B5F7E3A0A7DB54C /* SessionVerificationScreenViewModelProtocol.swift */; };
|
||||||
763D69741D58D2B650BC1FC9 /* CallScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37FA1A5D55633E1942B153B /* CallScreenCoordinator.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 */; };
|
90733645AE76FB33DAD28C2B /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE40D4A5DD857AC16EED945A /* URLSession.swift */; };
|
||||||
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; };
|
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; };
|
||||||
90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.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 */; };
|
915B4CDAF220D9AEB4047D45 /* PollInteractionHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */; };
|
||||||
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
|
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
|
||||||
91C6AC0E9D2B9C0C76CC6AD4 /* RoomDirectorySearchScreenScreenModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.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 */; };
|
C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E508AB0EDEE017FF4F6F8D1 /* DTHTMLElement+AttributedStringBuilder.swift */; };
|
||||||
C4FE0E11A907C8999F92D5A8 /* TimelineStartRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F5F9E02B1AB5350B1815E7 /* TimelineStartRoomTimelineItem.swift */; };
|
C4FE0E11A907C8999F92D5A8 /* TimelineStartRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F5F9E02B1AB5350B1815E7 /* TimelineStartRoomTimelineItem.swift */; };
|
||||||
C55A44C99F64A479ABA85B46 /* RoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5221DFDF809142A2D6AC82B9 /* RoomScreen.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 */; };
|
C58E305C380D3ADDF7912180 /* StickerRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 818695BED971753243FEF897 /* StickerRoomTimelineItem.swift */; };
|
||||||
C5A07E2D88BE7D51DCECD166 /* LoginScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */; };
|
C5A07E2D88BE7D51DCECD166 /* LoginScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */; };
|
||||||
C67FCC854F3A6FC7A2EC04D0 /* MediaUploadPreviewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C86696AC9521F8ED88FBEB /* MediaUploadPreviewScreen.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 */; };
|
CBD2ABE4C1A47ECD99E1488E /* NotificationSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421FA93BCC2840E66E4F306F /* NotificationSettingsScreenViewModelProtocol.swift */; };
|
||||||
CBFF4F1BFA90B46241B8106C /* Strings+SAS.swift in Sources */ = {isa = PBXBuildFile; fileRef = B172057567E049007A5C4D92 /* Strings+SAS.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 */; };
|
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 */; };
|
CC961529F9F1854BEC3272C9 /* LayoutMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC8AA23D4F37CC26564F63C5 /* LayoutMocks.swift */; };
|
||||||
CCBEC2100CAF2EEBE9DB4156 /* TemplateScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA40B98B098B6F0371B750B3 /* TemplateScreenModels.swift */; };
|
CCBEC2100CAF2EEBE9DB4156 /* TemplateScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA40B98B098B6F0371B750B3 /* TemplateScreenModels.swift */; };
|
||||||
CD0088B763CD970CF1CBF8CB /* DateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5E97E9615A158C76B2AB77 /* DateTests.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
A7E37072597F67C4DD8CC2DB /* ComposerDraftServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerDraftServiceProtocol.swift; sourceTree = "<group>"; };
|
||||||
@ -3763,6 +3770,7 @@
|
|||||||
347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */,
|
347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */,
|
||||||
25E7E9B7FEAB6169D960C206 /* QRCodeLoginScreenViewModelTests.swift */,
|
25E7E9B7FEAB6169D960C206 /* QRCodeLoginScreenViewModelTests.swift */,
|
||||||
086C19086DD16E9B38E25954 /* ReportContentViewModelTests.swift */,
|
086C19086DD16E9B38E25954 /* ReportContentViewModelTests.swift */,
|
||||||
|
A7978C9EFBDD7DE39BD86726 /* RestorationTokenTests.swift */,
|
||||||
41D041A857614A9AE13C7795 /* RoomChangePermissionsScreenViewModelTests.swift */,
|
41D041A857614A9AE13C7795 /* RoomChangePermissionsScreenViewModelTests.swift */,
|
||||||
8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */,
|
8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */,
|
||||||
00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */,
|
00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */,
|
||||||
@ -3786,6 +3794,7 @@
|
|||||||
277C20CDD5B64510401B6D0D /* ServerConfigurationScreenViewStateTests.swift */,
|
277C20CDD5B64510401B6D0D /* ServerConfigurationScreenViewStateTests.swift */,
|
||||||
F08776C48FFB47CACF64ED10 /* ServerConfirmationScreenViewModelTests.swift */,
|
F08776C48FFB47CACF64ED10 /* ServerConfirmationScreenViewModelTests.swift */,
|
||||||
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */,
|
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */,
|
||||||
|
0825EAFD47332DD459DE893F /* SessionDirectoriesTests.swift */,
|
||||||
A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */,
|
A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */,
|
||||||
DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */,
|
DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */,
|
||||||
3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */,
|
3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */,
|
||||||
@ -4916,6 +4925,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
3558A15CFB934F9229301527 /* RestorationToken.swift */,
|
3558A15CFB934F9229301527 /* RestorationToken.swift */,
|
||||||
|
43C2067FF58B4996323EB40C /* SessionDirectories.swift */,
|
||||||
0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */,
|
0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */,
|
||||||
BEBA759D1347CFFB3D84ED1F /* UserSessionStoreProtocol.swift */,
|
BEBA759D1347CFFB3D84ED1F /* UserSessionStoreProtocol.swift */,
|
||||||
);
|
);
|
||||||
@ -5932,6 +5942,7 @@
|
|||||||
414F50CFCFEEE2611127DCFB /* RestorationToken.swift in Sources */,
|
414F50CFCFEEE2611127DCFB /* RestorationToken.swift in Sources */,
|
||||||
17BC15DA08A52587466698C5 /* RoomMessageEventStringBuilder.swift in Sources */,
|
17BC15DA08A52587466698C5 /* RoomMessageEventStringBuilder.swift in Sources */,
|
||||||
7354D094A4C59B555F407FA1 /* RustTracing.swift in Sources */,
|
7354D094A4C59B555F407FA1 /* RustTracing.swift in Sources */,
|
||||||
|
7573D682F089205F7F1D96CF /* SessionDirectories.swift in Sources */,
|
||||||
422E8D182CA688D4565CD1E1 /* String.swift in Sources */,
|
422E8D182CA688D4565CD1E1 /* String.swift in Sources */,
|
||||||
CBFF4F1BFA90B46241B8106C /* Strings+SAS.swift in Sources */,
|
CBFF4F1BFA90B46241B8106C /* Strings+SAS.swift in Sources */,
|
||||||
ECA636DAF071C611FDC2BB57 /* Strings+Untranslated.swift in Sources */,
|
ECA636DAF071C611FDC2BB57 /* Strings+Untranslated.swift in Sources */,
|
||||||
@ -6020,6 +6031,7 @@
|
|||||||
D415764645491F10344FC6AC /* Publisher.swift in Sources */,
|
D415764645491F10344FC6AC /* Publisher.swift in Sources */,
|
||||||
BDC4EB54CC3036730475CB8B /* QRCodeLoginScreenViewModelTests.swift in Sources */,
|
BDC4EB54CC3036730475CB8B /* QRCodeLoginScreenViewModelTests.swift in Sources */,
|
||||||
D53B80EF02C1062E68659EDD /* ReportContentViewModelTests.swift in Sources */,
|
D53B80EF02C1062E68659EDD /* ReportContentViewModelTests.swift in Sources */,
|
||||||
|
C5627BCC3EBBB96A943B6D93 /* RestorationTokenTests.swift in Sources */,
|
||||||
9B03943616A1147539DF7F08 /* RoomChangePermissionsScreenViewModelTests.swift in Sources */,
|
9B03943616A1147539DF7F08 /* RoomChangePermissionsScreenViewModelTests.swift in Sources */,
|
||||||
D2825E013A8ECFB66D9A1DE6 /* RoomChangeRolesScreenViewModelTests.swift in Sources */,
|
D2825E013A8ECFB66D9A1DE6 /* RoomChangeRolesScreenViewModelTests.swift in Sources */,
|
||||||
9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */,
|
9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */,
|
||||||
@ -6043,6 +6055,7 @@
|
|||||||
53A55748D5F587C9061F98BF /* ServerConfigurationScreenViewStateTests.swift in Sources */,
|
53A55748D5F587C9061F98BF /* ServerConfigurationScreenViewStateTests.swift in Sources */,
|
||||||
89658A44C9FC19B58FD1C226 /* ServerConfirmationScreenViewModelTests.swift in Sources */,
|
89658A44C9FC19B58FD1C226 /* ServerConfirmationScreenViewModelTests.swift in Sources */,
|
||||||
93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */,
|
93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */,
|
||||||
|
CC1C948F67A5510A340FD7F0 /* SessionDirectoriesTests.swift in Sources */,
|
||||||
86675910612A12409262DFBD /* SessionVerificationStateMachineTests.swift in Sources */,
|
86675910612A12409262DFBD /* SessionVerificationStateMachineTests.swift in Sources */,
|
||||||
755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */,
|
755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */,
|
||||||
206F0DBAB6AF042CA1FF2C0D /* SettingsViewModelTests.swift in Sources */,
|
206F0DBAB6AF042CA1FF2C0D /* SettingsViewModelTests.swift in Sources */,
|
||||||
@ -6718,6 +6731,7 @@
|
|||||||
85F89F3F320F4FADCFFFE68B /* ServerSelectionScreenViewModel.swift in Sources */,
|
85F89F3F320F4FADCFFFE68B /* ServerSelectionScreenViewModel.swift in Sources */,
|
||||||
0C47AE2CA7929CB3B0E2D793 /* ServerSelectionScreenViewModelProtocol.swift in Sources */,
|
0C47AE2CA7929CB3B0E2D793 /* ServerSelectionScreenViewModelProtocol.swift in Sources */,
|
||||||
BD782053BE4C3D2F0BDE5699 /* ServiceLocator.swift in Sources */,
|
BD782053BE4C3D2F0BDE5699 /* ServiceLocator.swift in Sources */,
|
||||||
|
914BDF61447C723F104BCE33 /* SessionDirectories.swift in Sources */,
|
||||||
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */,
|
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */,
|
||||||
AE1A73B24D63DA3D63DC4EE3 /* SessionVerificationControllerProxyMock.swift in Sources */,
|
AE1A73B24D63DA3D63DC4EE3 /* SessionVerificationControllerProxyMock.swift in Sources */,
|
||||||
94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */,
|
94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */,
|
||||||
@ -7665,7 +7679,7 @@
|
|||||||
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = exactVersion;
|
kind = exactVersion;
|
||||||
version = 1.0.40;
|
version = 1.0.42;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
||||||
|
@ -149,8 +149,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "5d9f1865a71badfe6d9f7c3232b6cf23b12f8add",
|
"revision" : "ccae0615642728bbadcd051e4851d96ab298bab2",
|
||||||
"version" : "1.0.40"
|
"version" : "1.0.42"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -257,6 +257,7 @@
|
|||||||
"error_some_messages_have_not_been_sent" = "Some messages have not been sent";
|
"error_some_messages_have_not_been_sent" = "Some messages have not been sent";
|
||||||
"error_unknown" = "Sorry, an error occurred";
|
"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_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_sent_in_clear" = "Not encrypted.";
|
||||||
"event_shield_reason_unknown_device" = "Encrypted by an unknown or deleted device.";
|
"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.";
|
"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") }
|
internal static var errorUnknown: String { return L10n.tr("Localizable", "error_unknown") }
|
||||||
/// The authenticity of this encrypted message can't be guaranteed on this device.
|
/// 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") }
|
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.
|
/// Not encrypted.
|
||||||
internal static var eventShieldReasonSentInClear: String { return L10n.tr("Localizable", "event_shield_reason_sent_in_clear") }
|
internal static var eventShieldReasonSentInClear: String { return L10n.tr("Localizable", "event_shield_reason_sent_in_clear") }
|
||||||
/// Encrypted by an unknown or deleted device.
|
/// Encrypted by an unknown or deleted device.
|
||||||
|
@ -4804,17 +4804,17 @@ open class ClientBuilderSDKMock: MatrixRustSDK.ClientBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - sessionPath
|
//MARK: - sessionPaths
|
||||||
|
|
||||||
var sessionPathPathUnderlyingCallsCount = 0
|
var sessionPathsDataPathCachePathUnderlyingCallsCount = 0
|
||||||
open var sessionPathPathCallsCount: Int {
|
open var sessionPathsDataPathCachePathCallsCount: Int {
|
||||||
get {
|
get {
|
||||||
if Thread.isMainThread {
|
if Thread.isMainThread {
|
||||||
return sessionPathPathUnderlyingCallsCount
|
return sessionPathsDataPathCachePathUnderlyingCallsCount
|
||||||
} else {
|
} else {
|
||||||
var returnValue: Int? = nil
|
var returnValue: Int? = nil
|
||||||
DispatchQueue.main.sync {
|
DispatchQueue.main.sync {
|
||||||
returnValue = sessionPathPathUnderlyingCallsCount
|
returnValue = sessionPathsDataPathCachePathUnderlyingCallsCount
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnValue!
|
return returnValue!
|
||||||
@ -4822,29 +4822,29 @@ open class ClientBuilderSDKMock: MatrixRustSDK.ClientBuilder {
|
|||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
if Thread.isMainThread {
|
if Thread.isMainThread {
|
||||||
sessionPathPathUnderlyingCallsCount = newValue
|
sessionPathsDataPathCachePathUnderlyingCallsCount = newValue
|
||||||
} else {
|
} else {
|
||||||
DispatchQueue.main.sync {
|
DispatchQueue.main.sync {
|
||||||
sessionPathPathUnderlyingCallsCount = newValue
|
sessionPathsDataPathCachePathUnderlyingCallsCount = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
open var sessionPathPathCalled: Bool {
|
open var sessionPathsDataPathCachePathCalled: Bool {
|
||||||
return sessionPathPathCallsCount > 0
|
return sessionPathsDataPathCachePathCallsCount > 0
|
||||||
}
|
}
|
||||||
open var sessionPathPathReceivedPath: String?
|
open var sessionPathsDataPathCachePathReceivedArguments: (dataPath: String, cachePath: String?)?
|
||||||
open var sessionPathPathReceivedInvocations: [String] = []
|
open var sessionPathsDataPathCachePathReceivedInvocations: [(dataPath: String, cachePath: String?)] = []
|
||||||
|
|
||||||
var sessionPathPathUnderlyingReturnValue: ClientBuilder!
|
var sessionPathsDataPathCachePathUnderlyingReturnValue: ClientBuilder!
|
||||||
open var sessionPathPathReturnValue: ClientBuilder! {
|
open var sessionPathsDataPathCachePathReturnValue: ClientBuilder! {
|
||||||
get {
|
get {
|
||||||
if Thread.isMainThread {
|
if Thread.isMainThread {
|
||||||
return sessionPathPathUnderlyingReturnValue
|
return sessionPathsDataPathCachePathUnderlyingReturnValue
|
||||||
} else {
|
} else {
|
||||||
var returnValue: ClientBuilder? = nil
|
var returnValue: ClientBuilder? = nil
|
||||||
DispatchQueue.main.sync {
|
DispatchQueue.main.sync {
|
||||||
returnValue = sessionPathPathUnderlyingReturnValue
|
returnValue = sessionPathsDataPathCachePathUnderlyingReturnValue
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnValue!
|
return returnValue!
|
||||||
@ -4852,26 +4852,26 @@ open class ClientBuilderSDKMock: MatrixRustSDK.ClientBuilder {
|
|||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
if Thread.isMainThread {
|
if Thread.isMainThread {
|
||||||
sessionPathPathUnderlyingReturnValue = newValue
|
sessionPathsDataPathCachePathUnderlyingReturnValue = newValue
|
||||||
} else {
|
} else {
|
||||||
DispatchQueue.main.sync {
|
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 {
|
open override func sessionPaths(dataPath: String, cachePath: String?) -> ClientBuilder {
|
||||||
sessionPathPathCallsCount += 1
|
sessionPathsDataPathCachePathCallsCount += 1
|
||||||
sessionPathPathReceivedPath = path
|
sessionPathsDataPathCachePathReceivedArguments = (dataPath: dataPath, cachePath: cachePath)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.sessionPathPathReceivedInvocations.append(path)
|
self.sessionPathsDataPathCachePathReceivedInvocations.append((dataPath: dataPath, cachePath: cachePath))
|
||||||
}
|
}
|
||||||
if let sessionPathPathClosure = sessionPathPathClosure {
|
if let sessionPathsDataPathCachePathClosure = sessionPathsDataPathCachePathClosure {
|
||||||
return sessionPathPathClosure(path)
|
return sessionPathsDataPathCachePathClosure(dataPath, cachePath)
|
||||||
} else {
|
} else {
|
||||||
return sessionPathPathReturnValue
|
return sessionPathsDataPathCachePathReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +68,22 @@ extension URL: ExpressibleByStringLiteral {
|
|||||||
return url
|
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? {
|
var globalProxy: String? {
|
||||||
if let proxySettingsUnmanaged = CFNetworkCopySystemProxySettings() {
|
if let proxySettingsUnmanaged = CFNetworkCopySystemProxySettings() {
|
||||||
let proxySettings = proxySettingsUnmanaged.takeRetainedValue()
|
let proxySettings = proxySettingsUnmanaged.takeRetainedValue()
|
||||||
|
@ -559,7 +559,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
fatalError("Only events can have send info.")
|
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)
|
displayAlert(.sendingFailed)
|
||||||
} else if let authenticityMessage = eventTimelineItem.properties.encryptionAuthenticity?.message {
|
} else if let authenticityMessage = eventTimelineItem.properties.encryptionAuthenticity?.message {
|
||||||
displayAlert(.encryptionAuthenticity(authenticityMessage))
|
displayAlert(.encryptionAuthenticity(authenticityMessage))
|
||||||
|
@ -150,7 +150,7 @@ private extension TimelineItemSendInfo {
|
|||||||
itemID = timelineItem.id
|
itemID = timelineItem.id
|
||||||
localizedString = timelineItem.localizedSendInfo
|
localizedString = timelineItem.localizedSendInfo
|
||||||
|
|
||||||
status = if adjustedDeliveryStatus == .sendingFailed {
|
status = if case .sendingFailed = adjustedDeliveryStatus {
|
||||||
.sendingFailed
|
.sendingFailed
|
||||||
} else if let authenticity = timelineItem.properties.encryptionAuthenticity {
|
} else if let authenticity = timelineItem.properties.encryptionAuthenticity {
|
||||||
.encryptionAuthenticity(authenticity)
|
.encryptionAuthenticity(authenticity)
|
||||||
|
@ -35,11 +35,13 @@ struct TimelineStyler<Content: View>: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
mainContent
|
mainContent
|
||||||
.onChange(of: timelineItem.properties.deliveryStatus) { newStatus in
|
.onChange(of: timelineItem.properties.deliveryStatus) { newStatus in
|
||||||
if newStatus == .sendingFailed {
|
if case .sendingFailed = newStatus {
|
||||||
guard task == nil else {
|
guard task == nil else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
task = Task {
|
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))
|
try? await Task.sleep(for: .milliseconds(700))
|
||||||
if !Task.isCancelled {
|
if !Task.isCancelled {
|
||||||
adjustedDeliveryStatus = newStatus
|
adjustedDeliveryStatus = newStatus
|
||||||
@ -101,7 +103,7 @@ struct TimelineItemStyler_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
static let failed: TextRoomTimelineItem = {
|
static let failed: TextRoomTimelineItem = {
|
||||||
var result = base
|
var result = base
|
||||||
result.properties.deliveryStatus = .sendingFailed
|
result.properties.deliveryStatus = .sendingFailed(.unknown)
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import MatrixRustSDK
|
|||||||
|
|
||||||
class AuthenticationService: AuthenticationServiceProtocol {
|
class AuthenticationService: AuthenticationServiceProtocol {
|
||||||
private var client: Client?
|
private var client: Client?
|
||||||
private var sessionDirectory: URL
|
private var sessionDirectories: SessionDirectories
|
||||||
private let passphrase: String
|
private let passphrase: String
|
||||||
|
|
||||||
private let userSessionStore: UserSessionStoreProtocol
|
private let userSessionStore: UserSessionStoreProtocol
|
||||||
@ -31,7 +31,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
|||||||
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { homeserverSubject.asCurrentValuePublisher() }
|
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { homeserverSubject.asCurrentValuePublisher() }
|
||||||
|
|
||||||
init(userSessionStore: UserSessionStoreProtocol, encryptionKeyProvider: EncryptionKeyProviderProtocol, appSettings: AppSettings, appHooks: AppHooks) {
|
init(userSessionStore: UserSessionStoreProtocol, encryptionKeyProvider: EncryptionKeyProviderProtocol, appSettings: AppSettings, appHooks: AppHooks) {
|
||||||
sessionDirectory = .sessionsBaseDirectory.appending(component: UUID().uuidString)
|
sessionDirectories = .init()
|
||||||
passphrase = encryptionKeyProvider.generateKey().base64EncodedString()
|
passphrase = encryptionKeyProvider.generateKey().base64EncodedString()
|
||||||
self.userSessionStore = userSessionStore
|
self.userSessionStore = userSessionStore
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
@ -149,20 +149,24 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
|||||||
slidingSyncProxy: appSettings.slidingSyncProxyURL,
|
slidingSyncProxy: appSettings.slidingSyncProxyURL,
|
||||||
sessionDelegate: userSessionStore.clientSessionDelegate,
|
sessionDelegate: userSessionStore.clientSessionDelegate,
|
||||||
appHooks: appHooks)
|
appHooks: appHooks)
|
||||||
.sessionPath(path: sessionDirectory.path(percentEncoded: false))
|
.sessionPaths(dataPath: sessionDirectories.dataPath,
|
||||||
|
cachePath: sessionDirectories.cachePath)
|
||||||
.passphrase(passphrase: passphrase)
|
.passphrase(passphrase: passphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func rotateSessionDirectory() {
|
private func rotateSessionDirectory() {
|
||||||
if FileManager.default.directoryExists(at: sessionDirectory) {
|
if FileManager.default.directoryExists(at: sessionDirectories.dataDirectory) {
|
||||||
try? FileManager.default.removeItem(at: sessionDirectory)
|
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> {
|
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):
|
case .success(let clientProxy):
|
||||||
return .success(clientProxy)
|
return .success(clientProxy)
|
||||||
case .failure:
|
case .failure:
|
||||||
|
@ -120,6 +120,7 @@ class KeychainController: KeychainControllerProtocol {
|
|||||||
}
|
}
|
||||||
let restorationToken = RestorationToken(session: session,
|
let restorationToken = RestorationToken(session: session,
|
||||||
sessionDirectory: oldToken.sessionDirectory,
|
sessionDirectory: oldToken.sessionDirectory,
|
||||||
|
cacheDirectory: oldToken.cacheDirectory,
|
||||||
passphrase: oldToken.passphrase,
|
passphrase: oldToken.passphrase,
|
||||||
pusherNotificationClientIdentifier: oldToken.pusherNotificationClientIdentifier)
|
pusherNotificationClientIdentifier: oldToken.pusherNotificationClientIdentifier)
|
||||||
setRestorationToken(restorationToken, forUsername: session.userId)
|
setRestorationToken(restorationToken, forUsername: session.userId)
|
||||||
|
@ -20,7 +20,7 @@ import Foundation
|
|||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
final class QRCodeLoginService: QRCodeLoginServiceProtocol {
|
final class QRCodeLoginService: QRCodeLoginServiceProtocol {
|
||||||
private var sessionDirectory: URL
|
private var sessionDirectories: SessionDirectories
|
||||||
private let passphrase: String
|
private let passphrase: String
|
||||||
|
|
||||||
private let userSessionStore: UserSessionStoreProtocol
|
private let userSessionStore: UserSessionStoreProtocol
|
||||||
@ -36,7 +36,7 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
|
|||||||
userSessionStore: UserSessionStoreProtocol,
|
userSessionStore: UserSessionStoreProtocol,
|
||||||
appSettings: AppSettings,
|
appSettings: AppSettings,
|
||||||
appHooks: AppHooks) {
|
appHooks: AppHooks) {
|
||||||
sessionDirectory = .sessionsBaseDirectory.appending(component: UUID().uuidString)
|
sessionDirectories = .init()
|
||||||
passphrase = encryptionKeyProvider.generateKey().base64EncodedString()
|
passphrase = encryptionKeyProvider.generateKey().base64EncodedString()
|
||||||
self.userSessionStore = userSessionStore
|
self.userSessionStore = userSessionStore
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
@ -83,20 +83,24 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
|
|||||||
slidingSyncProxy: appSettings.slidingSyncProxyURL,
|
slidingSyncProxy: appSettings.slidingSyncProxyURL,
|
||||||
sessionDelegate: userSessionStore.clientSessionDelegate,
|
sessionDelegate: userSessionStore.clientSessionDelegate,
|
||||||
appHooks: appHooks)
|
appHooks: appHooks)
|
||||||
.sessionPath(path: sessionDirectory.path(percentEncoded: false))
|
.sessionPaths(dataPath: sessionDirectories.dataPath,
|
||||||
|
cachePath: sessionDirectories.cachePath)
|
||||||
.passphrase(passphrase: passphrase)
|
.passphrase(passphrase: passphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func rotateSessionDirectory() {
|
private func rotateSessionDirectory() {
|
||||||
if FileManager.default.directoryExists(at: sessionDirectory) {
|
if FileManager.default.directoryExists(at: sessionDirectories.dataDirectory) {
|
||||||
try? FileManager.default.removeItem(at: sessionDirectory)
|
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> {
|
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):
|
case .success(let session):
|
||||||
return .success(session)
|
return .success(session)
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
|
@ -28,6 +28,7 @@ enum EncryptionAuthenticity: Hashable {
|
|||||||
case unknownDevice(color: Color)
|
case unknownDevice(color: Color)
|
||||||
case unsignedDevice(color: Color)
|
case unsignedDevice(color: Color)
|
||||||
case unverifiedIdentity(color: Color)
|
case unverifiedIdentity(color: Color)
|
||||||
|
case previouslyVerified(color: Color)
|
||||||
case sentInClear(color: Color)
|
case sentInClear(color: Color)
|
||||||
|
|
||||||
var message: String {
|
var message: String {
|
||||||
@ -40,6 +41,8 @@ enum EncryptionAuthenticity: Hashable {
|
|||||||
L10n.eventShieldReasonUnsignedDevice
|
L10n.eventShieldReasonUnsignedDevice
|
||||||
case .unverifiedIdentity:
|
case .unverifiedIdentity:
|
||||||
L10n.eventShieldReasonUnverifiedIdentity
|
L10n.eventShieldReasonUnverifiedIdentity
|
||||||
|
case .previouslyVerified:
|
||||||
|
L10n.eventShieldReasonPreviouslyVerified
|
||||||
case .sentInClear:
|
case .sentInClear:
|
||||||
L10n.eventShieldReasonSentInClear
|
L10n.eventShieldReasonSentInClear
|
||||||
}
|
}
|
||||||
@ -51,6 +54,7 @@ enum EncryptionAuthenticity: Hashable {
|
|||||||
.unknownDevice(let color),
|
.unknownDevice(let color),
|
||||||
.unsignedDevice(let color),
|
.unsignedDevice(let color),
|
||||||
.unverifiedIdentity(let color),
|
.unverifiedIdentity(let color),
|
||||||
|
.previouslyVerified(let color),
|
||||||
.sentInClear(let color):
|
.sentInClear(let color):
|
||||||
color
|
color
|
||||||
}
|
}
|
||||||
@ -59,7 +63,7 @@ enum EncryptionAuthenticity: Hashable {
|
|||||||
var icon: KeyPath<CompoundIcons, Image> {
|
var icon: KeyPath<CompoundIcons, Image> {
|
||||||
switch self {
|
switch self {
|
||||||
case .notGuaranteed: \.info
|
case .notGuaranteed: \.info
|
||||||
case .unknownDevice, .unsignedDevice, .unverifiedIdentity: \.helpSolid
|
case .unknownDevice, .unsignedDevice, .unverifiedIdentity, .previouslyVerified: \.helpSolid
|
||||||
case .sentInClear: \.lockOff
|
case .sentInClear: \.lockOff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,6 +91,8 @@ extension EncryptionAuthenticity {
|
|||||||
self = .unsignedDevice(color: color)
|
self = .unsignedDevice(color: color)
|
||||||
case .unverifiedIdentity:
|
case .unverifiedIdentity:
|
||||||
self = .unverifiedIdentity(color: color)
|
self = .unverifiedIdentity(color: color)
|
||||||
|
case .previouslyVerified:
|
||||||
|
self = .previouslyVerified(color: color)
|
||||||
case .sentInClear:
|
case .sentInClear:
|
||||||
self = .sentInClear(color: color)
|
self = .sentInClear(color: color)
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,20 @@ enum TimelineItemProxy {
|
|||||||
enum TimelineItemDeliveryStatus: Hashable {
|
enum TimelineItemDeliveryStatus: Hashable {
|
||||||
case sending
|
case sending
|
||||||
case sent
|
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.
|
/// A light wrapper around event timeline items returned from Rust.
|
||||||
@ -88,11 +101,15 @@ class EventTimelineItemProxy {
|
|||||||
|
|
||||||
switch localSendState {
|
switch localSendState {
|
||||||
case .sendingFailed(_, let isRecoverable):
|
case .sendingFailed(_, let isRecoverable):
|
||||||
return isRecoverable ? .sending : .sendingFailed
|
return isRecoverable ? .sending : .sendingFailed(.unknown)
|
||||||
case .notSentYet:
|
case .notSentYet:
|
||||||
return .sending
|
return .sending
|
||||||
case .sent:
|
case .sent:
|
||||||
return .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 {
|
var hasFailedToSend: Bool {
|
||||||
properties.deliveryStatus == .sendingFailed
|
properties.deliveryStatus?.isSendingFailed == true
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasFailedDecryption: Bool {
|
var hasFailedDecryption: Bool {
|
||||||
|
@ -20,6 +20,7 @@ import MatrixRustSDK
|
|||||||
struct RestorationToken: Equatable {
|
struct RestorationToken: Equatable {
|
||||||
let session: MatrixRustSDK.Session
|
let session: MatrixRustSDK.Session
|
||||||
let sessionDirectory: URL
|
let sessionDirectory: URL
|
||||||
|
let cacheDirectory: URL
|
||||||
let passphrase: String?
|
let passphrase: String?
|
||||||
let pusherNotificationClientIdentifier: String?
|
let pusherNotificationClientIdentifier: String?
|
||||||
}
|
}
|
||||||
@ -29,10 +30,22 @@ extension RestorationToken: Codable {
|
|||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
let session = try container.decode(MatrixRustSDK.Session.self, forKey: .session)
|
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,
|
self = try .init(session: session,
|
||||||
sessionDirectory: sessionDirectory ?? .legacySessionDirectory(for: session.userId),
|
sessionDirectory: sessionDirectories.dataDirectory,
|
||||||
|
cacheDirectory: sessionDirectories.cacheDirectory,
|
||||||
passphrase: container.decodeIfPresent(String.self, forKey: .passphrase),
|
passphrase: container.decodeIfPresent(String.self, forKey: .passphrase),
|
||||||
pusherNotificationClientIdentifier: container.decodeIfPresent(String.self, forKey: .pusherNotificationClientIdentifier))
|
pusherNotificationClientIdentifier: container.decodeIfPresent(String.self, forKey: .pusherNotificationClientIdentifier))
|
||||||
}
|
}
|
||||||
@ -66,18 +79,3 @@ extension MatrixRustSDK.Session: Codable {
|
|||||||
case accessToken, refreshToken, userId, deviceId, homeserverUrl, oidcData, slidingSyncProxy
|
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
|
// On any restoration failure reset the token and restart
|
||||||
keychainController.removeRestorationTokenForUsername(credentials.userID)
|
keychainController.removeRestorationTokenForUsername(credentials.userID)
|
||||||
deleteSessionDirectory(for: credentials)
|
deleteSessionDirectories(for: credentials)
|
||||||
|
|
||||||
return .failure(error)
|
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 {
|
do {
|
||||||
let session = try client.session()
|
let session = try client.session()
|
||||||
let userID = try client.userId()
|
let userID = try client.userId()
|
||||||
let clientProxy = await setupProxyForClient(client)
|
let clientProxy = await setupProxyForClient(client)
|
||||||
|
|
||||||
keychainController.setRestorationToken(RestorationToken(session: session,
|
keychainController.setRestorationToken(RestorationToken(session: session,
|
||||||
sessionDirectory: sessionDirectory,
|
sessionDirectory: sessionDirectories.dataDirectory,
|
||||||
|
cacheDirectory: sessionDirectories.cacheDirectory,
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier),
|
pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier),
|
||||||
forUsername: userID)
|
forUsername: userID)
|
||||||
@ -95,7 +96,7 @@ class UserSessionStore: UserSessionStoreProtocol {
|
|||||||
keychainController.removeRestorationTokenForUsername(userID)
|
keychainController.removeRestorationTokenForUsername(userID)
|
||||||
|
|
||||||
if let credentials {
|
if let credentials {
|
||||||
deleteSessionDirectory(for: credentials)
|
deleteSessionDirectories(for: credentials)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +134,8 @@ class UserSessionStore: UserSessionStoreProtocol {
|
|||||||
slidingSync: appSettings.simplifiedSlidingSyncEnabled ? .simplified : .restored,
|
slidingSync: appSettings.simplifiedSlidingSyncEnabled ? .simplified : .restored,
|
||||||
sessionDelegate: keychainController,
|
sessionDelegate: keychainController,
|
||||||
appHooks: appHooks)
|
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)
|
.username(username: credentials.userID)
|
||||||
.homeserverUrl(url: homeserverURL)
|
.homeserverUrl(url: homeserverURL)
|
||||||
.passphrase(passphrase: credentials.restorationToken.passphrase)
|
.passphrase(passphrase: credentials.restorationToken.passphrase)
|
||||||
@ -156,22 +158,36 @@ class UserSessionStore: UserSessionStoreProtocol {
|
|||||||
appSettings: appSettings)
|
appSettings: appSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func deleteSessionDirectory(for credentials: KeychainCredentials) {
|
private func deleteSessionDirectories(for credentials: KeychainCredentials) {
|
||||||
do {
|
do {
|
||||||
try FileManager.default.removeItem(at: credentials.restorationToken.sessionDirectory)
|
try FileManager.default.removeItem(at: credentials.restorationToken.sessionDirectory)
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.failure("Failed deleting the session data: \(error)")
|
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) {
|
private func deleteCaches(for credentials: KeychainCredentials) {
|
||||||
do {
|
do {
|
||||||
let sessionDirectoryContents = try FileManager.default.contentsOfDirectory(at: credentials.restorationToken.sessionDirectory, includingPropertiesForKeys: nil)
|
try deleteContentsOfDirectory(at: credentials.restorationToken.sessionDirectory)
|
||||||
|
} catch {
|
||||||
|
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) {
|
for url in sessionDirectoryContents where url.path.contains(matrixSDKStateKey) {
|
||||||
try FileManager.default.removeItem(at: url)
|
try FileManager.default.removeItem(at: url)
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
MXLog.failure("Failed clearing caches: \(error)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ protocol UserSessionStoreProtocol {
|
|||||||
func restoreUserSession() async -> Result<UserSessionProtocol, UserSessionStoreError>
|
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.
|
/// 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.
|
/// Logs out of the specified session.
|
||||||
func logout(userSession: UserSessionProtocol)
|
func logout(userSession: UserSessionProtocol)
|
||||||
|
@ -39,7 +39,8 @@ final class NSEUserSession {
|
|||||||
slidingSync: simplifiedSlidingSyncEnabled ? .simplified : .restored,
|
slidingSync: simplifiedSlidingSyncEnabled ? .simplified : .restored,
|
||||||
sessionDelegate: clientSessionDelegate,
|
sessionDelegate: clientSessionDelegate,
|
||||||
appHooks: appHooks)
|
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)
|
.username(username: credentials.userID)
|
||||||
.homeserverUrl(url: homeserverURL)
|
.homeserverUrl(url: homeserverURL)
|
||||||
.passphrase(passphrase: credentials.restorationToken.passphrase)
|
.passphrase(passphrase: credentials.restorationToken.passphrase)
|
||||||
|
@ -111,3 +111,4 @@ targets:
|
|||||||
- path: ../../ElementX/Sources/Services/Notification/Proxy
|
- path: ../../ElementX/Sources/Services/Notification/Proxy
|
||||||
- path: ../../ElementX/Sources/Services/Room/RoomSummary/RoomMessageEventStringBuilder.swift
|
- path: ../../ElementX/Sources/Services/Room/RoomSummary/RoomMessageEventStringBuilder.swift
|
||||||
- path: ../../ElementX/Sources/Services/UserSession/RestorationToken.swift
|
- path: ../../ElementX/Sources/Services/UserSession/RestorationToken.swift
|
||||||
|
- path: ../../ElementX/Sources/Services/UserSession/SessionDirectories.swift
|
||||||
|
@ -41,6 +41,7 @@ class KeychainControllerTests: XCTestCase {
|
|||||||
oidcData: "oidcData",
|
oidcData: "oidcData",
|
||||||
slidingSyncProxy: "https://my.sync.proxy"),
|
slidingSyncProxy: "https://my.sync.proxy"),
|
||||||
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||||
|
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||||
passphrase: "passphrase",
|
passphrase: "passphrase",
|
||||||
pusherNotificationClientIdentifier: "pusherClientID")
|
pusherNotificationClientIdentifier: "pusherClientID")
|
||||||
keychain.setRestorationToken(restorationToken, forUsername: username)
|
keychain.setRestorationToken(restorationToken, forUsername: username)
|
||||||
@ -60,6 +61,7 @@ class KeychainControllerTests: XCTestCase {
|
|||||||
oidcData: "oidcData",
|
oidcData: "oidcData",
|
||||||
slidingSyncProxy: "https://my.sync.proxy"),
|
slidingSyncProxy: "https://my.sync.proxy"),
|
||||||
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||||
|
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||||
passphrase: "passphrase",
|
passphrase: "passphrase",
|
||||||
pusherNotificationClientIdentifier: "pusherClientID")
|
pusherNotificationClientIdentifier: "pusherClientID")
|
||||||
keychain.setRestorationToken(restorationToken, forUsername: username)
|
keychain.setRestorationToken(restorationToken, forUsername: username)
|
||||||
@ -85,6 +87,7 @@ class KeychainControllerTests: XCTestCase {
|
|||||||
oidcData: "oidcData",
|
oidcData: "oidcData",
|
||||||
slidingSyncProxy: "https://my.sync.proxy"),
|
slidingSyncProxy: "https://my.sync.proxy"),
|
||||||
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||||
|
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||||
passphrase: "passphrase",
|
passphrase: "passphrase",
|
||||||
pusherNotificationClientIdentifier: "pusherClientID")
|
pusherNotificationClientIdentifier: "pusherClientID")
|
||||||
keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com")
|
keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com")
|
||||||
@ -109,6 +112,7 @@ class KeychainControllerTests: XCTestCase {
|
|||||||
oidcData: "oidcData",
|
oidcData: "oidcData",
|
||||||
slidingSyncProxy: "https://my.sync.proxy"),
|
slidingSyncProxy: "https://my.sync.proxy"),
|
||||||
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||||
|
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||||
passphrase: "passphrase",
|
passphrase: "passphrase",
|
||||||
pusherNotificationClientIdentifier: "pusherClientID")
|
pusherNotificationClientIdentifier: "pusherClientID")
|
||||||
keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com")
|
keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com")
|
||||||
@ -141,6 +145,7 @@ class KeychainControllerTests: XCTestCase {
|
|||||||
oidcData: "oidcData",
|
oidcData: "oidcData",
|
||||||
slidingSyncProxy: nil),
|
slidingSyncProxy: nil),
|
||||||
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||||
|
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
|
||||||
passphrase: "passphrase",
|
passphrase: "passphrase",
|
||||||
pusherNotificationClientIdentifier: "pusherClientID")
|
pusherNotificationClientIdentifier: "pusherClientID")
|
||||||
keychain.setRestorationToken(restorationToken, forUsername: username)
|
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),
|
sender: .init(id: UUID().uuidString),
|
||||||
content: .init(body: "Test"))
|
content: .init(body: "Test"))
|
||||||
timelineItem.properties.isEdited = true
|
timelineItem.properties.isEdited = true
|
||||||
timelineItem.properties.deliveryStatus = .sendingFailed
|
timelineItem.properties.deliveryStatus = .sendingFailed(.unknown)
|
||||||
let editedCount = L10n.commonEditedSuffix.count
|
let editedCount = L10n.commonEditedSuffix.count
|
||||||
XCTAssertEqual(timelineItem.additionalWhitespaces(), timestamp.count + editedCount + 5)
|
XCTAssertEqual(timelineItem.additionalWhitespaces(), timestamp.count + editedCount + 5)
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ packages:
|
|||||||
# Element/Matrix dependencies
|
# Element/Matrix dependencies
|
||||||
MatrixRustSDK:
|
MatrixRustSDK:
|
||||||
url: https://github.com/element-hq/matrix-rust-components-swift
|
url: https://github.com/element-hq/matrix-rust-components-swift
|
||||||
exactVersion: 1.0.40
|
exactVersion: 1.0.42
|
||||||
# path: ../matrix-rust-sdk
|
# path: ../matrix-rust-sdk
|
||||||
Compound:
|
Compound:
|
||||||
url: https://github.com/element-hq/compound-ios
|
url: https://github.com/element-hq/compound-ios
|
||||||
|
Loading…
x
Reference in New Issue
Block a user