From 65e4d7f2ba30318b58a3f9260ab86e6cc88d7849 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 7 Aug 2025 13:13:06 +1000 Subject: [PATCH 01/66] Renamed SessionSnodeKit to SessionNetworkingKit --- Session.xcodeproj/project.pbxproj | 146 +++++++++--------- .../xcshareddata/xcschemes/Session.xcscheme | 4 +- ...xcscheme => SessionNetworkingKit.xcscheme} | 8 +- .../Calls/Call Management/SessionCall.swift | 2 +- Session/Calls/WebRTC/WebRTCSession.swift | 2 +- .../Closed Groups/EditGroupViewModel.swift | 2 +- .../ContextMenuVC+ActionView.swift | 2 +- .../ConversationVC+Interaction.swift | 2 +- .../Conversations/ConversationViewModel.swift | 2 +- .../DisappearingMessageTimerView.swift | 2 +- ...isappearingMessagesSettingsViewModel.swift | 2 +- .../ThreadNotificationSettingsViewModel.swift | 2 +- .../Settings/ThreadSettingsViewModel.swift | 2 +- .../New Conversation/NewMessageScreen.swift | 2 +- .../GIFs/GifPickerCell.swift | 2 +- .../GIFs/GiphyAPI.swift | 2 +- .../MediaPageViewController.swift | 2 +- .../MessageInfoScreen.swift | 2 +- .../PhotoCapture.swift | 2 +- Session/Meta/AppDelegate.swift | 2 +- Session/Meta/Session+SNUIKit.swift | 2 +- .../NotificationActionHandler.swift | 2 +- Session/Notifications/SyncPushTokensJob.swift | 2 +- Session/Onboarding/LoadingScreen.swift | 2 +- Session/Onboarding/Onboarding.swift | 2 +- Session/Onboarding/PNModeScreen.swift | 2 +- Session/Path/PathStatusView.swift | 2 +- Session/Path/PathVC.swift | 2 +- .../Settings/DeveloperSettingsViewModel.swift | 2 +- Session/Settings/NukeDataModal.swift | 2 +- .../SessionNetworkScreen+ViewModel.swift | 2 +- Session/Utilities/BackgroundPoller.swift | 2 +- Session/Utilities/IP2Country.swift | 2 +- .../Crypto/Crypto+SessionMessagingKit.swift | 2 +- .../Migrations/_002_SetupStandardJobs.swift | 2 +- .../Migrations/_004_RemoveLegacyYDB.swift | 2 +- .../_022_GroupsRebuildChanges.swift | 2 +- .../Database/Models/Attachment.swift | 2 +- .../Database/Models/ClosedGroup.swift | 2 +- .../Database/Models/ConfigDump.swift | 2 +- .../DisappearingMessageConfiguration.swift | 2 +- .../Database/Models/Interaction.swift | 2 +- .../Database/Models/LinkPreview.swift | 2 +- .../Database/Models/SessionThread.swift | 2 +- .../Jobs/AttachmentDownloadJob.swift | 2 +- .../Jobs/AttachmentUploadJob.swift | 2 +- .../Jobs/CheckForAppUpdatesJob.swift | 2 +- .../Jobs/ConfigMessageReceiveJob.swift | 2 +- .../Jobs/ConfigurationSyncJob.swift | 2 +- .../Jobs/DisappearingMessagesJob.swift | 2 +- .../Jobs/DisplayPictureDownloadJob.swift | 2 +- .../Jobs/ExpirationUpdateJob.swift | 2 +- .../Jobs/GarbageCollectionJob.swift | 2 +- .../Jobs/GetExpirationJob.swift | 2 +- .../Jobs/GroupInviteMemberJob.swift | 2 +- .../Jobs/GroupLeavingJob.swift | 2 +- .../Jobs/GroupPromoteMemberJob.swift | 2 +- SessionMessagingKit/Jobs/MessageSendJob.swift | 2 +- ...ProcessPendingGroupMemberRemovalsJob.swift | 2 +- .../Jobs/SendReadReceiptsJob.swift | 2 +- .../LibSession+GroupInfo.swift | 2 +- .../LibSession+GroupMembers.swift | 2 +- .../Config Handling/LibSession+Shared.swift | 2 +- .../LibSession+SharedGroup.swift | 2 +- .../LibSession+UserGroups.swift | 2 +- .../LibSession+SessionMessagingKit.swift | 2 +- .../LibSession/Types/Config.swift | 2 +- .../Messages/Message+Destination.swift | 2 +- .../Message+DisappearingMessages.swift | 2 +- .../Messages/Message+Origin.swift | 2 +- SessionMessagingKit/Messages/Message.swift | 2 +- .../Open Groups/Models/SOGSMessage.swift | 2 +- .../Open Groups/OpenGroupAPI.swift | 2 +- .../Open Groups/OpenGroupManager.swift | 2 +- .../Types/HTTPHeader+OpenGroup.swift | 2 +- .../Types/HTTPQueryParam+OpenGroup.swift | 2 +- .../Types/Request+OpenGroupAPI.swift | 2 +- .../Open Groups/Types/SOGSEndpoint.swift | 2 +- .../MessageReceiver+Calls.swift | 2 +- ...eReceiver+DataExtractionNotification.swift | 2 +- .../MessageReceiver+Groups.swift | 2 +- .../MessageReceiver+LegacyClosedGroups.swift | 2 +- .../MessageReceiver+LibSession.swift | 2 +- .../MessageReceiver+MessageRequests.swift | 2 +- .../MessageReceiver+UnsendRequests.swift | 2 +- .../MessageReceiver+VisibleMessages.swift | 2 +- .../MessageSender+Groups.swift | 2 +- .../Sending & Receiving/MessageReceiver.swift | 2 +- .../MessageSender+Convenience.swift | 2 +- .../Sending & Receiving/MessageSender.swift | 2 +- .../Models/LegacyUnsubscribeRequest.swift | 2 +- .../Models/NotificationMetadata.swift | 2 +- .../Models/SubscribeRequest.swift | 2 +- .../Models/UnsubscribeRequest.swift | 2 +- .../Notifications/PushNotificationAPI.swift | 2 +- .../Types/PushNotificationAPIEndpoint.swift | 2 +- .../Types/Request+PushNotificationAPI.swift | 2 +- .../Pollers/CommunityPoller.swift | 2 +- .../Pollers/CurrentUserPoller.swift | 2 +- .../Pollers/GroupPoller.swift | 2 +- .../Pollers/PollerType.swift | 2 +- .../Pollers/SwarmPoller.swift | 2 +- .../Typing Indicators/TypingIndicators.swift | 2 +- .../MessageViewModel+DeletionActions.swift | 2 +- .../Authentication+SessionMessagingKit.swift | 2 +- .../Utilities/DisplayPictureManager.swift | 2 +- .../Utilities/MessageWrapper.swift | 2 +- .../SNProtoEnvelope+Conversion.swift | 2 +- .../Jobs/DisplayPictureDownloadJobSpec.swift | 2 +- ...RetrieveDefaultOpenGroupRoomsJobSpec.swift | 2 +- .../LibSession/LibSessionGroupInfoSpec.swift | 6 +- .../LibSessionGroupMembersSpec.swift | 2 +- .../LibSession/LibSessionSpec.swift | 2 +- .../Open Groups/Models/SOGSMessageSpec.swift | 2 +- .../Open Groups/OpenGroupAPISpec.swift | 2 +- .../Open Groups/OpenGroupManagerSpec.swift | 2 +- .../Open Groups/Types/SOGSEndpointSpec.swift | 2 +- .../MessageReceiverGroupsSpec.swift | 4 +- .../MessageSenderGroupsSpec.swift | 2 +- .../MessageSenderSpec.swift | 2 +- .../Pollers/CommunityPollerSpec.swift | 2 +- .../_TestUtilities/MockPoller.swift | 2 +- .../_TestUtilities/MockSwarmPoller.swift | 2 +- .../Configuration.swift | 4 +- .../Crypto/Crypto+SessionNetworkingKit.swift | 0 .../_001_InitialSetupMigration.swift | 2 +- .../Migrations/_002_SetupStandardJobs.swift | 2 +- .../Migrations/_003_YDBToGRDBMigration.swift | 2 +- ...04_FlagMessageHashAsDeletedOrInvalid.swift | 2 +- ...ddSnodeReveivedMessageInfoPrimaryKey.swift | 2 +- .../Migrations/_006_DropSnodeCache.swift | 2 +- .../_007_SplitSnodeReceivedMessageInfo.swift | 2 +- .../_008_ResetUserConfigLastHashes.swift | 2 +- .../Models/SnodeReceivedMessageInfo.swift | 0 .../LibSession/LibSession+Networking.swift | 0 .../Meta/Info.plist | 0 .../Meta/SessionNetworkingKit.h | 4 + .../Models/AppVersionResponse.swift | 0 .../Models/DeleteAllBeforeRequest.swift | 0 .../Models/DeleteAllBeforeResponse.swift | 0 .../Models/DeleteAllMessagesRequest.swift | 0 .../Models/DeleteAllMessagesResponse.swift | 0 .../Models/DeleteMessagesRequest.swift | 0 .../Models/DeleteMessagesResponse.swift | 0 .../Models/FileUploadResponse.swift | 0 .../Models/GetExpiriesRequest.swift | 0 .../Models/GetExpiriesResponse.swift | 0 .../Models/GetMessagesRequest.swift | 0 .../Models/GetMessagesResponse.swift | 0 .../Models/GetNetworkTimestampResponse.swift | 0 .../Models/LegacyGetMessagesRequest.swift | 0 .../Models/LegacySendMessageRequest.swift | 0 .../Models/ONSResolveRequest.swift | 0 .../Models/ONSResolveResponse.swift | 0 .../Models/OxenDaemonRPCRequest.swift | 0 .../Models/RevokeSubaccountRequest.swift | 0 .../Models/RevokeSubaccountResponse.swift | 0 .../Models/SendMessageRequest.swift | 0 .../Models/SendMessageResponse.swift | 0 .../SnodeAuthenticatedRequestBody.swift | 0 .../Models/SnodeBatchRequest.swift | 0 .../Models/SnodeMessage.swift | 0 .../Models/SnodeReceivedMessage.swift | 0 .../Models/SnodeRecursiveResponse.swift | 0 .../Models/SnodeRequest.swift | 0 .../Models/SnodeResponse.swift | 0 .../Models/SnodeSwarmItem.swift | 0 .../Models/UnrevokeSubaccountRequest.swift | 0 .../Models/UnrevokeSubaccountResponse.swift | 0 .../Models/UpdateExpiryAllRequest.swift | 0 .../Models/UpdateExpiryAllResponse.swift | 0 .../Models/UpdateExpiryRequest.swift | 0 .../Models/UpdateExpiryResponse.swift | 0 .../Networking/SnodeAPI.swift | 0 .../HTTPHeader+SessionNetwork.swift | 0 .../SessionNetworkAPI+Database.swift | 0 .../SessionNetworkAPI+Models.swift | 0 .../SessionNetworkAPI+Network.swift | 4 +- .../SessionNetworkAPI/SessionNetworkAPI.swift | 0 .../SnodeAPI/Request+SnodeAPI.swift | 0 .../SnodeAPI/ResponseInfo+SnodeAPI.swift | 0 .../SnodeAPI/SnodeAPI.swift | 0 .../SnodeAPI/SnodeAPIEndpoint.swift | 0 .../SnodeAPI/SnodeAPIError.swift | 0 .../SnodeAPI/SnodeAPINamespace.swift | 0 .../Types/BatchRequest.swift | 0 .../Types/BatchResponse.swift | 0 .../Types/BencodeResponse.swift | 0 .../Types/ContentProxy.swift | 0 .../Types/Destination.swift | 0 .../Types/HTTPHeader.swift | 0 .../Types/HTTPMethod.swift | 0 .../Types/HTTPQueryParam.swift | 0 .../Types/IPv4.swift | 0 .../Types/JSON.swift | 0 .../Types/Network.swift | 0 .../Types/NetworkError.swift | 0 .../Types/PreparedRequest+Sending.swift | 0 .../Types/PreparedRequest.swift | 0 .../Types/ProxiedContentDownloader.swift | 0 .../Types/Request.swift | 0 .../Types/RequestCategory.swift | 0 .../Types/ResponseInfo.swift | 0 .../HTTPHeader+SessionNetwork.swift | 9 ++ .../SessionNetworkAPI+Database.swift | 32 ++++ .../SessionNetworkAPI+Models.swift | 75 +++++++++ .../SessionNetworkAPI+Network.swift | 107 +++++++++++++ .../SessionNetworkAPI/SessionNetworkAPI.swift | 131 ++++++++++++++++ .../Types/SwarmDrainBehaviour.swift | 0 .../Types/UpdatableTimestamp.swift | 0 .../Types/ValidatableResponse.swift | 0 .../Utilities/Data+Utilities.swift | 0 .../Utilities/Publisher+Utilities.swift | 0 .../Utilities/RetryWithDependencies.swift | 0 .../Utilities/String+Trimming.swift | 0 .../Utilities/URLResponse+Utilities.swift | 0 .../Models/FileUploadResponseSpec.swift | 2 +- .../Models/SnodeRequestSpec.swift | 2 +- .../SessionNetworkingKit.xctestplan | 2 +- .../Types/BatchRequestSpec.swift | 2 +- .../Types/BatchResponseSpec.swift | 2 +- .../Types/BencodeResponseSpec.swift | 2 +- .../Types/DestinationSpec.swift | 2 +- .../Types/HeaderSpec.swift | 2 +- .../Types/PreparedRequestSendingSpec.swift | 2 +- .../Types/PreparedRequestSpec.swift | 2 +- .../Types/RequestSpec.swift | 2 +- .../CommonSSKMockExtensions.swift | 2 +- .../_TestUtilities/MockNetwork.swift | 2 +- .../_TestUtilities/MockSnodeAPICache.swift | 2 +- .../NotificationServiceExtension.swift | 2 +- .../ShareNavController.swift | 2 +- SessionShareExtension/ThreadPickerVC.swift | 2 +- SessionSnodeKit/Meta/SessionSnodeKit.h | 4 - SessionSnodeKit/Utilities/Threading+SSK.swift | 6 - ...eadDisappearingMessagesViewModelSpec.swift | 4 +- ...eadNotificationSettingsViewModelSpec.swift | 4 +- .../ThreadSettingsViewModelSpec.swift | 4 +- SessionTests/Database/DatabaseSpec.swift | 6 +- SessionTests/Session.xctestplan | 4 +- .../NotificationContentViewModelSpec.swift | 4 +- .../Database/Types/TargetMigrations.swift | 2 +- SessionUtilitiesKit/General/Logging.swift | 139 +++++++++++++---- .../LibSession/LibSession.swift | 59 ++++++- SignalUtilitiesKit/Utilities/AppSetup.swift | 4 +- 245 files changed, 760 insertions(+), 300 deletions(-) rename Session.xcodeproj/xcshareddata/xcschemes/{SessionSnodeKit.xcscheme => SessionNetworkingKit.xcscheme} (91%) rename {SessionSnodeKit => SessionNetworkingKit}/Configuration.swift (89%) rename SessionSnodeKit/Crypto/Crypto+SessionSnodeKit.swift => SessionNetworkingKit/Crypto/Crypto+SessionNetworkingKit.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Database/Migrations/_001_InitialSetupMigration.swift (96%) rename {SessionSnodeKit => SessionNetworkingKit}/Database/Migrations/_002_SetupStandardJobs.swift (96%) rename {SessionSnodeKit => SessionNetworkingKit}/Database/Migrations/_003_YDBToGRDBMigration.swift (88%) rename {SessionSnodeKit => SessionNetworkingKit}/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift (93%) rename {SessionSnodeKit => SessionNetworkingKit}/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift (97%) rename {SessionSnodeKit => SessionNetworkingKit}/Database/Migrations/_006_DropSnodeCache.swift (94%) rename {SessionSnodeKit => SessionNetworkingKit}/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift (98%) rename {SessionSnodeKit => SessionNetworkingKit}/Database/Migrations/_008_ResetUserConfigLastHashes.swift (94%) rename {SessionSnodeKit => SessionNetworkingKit}/Database/Models/SnodeReceivedMessageInfo.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/LibSession/LibSession+Networking.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Meta/Info.plist (100%) create mode 100644 SessionNetworkingKit/Meta/SessionNetworkingKit.h rename {SessionSnodeKit => SessionNetworkingKit}/Models/AppVersionResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/DeleteAllBeforeRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/DeleteAllBeforeResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/DeleteAllMessagesRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/DeleteAllMessagesResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/DeleteMessagesRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/DeleteMessagesResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/FileUploadResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/GetExpiriesRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/GetExpiriesResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/GetMessagesRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/GetMessagesResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/GetNetworkTimestampResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/LegacyGetMessagesRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/LegacySendMessageRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/ONSResolveRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/ONSResolveResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/OxenDaemonRPCRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/RevokeSubaccountRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/RevokeSubaccountResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/SendMessageRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/SendMessageResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/SnodeAuthenticatedRequestBody.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/SnodeBatchRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/SnodeMessage.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/SnodeReceivedMessage.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/SnodeRecursiveResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/SnodeRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/SnodeResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/SnodeSwarmItem.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/UnrevokeSubaccountRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/UnrevokeSubaccountResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/UpdateExpiryAllRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/UpdateExpiryAllResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/UpdateExpiryRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Models/UpdateExpiryResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Networking/SnodeAPI.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/SessionNetworkAPI/SessionNetworkAPI+Database.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/SessionNetworkAPI/SessionNetworkAPI+Models.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/SessionNetworkAPI/SessionNetworkAPI+Network.swift (96%) rename {SessionSnodeKit => SessionNetworkingKit}/SessionNetworkAPI/SessionNetworkAPI.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/SnodeAPI/Request+SnodeAPI.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/SnodeAPI/ResponseInfo+SnodeAPI.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/SnodeAPI/SnodeAPI.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/SnodeAPI/SnodeAPIEndpoint.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/SnodeAPI/SnodeAPIError.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/SnodeAPI/SnodeAPINamespace.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/BatchRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/BatchResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/BencodeResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/ContentProxy.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/Destination.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/HTTPHeader.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/HTTPMethod.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/HTTPQueryParam.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/IPv4.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/JSON.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/Network.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/NetworkError.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/PreparedRequest+Sending.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/PreparedRequest.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/ProxiedContentDownloader.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/Request.swift (100%) create mode 100644 SessionNetworkingKit/Types/RequestCategory.swift rename {SessionSnodeKit => SessionNetworkingKit}/Types/ResponseInfo.swift (100%) create mode 100644 SessionNetworkingKit/Types/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift create mode 100644 SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Database.swift create mode 100644 SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Models.swift create mode 100644 SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Network.swift create mode 100644 SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI.swift rename {SessionSnodeKit => SessionNetworkingKit}/Types/SwarmDrainBehaviour.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/UpdatableTimestamp.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Types/ValidatableResponse.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Utilities/Data+Utilities.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Utilities/Publisher+Utilities.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Utilities/RetryWithDependencies.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Utilities/String+Trimming.swift (100%) rename {SessionSnodeKit => SessionNetworkingKit}/Utilities/URLResponse+Utilities.swift (100%) rename {SessionSnodeKitTests => SessionNetworkingKitTests}/Models/FileUploadResponseSpec.swift (96%) rename {SessionSnodeKitTests => SessionNetworkingKitTests}/Models/SnodeRequestSpec.swift (98%) rename SessionSnodeKitTests/SessionSnodeKit.xctestplan => SessionNetworkingKitTests/SessionNetworkingKit.xctestplan (90%) rename {SessionSnodeKitTests => SessionNetworkingKitTests}/Types/BatchRequestSpec.swift (99%) rename {SessionSnodeKitTests => SessionNetworkingKitTests}/Types/BatchResponseSpec.swift (99%) rename {SessionSnodeKitTests => SessionNetworkingKitTests}/Types/BencodeResponseSpec.swift (99%) rename {SessionSnodeKitTests => SessionNetworkingKitTests}/Types/DestinationSpec.swift (98%) rename {SessionSnodeKitTests => SessionNetworkingKitTests}/Types/HeaderSpec.swift (93%) rename {SessionSnodeKitTests => SessionNetworkingKitTests}/Types/PreparedRequestSendingSpec.swift (99%) rename {SessionSnodeKitTests => SessionNetworkingKitTests}/Types/PreparedRequestSpec.swift (99%) rename {SessionSnodeKitTests => SessionNetworkingKitTests}/Types/RequestSpec.swift (99%) rename {SessionSnodeKitTests => SessionNetworkingKitTests}/_TestUtilities/CommonSSKMockExtensions.swift (96%) rename {SessionSnodeKitTests => SessionNetworkingKitTests}/_TestUtilities/MockNetwork.swift (99%) rename {SessionSnodeKitTests => SessionNetworkingKitTests}/_TestUtilities/MockSnodeAPICache.swift (97%) delete mode 100644 SessionSnodeKit/Meta/SessionSnodeKit.h delete mode 100644 SessionSnodeKit/Utilities/Threading+SSK.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 5ed0e0cdfc..88ed056459 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -268,7 +268,7 @@ B8D0A25925E367AC00C1835E /* Notification+MessageReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D0A25825E367AC00C1835E /* Notification+MessageReceiver.swift */; }; B8D0A26925E4A2C200C1835E /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D0A26825E4A2C200C1835E /* Onboarding.swift */; }; B8D64FBB25BA78310029CFC0 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; }; - B8D64FBD25BA78310029CFC0 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; }; + B8D64FBD25BA78310029CFC0 /* SessionNetworkingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; }; B8D64FBE25BA78310029CFC0 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; B8D64FC725BA78520029CFC0 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; }; B8D64FCB25BA78A90029CFC0 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; @@ -286,7 +286,7 @@ C300A5F22554B09800555489 /* MessageSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5F12554B09800555489 /* MessageSender.swift */; }; C300A60D2554B31900555489 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5CE2553860700C340D1 /* Logging.swift */; }; C302093E25DCBF08001F572D /* MentionSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C302093D25DCBF07001F572D /* MentionSelectionView.swift */; }; - C32824D325C9F9790062D0A7 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; }; + C32824D325C9F9790062D0A7 /* SessionNetworkingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; }; C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328250E25CA06020062D0A7 /* VoiceMessageView.swift */; }; C328251F25CA3A900062D0A7 /* QuoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328251E25CA3A900062D0A7 /* QuoteView.swift */; }; C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328252F25CA55360062D0A7 /* ContextMenuWindow.swift */; }; @@ -314,7 +314,7 @@ C331FFFE2558FF3B00070591 /* FullConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82AA238F669C00BA5194 /* FullConversationCell.swift */; }; C33FD9B3255A548A00E217F9 /* SignalUtilitiesKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C33FD9C2255A54EF00E217F9 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; }; - C33FD9C4255A54EF00E217F9 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; }; + C33FD9C4255A54EF00E217F9 /* SessionNetworkingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; }; C33FD9C5255A54EF00E217F9 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; C33FDC58255A582000E217F9 /* ReverseDispatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */; }; C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */; }; @@ -329,7 +329,7 @@ C374EEF425DB31D40073A857 /* VoiceMessageRecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C374EEF325DB31D40073A857 /* VoiceMessageRecordingView.swift */; }; C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C379DCF3256735770002D4EB /* VisibleMessage+Attachment.swift */; }; C37F5414255BAFA7002AEA92 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; - C37F54DC255BB84A002AEA92 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; }; + C37F54DC255BB84A002AEA92 /* SessionNetworkingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; }; C38EF00C255B61CC007E1867 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; C38EF24D255B6D67007E1867 /* UIView+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF240255B6D67007E1867 /* UIView+OWS.swift */; }; C38EF24E255B6D67007E1867 /* Collection+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF241255B6D67007E1867 /* Collection+OWS.swift */; }; @@ -373,16 +373,15 @@ C3ADC66126426688005F1414 /* ShareNavController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ADC66026426688005F1414 /* ShareNavController.swift */; }; C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */; }; C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */; }; - C3C2A5A3255385C100C340D1 /* SessionSnodeKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A5A1255385C100C340D1 /* SessionSnodeKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C3C2A5A7255385C100C340D1 /* SessionSnodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C3C2A5A3255385C100C340D1 /* SessionNetworkingKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A5A1255385C100C340D1 /* SessionNetworkingKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A5A7255385C100C340D1 /* SessionNetworkingKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B9255385ED00C340D1 /* Configuration.swift */; }; - C3C2A5E02553860B00C340D1 /* Threading+SSK.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D42553860A00C340D1 /* Threading+SSK.swift */; }; C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */; }; C3C2A681255388CC00C340D1 /* SessionUtilitiesKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C3C2A6C62553896A00C340D1 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; C3C2A6F425539DE700C340D1 /* SessionMessagingKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A6F225539DE700C340D1 /* SessionMessagingKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; C3C2A6F825539DE700C340D1 /* SessionMessagingKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C3C2A70B25539E1E00C340D1 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; }; + C3C2A70B25539E1E00C340D1 /* SessionNetworkingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; }; C3C2A74425539EB700C340D1 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A74325539EB700C340D1 /* Message.swift */; }; C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A74C2553A39700C340D1 /* VisibleMessage.swift */; }; C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A75E2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift */; }; @@ -423,7 +422,7 @@ FD01504B2CA243CB005B08A1 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0150472CA243CB005B08A1 /* Mock.swift */; }; FD01504C2CA243CB005B08A1 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0150472CA243CB005B08A1 /* Mock.swift */; }; FD01504E2CA243E7005B08A1 /* TypeConversionUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD01504D2CA243E7005B08A1 /* TypeConversionUtilitiesSpec.swift */; }; - FD0150502CA24468005B08A1 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; platformFilter = ios; }; + FD0150502CA24468005B08A1 /* SessionNetworkingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; platformFilter = ios; }; FD0150522CA2446D005B08A1 /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = FD0150512CA2446D005B08A1 /* Quick */; }; FD0150542CA24471005B08A1 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = FD0150532CA24471005B08A1 /* Nimble */; }; FD0150582CA27DF3005B08A1 /* ScrollableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0150572CA27DEE005B08A1 /* ScrollableLabel.swift */; }; @@ -739,7 +738,7 @@ FD6673FD2D77F54600041530 /* ScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6673FC2D77F54400041530 /* ScreenLockViewController.swift */; }; FD6673FF2D77F9C100041530 /* ScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6673FE2D77F9BE00041530 /* ScreenLock.swift */; }; FD6674002D77F9FD00041530 /* ScreenLockWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090828B59411006098F6 /* ScreenLockWindow.swift */; }; - FD66CB2A2BF3449B00268FAB /* SessionSnodeKit.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = FD66CB272BF3449B00268FAB /* SessionSnodeKit.xctestplan */; }; + FD66CB2A2BF3449B00268FAB /* SessionNetworkingKit.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = FD66CB272BF3449B00268FAB /* SessionNetworkingKit.xctestplan */; }; FD6A38E92C2A630E00762359 /* CocoaLumberjackSwift in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A38E82C2A630E00762359 /* CocoaLumberjackSwift */; }; FD6A38EC2C2A63B500762359 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A38EB2C2A63B500762359 /* KeychainSwift */; }; FD6A38EF2C2A641200762359 /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A38EE2C2A641200762359 /* DifferenceKit */; }; @@ -1057,7 +1056,7 @@ FDE754DE2C9BAF8A002A2623 /* Crypto+SessionUtilitiesKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754D82C9BAF89002A2623 /* Crypto+SessionUtilitiesKit.swift */; }; FDE754DF2C9BAF8A002A2623 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754D92C9BAF89002A2623 /* KeyPair.swift */; }; FDE754E02C9BAF8A002A2623 /* Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754DA2C9BAF8A002A2623 /* Hex.swift */; }; - FDE754E32C9BAFF4002A2623 /* Crypto+SessionSnodeKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754E12C9BAFF4002A2623 /* Crypto+SessionSnodeKit.swift */; }; + FDE754E32C9BAFF4002A2623 /* Crypto+SessionNetworkingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754E12C9BAFF4002A2623 /* Crypto+SessionNetworkingKit.swift */; }; FDE754E52C9BB012002A2623 /* BezierPathView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754E42C9BB012002A2623 /* BezierPathView.swift */; }; FDE754E72C9BB051002A2623 /* OWSViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754E62C9BB051002A2623 /* OWSViewController.swift */; }; FDE754F02C9BB08B002A2623 /* Crypto+Attachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754EC2C9BB08B002A2623 /* Crypto+Attachments.swift */; }; @@ -1358,7 +1357,7 @@ files = ( C3C2A681255388CC00C340D1 /* SessionUtilitiesKit.framework in Embed Frameworks */, C33FD9B3255A548A00E217F9 /* SignalUtilitiesKit.framework in Embed Frameworks */, - C3C2A5A7255385C100C340D1 /* SessionSnodeKit.framework in Embed Frameworks */, + C3C2A5A7255385C100C340D1 /* SessionNetworkingKit.framework in Embed Frameworks */, C331FF232558F9D300070591 /* SessionUIKit.framework in Embed Frameworks */, C3C2A6F825539DE700C340D1 /* SessionMessagingKit.framework in Embed Frameworks */, ); @@ -1742,13 +1741,12 @@ C3AAFFF125AE99710089E6DD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C3ADC66026426688005F1414 /* ShareNavController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareNavController.swift; sourceTree = ""; }; C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FixedWidthInteger+BigEndian.swift"; sourceTree = ""; }; - C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionSnodeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C3C2A5A1255385C100C340D1 /* SessionSnodeKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionSnodeKit.h; sourceTree = ""; }; + C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionNetworkingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C3C2A5A1255385C100C340D1 /* SessionNetworkingKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionNetworkingKit.h; sourceTree = ""; }; C3C2A5A2255385C100C340D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C3C2A5B9255385ED00C340D1 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; C3C2A5CE2553860700C340D1 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; C3C2A5D22553860900C340D1 /* String+Trimming.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Trimming.swift"; sourceTree = ""; }; - C3C2A5D42553860A00C340D1 /* Threading+SSK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Threading+SSK.swift"; sourceTree = ""; }; C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Utilities.swift"; sourceTree = ""; }; C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -2044,7 +2042,7 @@ FD6531892AA025C500DFEEAA /* TestDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDependencies.swift; sourceTree = ""; }; FD6673FC2D77F54400041530 /* ScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockViewController.swift; sourceTree = ""; }; FD6673FE2D77F9BE00041530 /* ScreenLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLock.swift; sourceTree = ""; }; - FD66CB272BF3449B00268FAB /* SessionSnodeKit.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SessionSnodeKit.xctestplan; sourceTree = ""; }; + FD66CB272BF3449B00268FAB /* SessionNetworkingKit.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SessionNetworkingKit.xctestplan; sourceTree = ""; }; FD66CB2B2BF344C600268FAB /* SessionMessageKit.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = SessionMessageKit.xctestplan; sourceTree = ""; }; FD6A38F02C2A66B100762359 /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = ""; }; FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; @@ -2207,7 +2205,7 @@ FDB5DAE52A95D8B0002C8721 /* GroupUpdateDeleteMemberContentMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupUpdateDeleteMemberContentMessage.swift; sourceTree = ""; }; FDB5DAE72A95D96C002C8721 /* MessageReceiver+Groups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+Groups.swift"; sourceTree = ""; }; FDB5DAF22A96DD4F002C8721 /* PreparedRequest+Sending.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PreparedRequest+Sending.swift"; sourceTree = ""; }; - FDB5DAFA2A981C42002C8721 /* SessionSnodeKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionSnodeKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FDB5DAFA2A981C42002C8721 /* SessionNetworkingKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionNetworkingKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; FDB5DB052A981C67002C8721 /* PreparedRequestSendingSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreparedRequestSendingSpec.swift; sourceTree = ""; }; FDB6A87B2AD75B7F002D4F96 /* PhotosUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PhotosUI.framework; path = System/Library/Frameworks/PhotosUI.framework; sourceTree = SDKROOT; }; FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Utilities.swift"; sourceTree = ""; }; @@ -2324,7 +2322,7 @@ FDE754D82C9BAF89002A2623 /* Crypto+SessionUtilitiesKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Crypto+SessionUtilitiesKit.swift"; sourceTree = ""; }; FDE754D92C9BAF89002A2623 /* KeyPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPair.swift; sourceTree = ""; }; FDE754DA2C9BAF8A002A2623 /* Hex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Hex.swift; sourceTree = ""; }; - FDE754E12C9BAFF4002A2623 /* Crypto+SessionSnodeKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Crypto+SessionSnodeKit.swift"; sourceTree = ""; }; + FDE754E12C9BAFF4002A2623 /* Crypto+SessionNetworkingKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Crypto+SessionNetworkingKit.swift"; sourceTree = ""; }; FDE754E42C9BB012002A2623 /* BezierPathView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BezierPathView.swift; sourceTree = ""; }; FDE754E62C9BB051002A2623 /* OWSViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSViewController.swift; sourceTree = ""; }; FDE754EC2C9BB08B002A2623 /* Crypto+Attachments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Crypto+Attachments.swift"; sourceTree = ""; }; @@ -2436,7 +2434,7 @@ buildActionMask = 2147483647; files = ( FD860CC92D6ED2ED00BBE29C /* DifferenceKit in Frameworks */, - C32824D325C9F9790062D0A7 /* SessionSnodeKit.framework in Frameworks */, + C32824D325C9F9790062D0A7 /* SessionNetworkingKit.framework in Frameworks */, B8D64FC725BA78520029CFC0 /* SessionMessagingKit.framework in Frameworks */, C3D90A5C25773A25002C9DF5 /* SessionUtilitiesKit.framework in Frameworks */, C3402FE52559036600EA6424 /* SessionUIKit.framework in Frameworks */, @@ -2450,7 +2448,7 @@ files = ( B8D64FBB25BA78310029CFC0 /* SessionMessagingKit.framework in Frameworks */, FD8A5B182DBF47E9004C689B /* SessionUIKit.framework in Frameworks */, - B8D64FBD25BA78310029CFC0 /* SessionSnodeKit.framework in Frameworks */, + B8D64FBD25BA78310029CFC0 /* SessionNetworkingKit.framework in Frameworks */, B8D64FBE25BA78310029CFC0 /* SessionUtilitiesKit.framework in Frameworks */, C38EF00C255B61CC007E1867 /* SignalUtilitiesKit.framework in Frameworks */, ); @@ -2473,7 +2471,7 @@ FD6A39222C2AA91D00762359 /* NVActivityIndicatorView in Frameworks */, FD22866F2C38D42300BC06F7 /* DifferenceKit in Frameworks */, C33FD9C2255A54EF00E217F9 /* SessionMessagingKit.framework in Frameworks */, - C33FD9C4255A54EF00E217F9 /* SessionSnodeKit.framework in Frameworks */, + C33FD9C4255A54EF00E217F9 /* SessionNetworkingKit.framework in Frameworks */, C33FD9C5255A54EF00E217F9 /* SessionUtilitiesKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2508,7 +2506,7 @@ FD6673FA2D7021F800041530 /* SessionUtil in Frameworks */, FD2286732C38D43900BC06F7 /* DifferenceKit in Frameworks */, FDC4386C27B4E90300C60D73 /* SessionUtilitiesKit.framework in Frameworks */, - C3C2A70B25539E1E00C340D1 /* SessionSnodeKit.framework in Frameworks */, + C3C2A70B25539E1E00C340D1 /* SessionNetworkingKit.framework in Frameworks */, FD6A39132C2A946A00762359 /* SwiftProtobuf in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2522,7 +2520,7 @@ B8FF8DAE25C0D00F004D1F22 /* SessionMessagingKit.framework in Frameworks */, B8FF8DAF25C0D00F004D1F22 /* SessionUtilitiesKit.framework in Frameworks */, FDB6A87C2AD75B7F002D4F96 /* PhotosUI.framework in Frameworks */, - C37F54DC255BB84A002AEA92 /* SessionSnodeKit.framework in Frameworks */, + C37F54DC255BB84A002AEA92 /* SessionNetworkingKit.framework in Frameworks */, C37F5414255BAFA7002AEA92 /* SignalUtilitiesKit.framework in Frameworks */, 455A16DD1F1FEA0000F86704 /* Metal.framework in Frameworks */, 455A16DE1F1FEA0000F86704 /* MetalKit.framework in Frameworks */, @@ -2561,7 +2559,7 @@ files = ( FD0150542CA24471005B08A1 /* Nimble in Frameworks */, FD0150522CA2446D005B08A1 /* Quick in Frameworks */, - FD0150502CA24468005B08A1 /* SessionSnodeKit.framework in Frameworks */, + FD0150502CA24468005B08A1 /* SessionNetworkingKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3668,27 +3666,27 @@ path = Utilities; sourceTree = ""; }; - C3C2A5A0255385C100C340D1 /* SessionSnodeKit */ = { + C3C2A5A0255385C100C340D1 /* SessionNetworkingKit */ = { isa = PBXGroup; children = ( - 947D7FD32D509FC900E8E413 /* SessionNetworkAPI */, C3C2A5B0255385C700C340D1 /* Meta */, FDE754E22C9BAFF4002A2623 /* Crypto */, FD17D79D27F40CAA00122BE0 /* Database */, + FD7F74682BAB8A5D006DDFD8 /* LibSession */, FDF8489929405C5A007DCAE5 /* Models */, - FDF8488F29405C13007DCAE5 /* Types */, + 947D7FD32D509FC900E8E413 /* SessionNetworkAPI */, FD2272842C33E28D004D8A6C /* SnodeAPI */, - FD7F74682BAB8A5D006DDFD8 /* LibSession */, + FDF8488F29405C13007DCAE5 /* Types */, C3C2A5CD255385F300C340D1 /* Utilities */, C3C2A5B9255385ED00C340D1 /* Configuration.swift */, ); - path = SessionSnodeKit; + path = SessionNetworkingKit; sourceTree = ""; }; C3C2A5B0255385C700C340D1 /* Meta */ = { isa = PBXGroup; children = ( - C3C2A5A1255385C100C340D1 /* SessionSnodeKit.h */, + C3C2A5A1255385C100C340D1 /* SessionNetworkingKit.h */, C3C2A5A2255385C100C340D1 /* Info.plist */, ); path = Meta; @@ -3701,7 +3699,6 @@ FD2272BD2C34B710004D8A6C /* Publisher+Utilities.swift */, FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */, C3C2A5D22553860900C340D1 /* String+Trimming.swift */, - C3C2A5D42553860A00C340D1 /* Threading+SSK.swift */, FD2272A82C33E337004D8A6C /* URLResponse+Utilities.swift */, ); path = Utilities; @@ -3858,13 +3855,13 @@ 7BC01A3C241F40AB00BC7C55 /* SessionNotificationServiceExtension */, C331FF1C2558F9D300070591 /* SessionUIKit */, C3C2A6F125539DE700C340D1 /* SessionMessagingKit */, - C3C2A5A0255385C100C340D1 /* SessionSnodeKit */, + C3C2A5A0255385C100C340D1 /* SessionNetworkingKit */, C3C2A67A255388CC00C340D1 /* SessionUtilitiesKit */, C33FD9AC255A548A00E217F9 /* SignalUtilitiesKit */, FD83B9BC27CF2215005E1583 /* _SharedTestUtilities */, FD71160A28D00BAE00B47552 /* SessionTests */, FDC4388F27B9FFC700C60D73 /* SessionMessagingKitTests */, - FDB5DAFB2A981C43002C8721 /* SessionSnodeKitTests */, + FDB5DAFB2A981C43002C8721 /* SessionNetworkingKitTests */, FD83B9B027CF200A005E1583 /* SessionUtilitiesKitTests */, FDE7214E287E50D50093DF33 /* Scripts */, D221A08C169C9E5E00537ABF /* Frameworks */, @@ -3879,7 +3876,7 @@ D221A089169C9E5E00537ABF /* Session.app */, 453518681FC635DD00210559 /* SessionShareExtension.appex */, 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */, - C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */, + C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */, C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */, C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */, C331FF1B2558F9D300070591 /* SessionUIKit.framework */, @@ -3887,7 +3884,7 @@ FDC4388E27B9FFC700C60D73 /* SessionMessagingKitTests.xctest */, FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */, FD71160928D00BAE00B47552 /* SessionTests.xctest */, - FDB5DAFA2A981C42002C8721 /* SessionSnodeKitTests.xctest */, + FDB5DAFA2A981C42002C8721 /* SessionNetworkingKitTests.xctest */, ); name = Products; sourceTree = ""; @@ -4749,15 +4746,15 @@ path = "Group Update Messages"; sourceTree = ""; }; - FDB5DAFB2A981C43002C8721 /* SessionSnodeKitTests */ = { + FDB5DAFB2A981C43002C8721 /* SessionNetworkingKitTests */ = { isa = PBXGroup; children = ( - FD66CB272BF3449B00268FAB /* SessionSnodeKit.xctestplan */, + FD66CB272BF3449B00268FAB /* SessionNetworkingKit.xctestplan */, FD3765DD2AD8F02300DC1489 /* _TestUtilities */, FDAA16792AC28E2200DDBF77 /* Models */, FD2272C52C34E9D1004D8A6C /* Types */, ); - path = SessionSnodeKitTests; + path = SessionNetworkingKitTests; sourceTree = ""; }; FDC13D4E2A16EE41007267C7 /* Types */ = { @@ -4963,7 +4960,7 @@ FDE754E22C9BAFF4002A2623 /* Crypto */ = { isa = PBXGroup; children = ( - FDE754E12C9BAFF4002A2623 /* Crypto+SessionSnodeKit.swift */, + FDE754E12C9BAFF4002A2623 /* Crypto+SessionNetworkingKit.swift */, ); path = Crypto; sourceTree = ""; @@ -5146,7 +5143,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - C3C2A5A3255385C100C340D1 /* SessionSnodeKit.h in Headers */, + C3C2A5A3255385C100C340D1 /* SessionNetworkingKit.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5267,9 +5264,9 @@ productReference = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; productType = "com.apple.product-type.framework"; }; - C3C2A59E255385C100C340D1 /* SessionSnodeKit */ = { + C3C2A59E255385C100C340D1 /* SessionNetworkingKit */ = { isa = PBXNativeTarget; - buildConfigurationList = C3C2A5AA255385C100C340D1 /* Build configuration list for PBXNativeTarget "SessionSnodeKit" */; + buildConfigurationList = C3C2A5AA255385C100C340D1 /* Build configuration list for PBXNativeTarget "SessionNetworkingKit" */; buildPhases = ( C3C2A59A255385C100C340D1 /* Headers */, C3C2A59B255385C100C340D1 /* Sources */, @@ -5282,12 +5279,12 @@ FDB348822BE86A4400B716C2 /* PBXTargetDependency */, FD7F74622BAAA4C7006DDFD8 /* PBXTargetDependency */, ); - name = SessionSnodeKit; + name = SessionNetworkingKit; packageProductDependencies = ( FD6673F72D7021F200041530 /* SessionUtil */, ); productName = SessionSnodeKit; - productReference = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; + productReference = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; productType = "com.apple.product-type.framework"; }; C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */ = { @@ -5425,9 +5422,9 @@ productReference = FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - FDB5DAF92A981C42002C8721 /* SessionSnodeKitTests */ = { + FDB5DAF92A981C42002C8721 /* SessionNetworkingKitTests */ = { isa = PBXNativeTarget; - buildConfigurationList = FD2272522C32910F004D8A6C /* Build configuration list for PBXNativeTarget "SessionSnodeKitTests" */; + buildConfigurationList = FD2272522C32910F004D8A6C /* Build configuration list for PBXNativeTarget "SessionNetworkingKitTests" */; buildPhases = ( FDB5DAF62A981C42002C8721 /* Sources */, FD01504F2CA2445E005B08A1 /* Frameworks */, @@ -5438,9 +5435,9 @@ dependencies = ( FDB5DB002A981C43002C8721 /* PBXTargetDependency */, ); - name = SessionSnodeKitTests; + name = SessionNetworkingKitTests; productName = SessionSnodeKitTests; - productReference = FDB5DAFA2A981C42002C8721 /* SessionSnodeKitTests.xctest */; + productReference = FDB5DAFA2A981C42002C8721 /* SessionNetworkingKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; FDC4388D27B9FFC700C60D73 /* SessionMessagingKitTests */ = { @@ -5611,11 +5608,11 @@ C33FD9AA255A548A00E217F9 /* SignalUtilitiesKit */, C331FF1A2558F9D300070591 /* SessionUIKit */, C3C2A6EF25539DE700C340D1 /* SessionMessagingKit */, - C3C2A59E255385C100C340D1 /* SessionSnodeKit */, + C3C2A59E255385C100C340D1 /* SessionNetworkingKit */, C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */, FD71160828D00BAE00B47552 /* SessionTests */, FDC4388D27B9FFC700C60D73 /* SessionMessagingKitTests */, - FDB5DAF92A981C42002C8721 /* SessionSnodeKitTests */, + FDB5DAF92A981C42002C8721 /* SessionNetworkingKitTests */, FD83B9AE27CF200A005E1583 /* SessionUtilitiesKitTests */, ); }; @@ -5760,7 +5757,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - FD66CB2A2BF3449B00268FAB /* SessionSnodeKit.xctestplan in Resources */, + FD66CB2A2BF3449B00268FAB /* SessionNetworkingKit.xctestplan in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6202,7 +6199,6 @@ FD2272AB2C33E337004D8A6C /* ValidatableResponse.swift in Sources */, FD2272B72C33E337004D8A6C /* Request.swift in Sources */, FD2272B92C33E337004D8A6C /* ResponseInfo.swift in Sources */, - C3C2A5E02553860B00C340D1 /* Threading+SSK.swift in Sources */, FDF848D729405C5B007DCAE5 /* SnodeBatchRequest.swift in Sources */, FDF848CE29405C5B007DCAE5 /* UpdateExpiryAllRequest.swift in Sources */, FDF848C229405C5A007DCAE5 /* OxenDaemonRPCRequest.swift in Sources */, @@ -6224,7 +6220,7 @@ FD5E93D12C100FD70038C25A /* FileUploadResponse.swift in Sources */, FDF848D629405C5B007DCAE5 /* SnodeMessage.swift in Sources */, FD02CC162C3681EF009AB976 /* RevokeSubaccountResponse.swift in Sources */, - FDE754E32C9BAFF4002A2623 /* Crypto+SessionSnodeKit.swift in Sources */, + FDE754E32C9BAFF4002A2623 /* Crypto+SessionNetworkingKit.swift in Sources */, FD2272AD2C33E337004D8A6C /* Network.swift in Sources */, FD2272B32C33E337004D8A6C /* BatchRequest.swift in Sources */, FDF848D129405C5B007DCAE5 /* SnodeSwarmItem.swift in Sources */, @@ -7121,7 +7117,7 @@ }; B8D64FB825BA78270029CFC0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C3C2A59E255385C100C340D1 /* SessionSnodeKit */; + target = C3C2A59E255385C100C340D1 /* SessionNetworkingKit */; targetProxy = B8D64FB725BA78270029CFC0 /* PBXContainerItemProxy */; }; B8D64FBA25BA78270029CFC0 /* PBXTargetDependency */ = { @@ -7136,7 +7132,7 @@ }; B8D64FC425BA784A0029CFC0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C3C2A59E255385C100C340D1 /* SessionSnodeKit */; + target = C3C2A59E255385C100C340D1 /* SessionNetworkingKit */; targetProxy = B8D64FC325BA784A0029CFC0 /* PBXContainerItemProxy */; }; B8D64FC625BA784A0029CFC0 /* PBXTargetDependency */ = { @@ -7156,7 +7152,7 @@ }; C3C2A5A5255385C100C340D1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C3C2A59E255385C100C340D1 /* SessionSnodeKit */; + target = C3C2A59E255385C100C340D1 /* SessionNetworkingKit */; targetProxy = C3C2A5A4255385C100C340D1 /* PBXContainerItemProxy */; }; C3C2A67F255388CC00C340D1 /* PBXTargetDependency */ = { @@ -7218,7 +7214,7 @@ FDB5DB002A981C43002C8721 /* PBXTargetDependency */ = { isa = PBXTargetDependency; platformFilter = ios; - target = C3C2A59E255385C100C340D1 /* SessionSnodeKit */; + target = C3C2A59E255385C100C340D1 /* SessionNetworkingKit */; targetProxy = FDB5DAFF2A981C43002C8721 /* PBXContainerItemProxy */; }; FDC4389427B9FFC700C60D73 /* PBXTargetDependency */ = { @@ -7773,7 +7769,7 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist; + INFOPLIST_FILE = SessionNetworkingKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -7784,7 +7780,7 @@ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionSnodeKit"; + PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionNetworkingKit"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; @@ -7846,7 +7842,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist; + INFOPLIST_FILE = SessionNetworkingKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -7857,7 +7853,7 @@ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionSnodeKit"; + PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionNetworkingKit"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -8382,8 +8378,8 @@ ENABLE_MODULE_VERIFIER = NO; GCC_DYNAMIC_NO_PIC = NO; GENERATE_INFOPLIST_FILE = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionSnodeKitTests; - PRODUCT_NAME = SessionSnodeKitTests; + PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionNetworkingKitTests; + PRODUCT_NAME = SessionNetworkingKitTests; }; name = Debug; }; @@ -8392,8 +8388,8 @@ buildSettings = { ENABLE_MODULE_VERIFIER = NO; GENERATE_INFOPLIST_FILE = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionSnodeKitTests; - PRODUCT_NAME = SessionSnodeKitTests; + PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionNetworkingKitTests; + PRODUCT_NAME = SessionNetworkingKitTests; }; name = App_Store_Release; }; @@ -8581,8 +8577,8 @@ isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionSnodeKitTests; - PRODUCT_NAME = SessionSnodeKitTests; + PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionNetworkingKitTests; + PRODUCT_NAME = SessionNetworkingKitTests; }; name = Debug_Compile_LibSession; }; @@ -8590,8 +8586,8 @@ isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionSnodeKitTests; - PRODUCT_NAME = SessionSnodeKitTests; + PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionNetworkingKitTests; + PRODUCT_NAME = SessionNetworkingKitTests; }; name = App_Store_Release_Compile_LibSession; }; @@ -9103,7 +9099,7 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist; + INFOPLIST_FILE = SessionNetworkingKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -9114,7 +9110,7 @@ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionSnodeKit"; + PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionNetworkingKit"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; @@ -9822,7 +9818,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist; + INFOPLIST_FILE = SessionNetworkingKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -9833,7 +9829,7 @@ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionSnodeKit"; + PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionNetworkingKit"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -10139,7 +10135,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = App_Store_Release; }; - C3C2A5AA255385C100C340D1 /* Build configuration list for PBXNativeTarget "SessionSnodeKit" */ = { + C3C2A5AA255385C100C340D1 /* Build configuration list for PBXNativeTarget "SessionNetworkingKit" */ = { isa = XCConfigurationList; buildConfigurations = ( C3C2A5A8255385C100C340D1 /* Debug */, @@ -10194,7 +10190,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = App_Store_Release; }; - FD2272522C32910F004D8A6C /* Build configuration list for PBXNativeTarget "SessionSnodeKitTests" */ = { + FD2272522C32910F004D8A6C /* Build configuration list for PBXNativeTarget "SessionNetworkingKitTests" */ = { isa = XCConfigurationList; buildConfigurations = ( FD2272502C32910F004D8A6C /* Debug */, diff --git a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme index ad1576e055..e20af5e4f5 100644 --- a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme +++ b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme @@ -77,8 +77,8 @@ @@ -55,8 +55,8 @@ diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index d747fbcdfd..f08656626c 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -9,7 +9,7 @@ import SessionUIKit import SignalUtilitiesKit import SessionMessagingKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { private let dependencies: Dependencies diff --git a/Session/Calls/WebRTC/WebRTCSession.swift b/Session/Calls/WebRTC/WebRTCSession.swift index 99e48d1e25..d579d8a1a5 100644 --- a/Session/Calls/WebRTC/WebRTCSession.swift +++ b/Session/Calls/WebRTC/WebRTCSession.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import WebRTC -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit diff --git a/Session/Closed Groups/EditGroupViewModel.swift b/Session/Closed Groups/EditGroupViewModel.swift index 01916f533c..8309d70282 100644 --- a/Session/Closed Groups/EditGroupViewModel.swift +++ b/Session/Closed Groups/EditGroupViewModel.swift @@ -5,7 +5,7 @@ import Combine import GRDB import DifferenceKit import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit diff --git a/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift b/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift index 54ae63cbc3..de93e57637 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift @@ -3,7 +3,7 @@ import UIKit import SessionUIKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit extension ContextMenuVC { final class ActionView: UIView { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 62baf8fb45..c7021b4186 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -14,7 +14,7 @@ import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit import SwiftUI -import SessionSnodeKit +import SessionNetworkingKit extension ConversationVC: InputViewDelegate, diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index 3c8ede79ed..fe501f887c 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -6,7 +6,7 @@ import UniformTypeIdentifiers import Lucide import GRDB import DifferenceKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit import SessionUIKit diff --git a/Session/Conversations/Message Cells/Content Views/DisappearingMessageTimerView.swift b/Session/Conversations/Message Cells/Content Views/DisappearingMessageTimerView.swift index b0f17cc31e..b835fe5e86 100644 --- a/Session/Conversations/Message Cells/Content Views/DisappearingMessageTimerView.swift +++ b/Session/Conversations/Message Cells/Content Views/DisappearingMessageTimerView.swift @@ -1,7 +1,7 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import UIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit class DisappearingMessageTimerView: UIView { diff --git a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift index b7df60ec3c..7ef4514e03 100644 --- a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift @@ -7,7 +7,7 @@ import DifferenceKit import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource { typealias TableItem = String diff --git a/Session/Conversations/Settings/ThreadNotificationSettingsViewModel.swift b/Session/Conversations/Settings/ThreadNotificationSettingsViewModel.swift index e89a98303b..f5b51cf455 100644 --- a/Session/Conversations/Settings/ThreadNotificationSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadNotificationSettingsViewModel.swift @@ -7,7 +7,7 @@ import DifferenceKit import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit class ThreadNotificationSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource { public let dependencies: Dependencies diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index c2462e2193..5045825fb5 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -9,7 +9,7 @@ import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit class ThreadSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource { public let dependencies: Dependencies diff --git a/Session/Home/New Conversation/NewMessageScreen.swift b/Session/Home/New Conversation/NewMessageScreen.swift index c478eb8331..725fa06ed8 100644 --- a/Session/Home/New Conversation/NewMessageScreen.swift +++ b/Session/Home/New Conversation/NewMessageScreen.swift @@ -5,7 +5,7 @@ import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit struct NewMessageScreen: View { @EnvironmentObject var host: HostWrapper diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift index bf01d591f7..b8a69f704f 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift @@ -4,7 +4,7 @@ import UIKit import Combine import UniformTypeIdentifiers import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SignalUtilitiesKit import SessionUtilitiesKit diff --git a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift index 2e9b72936b..eb2cd33430 100644 --- a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift +++ b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift @@ -5,7 +5,7 @@ import Combine import CoreServices import UniformTypeIdentifiers import SignalUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Singleton diff --git a/Session/Media Viewing & Editing/MediaPageViewController.swift b/Session/Media Viewing & Editing/MediaPageViewController.swift index 51523207fb..f00db791d2 100644 --- a/Session/Media Viewing & Editing/MediaPageViewController.swift +++ b/Session/Media Viewing & Editing/MediaPageViewController.swift @@ -6,7 +6,7 @@ import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, MediaDetailViewControllerDelegate, InteractivelyDismissableViewController { class DynamicallySizedView: UIView { diff --git a/Session/Media Viewing & Editing/MessageInfoScreen.swift b/Session/Media Viewing & Editing/MessageInfoScreen.swift index e614b81b5f..f4c447dd46 100644 --- a/Session/Media Viewing & Editing/MessageInfoScreen.swift +++ b/Session/Media Viewing & Editing/MessageInfoScreen.swift @@ -2,7 +2,7 @@ import SwiftUI import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import SessionMessagingKit diff --git a/Session/Media Viewing & Editing/PhotoCapture.swift b/Session/Media Viewing & Editing/PhotoCapture.swift index d4e1bc8d2d..30b38d6a73 100644 --- a/Session/Media Viewing & Editing/PhotoCapture.swift +++ b/Session/Media Viewing & Editing/PhotoCapture.swift @@ -6,7 +6,7 @@ import Foundation import Combine import AVFoundation import CoreServices -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 46cafadd53..eea5572f6d 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -8,7 +8,7 @@ import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/Session/Meta/Session+SNUIKit.swift b/Session/Meta/Session+SNUIKit.swift index 862f7a9eb3..2c7a2aedfe 100644 --- a/Session/Meta/Session+SNUIKit.swift +++ b/Session/Meta/Session+SNUIKit.swift @@ -3,7 +3,7 @@ import UIKit import AVFoundation import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - SessionSNUIKitConfig diff --git a/Session/Notifications/NotificationActionHandler.swift b/Session/Notifications/NotificationActionHandler.swift index 5c0999824b..083aa94356 100644 --- a/Session/Notifications/NotificationActionHandler.swift +++ b/Session/Notifications/NotificationActionHandler.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SignalUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 6a885dfd2c..7cf9efb5a4 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit diff --git a/Session/Onboarding/LoadingScreen.swift b/Session/Onboarding/LoadingScreen.swift index 4fffd9fe46..b731acafbd 100644 --- a/Session/Onboarding/LoadingScreen.swift +++ b/Session/Onboarding/LoadingScreen.swift @@ -3,7 +3,7 @@ import SwiftUI import Combine import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SignalUtilitiesKit import SessionUtilitiesKit diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 52da3682fa..4ca9d056dd 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -5,7 +5,7 @@ import Combine import GRDB import SessionUtilitiesKit import SessionMessagingKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Cache diff --git a/Session/Onboarding/PNModeScreen.swift b/Session/Onboarding/PNModeScreen.swift index d4976596a1..40100bf54e 100644 --- a/Session/Onboarding/PNModeScreen.swift +++ b/Session/Onboarding/PNModeScreen.swift @@ -3,7 +3,7 @@ import SwiftUI import Combine import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SignalUtilitiesKit import SessionUtilitiesKit diff --git a/Session/Path/PathStatusView.swift b/Session/Path/PathStatusView.swift index 907947605f..33108de585 100644 --- a/Session/Path/PathStatusView.swift +++ b/Session/Path/PathStatusView.swift @@ -3,7 +3,7 @@ import UIKit import Combine import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit diff --git a/Session/Path/PathVC.swift b/Session/Path/PathVC.swift index ab37d115f3..130224d297 100644 --- a/Session/Path/PathVC.swift +++ b/Session/Path/PathVC.swift @@ -5,7 +5,7 @@ import Combine import NVActivityIndicatorView import SessionMessagingKit import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit final class PathVC: BaseVC { diff --git a/Session/Settings/DeveloperSettingsViewModel.swift b/Session/Settings/DeveloperSettingsViewModel.swift index e61c5570c0..d007a2097e 100644 --- a/Session/Settings/DeveloperSettingsViewModel.swift +++ b/Session/Settings/DeveloperSettingsViewModel.swift @@ -8,7 +8,7 @@ import Compression import GRDB import DifferenceKit import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift index 4ade2b621d..d4162bb02c 100644 --- a/Session/Settings/NukeDataModal.swift +++ b/Session/Settings/NukeDataModal.swift @@ -4,7 +4,7 @@ import UIKit import Combine import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SignalUtilitiesKit import SessionUtilitiesKit diff --git a/Session/Settings/SessionNetworkScreen/SessionNetworkScreen+ViewModel.swift b/Session/Settings/SessionNetworkScreen/SessionNetworkScreen+ViewModel.swift index 2446f852f2..d7795ea74f 100644 --- a/Session/Settings/SessionNetworkScreen/SessionNetworkScreen+ViewModel.swift +++ b/Session/Settings/SessionNetworkScreen/SessionNetworkScreen+ViewModel.swift @@ -5,7 +5,7 @@ import SwiftUI import Combine import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import SessionMessagingKit diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index bb30cb2599..b03942a719 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -5,7 +5,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit diff --git a/Session/Utilities/IP2Country.swift b/Session/Utilities/IP2Country.swift index 8ef98ce322..56b9102bba 100644 --- a/Session/Utilities/IP2Country.swift +++ b/Session/Utilities/IP2Country.swift @@ -5,7 +5,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Cache diff --git a/SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift b/SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift index bea6331051..8621e3a2fa 100644 --- a/SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift +++ b/SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift @@ -4,7 +4,7 @@ import Foundation import CryptoKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtil import SessionUtilitiesKit diff --git a/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift index bfcdbea5d3..b9b3e31588 100644 --- a/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift +++ b/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit /// This migration sets up the standard jobs, since we want these jobs to run before any "once-off" jobs we do this migration /// before running the `YDBToGRDBMigration` diff --git a/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift b/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift index 07db8962d8..c5c4c4a4d8 100644 --- a/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift +++ b/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit /// This migration used to remove the legacy YapDatabase files (the old logic has been removed and is no longer supported so it now does nothing) enum _004_RemoveLegacyYDB: Migration { diff --git a/SessionMessagingKit/Database/Migrations/_022_GroupsRebuildChanges.swift b/SessionMessagingKit/Database/Migrations/_022_GroupsRebuildChanges.swift index 3f60a24d4a..d3ad1f25be 100644 --- a/SessionMessagingKit/Database/Migrations/_022_GroupsRebuildChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_022_GroupsRebuildChanges.swift @@ -5,7 +5,7 @@ import Foundation import UIKit.UIImage import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit enum _022_GroupsRebuildChanges: Migration { diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index 3f1f5854d9..a5a8279dff 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -7,7 +7,7 @@ import Combine import UniformTypeIdentifiers import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUIKit public struct Attachment: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { diff --git a/SessionMessagingKit/Database/Models/ClosedGroup.swift b/SessionMessagingKit/Database/Models/ClosedGroup.swift index e525398357..b2a680ed7a 100644 --- a/SessionMessagingKit/Database/Models/ClosedGroup.swift +++ b/SessionMessagingKit/Database/Models/ClosedGroup.swift @@ -5,7 +5,7 @@ import Combine import GRDB import DifferenceKit import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public struct ClosedGroup: Codable, Equatable, Hashable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { diff --git a/SessionMessagingKit/Database/Models/ConfigDump.swift b/SessionMessagingKit/Database/Models/ConfigDump.swift index b264051429..0b429793ad 100644 --- a/SessionMessagingKit/Database/Models/ConfigDump.swift +++ b/SessionMessagingKit/Database/Models/ConfigDump.swift @@ -4,7 +4,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public struct ConfigDump: Codable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { diff --git a/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift b/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift index 22c7d9a580..d9f804ded0 100644 --- a/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift +++ b/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift @@ -5,7 +5,7 @@ import GRDB import SessionUIKit import SessionUtil import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public struct DisappearingMessagesConfiguration: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "disappearingMessagesConfiguration" } diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index 59373a3201..47590f8d3a 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public struct Interaction: Codable, Identifiable, Equatable, Hashable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "interaction" } diff --git a/SessionMessagingKit/Database/Models/LinkPreview.swift b/SessionMessagingKit/Database/Models/LinkPreview.swift index faa84197cc..d0a32ca8db 100644 --- a/SessionMessagingKit/Database/Models/LinkPreview.swift +++ b/SessionMessagingKit/Database/Models/LinkPreview.swift @@ -7,7 +7,7 @@ import Combine import UniformTypeIdentifiers import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public struct LinkPreview: Codable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "linkPreview" } diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index 3e069e9268..1684d94c27 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public struct SessionThread: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible, IdentifiableTableRecord { public static var databaseTableName: String { "thread" } diff --git a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift index 0127ebc746..b52dbe95d5 100644 --- a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public enum AttachmentDownloadJob: JobExecutor { public static var maxFailureCount: Int = 3 diff --git a/SessionMessagingKit/Jobs/AttachmentUploadJob.swift b/SessionMessagingKit/Jobs/AttachmentUploadJob.swift index d1dc85ddea..a3cd20c22a 100644 --- a/SessionMessagingKit/Jobs/AttachmentUploadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentUploadJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/CheckForAppUpdatesJob.swift b/SessionMessagingKit/Jobs/CheckForAppUpdatesJob.swift index aba703204e..45022e0421 100644 --- a/SessionMessagingKit/Jobs/CheckForAppUpdatesJob.swift +++ b/SessionMessagingKit/Jobs/CheckForAppUpdatesJob.swift @@ -2,7 +2,7 @@ import Foundation import Combine -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/ConfigMessageReceiveJob.swift b/SessionMessagingKit/Jobs/ConfigMessageReceiveJob.swift index 5720b9acfb..f0ba83de12 100644 --- a/SessionMessagingKit/Jobs/ConfigMessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/ConfigMessageReceiveJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/ConfigurationSyncJob.swift index dc37ac0c02..136b736815 100644 --- a/SessionMessagingKit/Jobs/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/ConfigurationSyncJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/DisappearingMessagesJob.swift b/SessionMessagingKit/Jobs/DisappearingMessagesJob.swift index 0f7e16f812..31a09b3b8a 100644 --- a/SessionMessagingKit/Jobs/DisappearingMessagesJob.swift +++ b/SessionMessagingKit/Jobs/DisappearingMessagesJob.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift b/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift index ec9ee87bc3..c5257be82b 100644 --- a/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift +++ b/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift @@ -6,7 +6,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/ExpirationUpdateJob.swift b/SessionMessagingKit/Jobs/ExpirationUpdateJob.swift index 0ffd051f5a..03e602be04 100644 --- a/SessionMessagingKit/Jobs/ExpirationUpdateJob.swift +++ b/SessionMessagingKit/Jobs/ExpirationUpdateJob.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public enum ExpirationUpdateJob: JobExecutor { public static var maxFailureCount: Int = -1 diff --git a/SessionMessagingKit/Jobs/GarbageCollectionJob.swift b/SessionMessagingKit/Jobs/GarbageCollectionJob.swift index b799931f85..2d2b0f87ce 100644 --- a/SessionMessagingKit/Jobs/GarbageCollectionJob.swift +++ b/SessionMessagingKit/Jobs/GarbageCollectionJob.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/GetExpirationJob.swift b/SessionMessagingKit/Jobs/GetExpirationJob.swift index bf15ef97b7..a70bfc5d0a 100644 --- a/SessionMessagingKit/Jobs/GetExpirationJob.swift +++ b/SessionMessagingKit/Jobs/GetExpirationJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public enum GetExpirationJob: JobExecutor { diff --git a/SessionMessagingKit/Jobs/GroupInviteMemberJob.swift b/SessionMessagingKit/Jobs/GroupInviteMemberJob.swift index b40827c6e8..e652f75f13 100644 --- a/SessionMessagingKit/Jobs/GroupInviteMemberJob.swift +++ b/SessionMessagingKit/Jobs/GroupInviteMemberJob.swift @@ -5,7 +5,7 @@ import Combine import GRDB import SessionUIKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/GroupLeavingJob.swift b/SessionMessagingKit/Jobs/GroupLeavingJob.swift index 475a5ea13d..92448ad51b 100644 --- a/SessionMessagingKit/Jobs/GroupLeavingJob.swift +++ b/SessionMessagingKit/Jobs/GroupLeavingJob.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/GroupPromoteMemberJob.swift b/SessionMessagingKit/Jobs/GroupPromoteMemberJob.swift index 10a934451b..d46639ac78 100644 --- a/SessionMessagingKit/Jobs/GroupPromoteMemberJob.swift +++ b/SessionMessagingKit/Jobs/GroupPromoteMemberJob.swift @@ -5,7 +5,7 @@ import Combine import GRDB import SessionUIKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/MessageSendJob.swift b/SessionMessagingKit/Jobs/MessageSendJob.swift index 590a660a46..49948e7bac 100644 --- a/SessionMessagingKit/Jobs/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/MessageSendJob.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/ProcessPendingGroupMemberRemovalsJob.swift b/SessionMessagingKit/Jobs/ProcessPendingGroupMemberRemovalsJob.swift index 3decb55cf9..096d6729ff 100644 --- a/SessionMessagingKit/Jobs/ProcessPendingGroupMemberRemovalsJob.swift +++ b/SessionMessagingKit/Jobs/ProcessPendingGroupMemberRemovalsJob.swift @@ -6,7 +6,7 @@ import GRDB import SessionUtil import SessionUIKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/SendReadReceiptsJob.swift b/SessionMessagingKit/Jobs/SendReadReceiptsJob.swift index 1da0c5a95c..9196301bfc 100644 --- a/SessionMessagingKit/Jobs/SendReadReceiptsJob.swift +++ b/SessionMessagingKit/Jobs/SendReadReceiptsJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public enum SendReadReceiptsJob: JobExecutor { diff --git a/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupInfo.swift b/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupInfo.swift index ee77869c1e..eec8fbd631 100644 --- a/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupInfo.swift +++ b/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupInfo.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtil -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Size Restrictions diff --git a/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupMembers.swift b/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupMembers.swift index 51215e9344..8774ca003d 100644 --- a/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupMembers.swift +++ b/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupMembers.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtil -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Size Restrictions diff --git a/SessionMessagingKit/LibSession/Config Handling/LibSession+Shared.swift b/SessionMessagingKit/LibSession/Config Handling/LibSession+Shared.swift index 522957def1..ce4d1986dd 100644 --- a/SessionMessagingKit/LibSession/Config Handling/LibSession+Shared.swift +++ b/SessionMessagingKit/LibSession/Config Handling/LibSession+Shared.swift @@ -3,7 +3,7 @@ import UIKit import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtil import SessionUtilitiesKit diff --git a/SessionMessagingKit/LibSession/Config Handling/LibSession+SharedGroup.swift b/SessionMessagingKit/LibSession/Config Handling/LibSession+SharedGroup.swift index 54908b4470..64a1f5260a 100644 --- a/SessionMessagingKit/LibSession/Config Handling/LibSession+SharedGroup.swift +++ b/SessionMessagingKit/LibSession/Config Handling/LibSession+SharedGroup.swift @@ -3,7 +3,7 @@ import UIKit import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtil import SessionUtilitiesKit diff --git a/SessionMessagingKit/LibSession/Config Handling/LibSession+UserGroups.swift b/SessionMessagingKit/LibSession/Config Handling/LibSession+UserGroups.swift index 27f7bf7fdf..c9724f15b1 100644 --- a/SessionMessagingKit/LibSession/Config Handling/LibSession+UserGroups.swift +++ b/SessionMessagingKit/LibSession/Config Handling/LibSession+UserGroups.swift @@ -4,7 +4,7 @@ import Foundation import GRDB import SessionUtil import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Size Restrictions diff --git a/SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift b/SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift index f90e4f37b6..be68896f71 100644 --- a/SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift +++ b/SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUIKit import SessionUtil import SessionUtilitiesKit diff --git a/SessionMessagingKit/LibSession/Types/Config.swift b/SessionMessagingKit/LibSession/Types/Config.swift index 52c8057ef5..7d503d57f0 100644 --- a/SessionMessagingKit/LibSession/Types/Config.swift +++ b/SessionMessagingKit/LibSession/Types/Config.swift @@ -4,7 +4,7 @@ import Foundation import SessionUtil -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public extension LibSession { diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index b7d393a02c..e6e7a98934 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public extension Message { diff --git a/SessionMessagingKit/Messages/Message+DisappearingMessages.swift b/SessionMessagingKit/Messages/Message+DisappearingMessages.swift index d511a374fc..4212f74901 100644 --- a/SessionMessagingKit/Messages/Message+DisappearingMessages.swift +++ b/SessionMessagingKit/Messages/Message+DisappearingMessages.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUIKit import SessionUtilitiesKit diff --git a/SessionMessagingKit/Messages/Message+Origin.swift b/SessionMessagingKit/Messages/Message+Origin.swift index 4da490c326..540c6eb5d5 100644 --- a/SessionMessagingKit/Messages/Message+Origin.swift +++ b/SessionMessagingKit/Messages/Message+Origin.swift @@ -1,7 +1,7 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public extension Message { diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index c640978d0d..1f775614c2 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit /// Abstract base class for `VisibleMessage` and `ControlMessage`. diff --git a/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift b/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift index 219021f442..f50966a7ac 100644 --- a/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift +++ b/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension OpenGroupAPI { diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 78b97e3f08..9f80cbe964 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -3,7 +3,7 @@ // stringlint:disable import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public enum OpenGroupAPI { diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 4c6d534abe..9cd978e457 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Singleton diff --git a/SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift b/SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift index 0b3dbc54ab..29189d3cef 100644 --- a/SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift +++ b/SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift @@ -3,7 +3,7 @@ // stringlint:disable import Foundation -import SessionSnodeKit +import SessionNetworkingKit public extension HTTPHeader { static let sogsPubKey: HTTPHeader = "X-SOGS-Pubkey" diff --git a/SessionMessagingKit/Open Groups/Types/HTTPQueryParam+OpenGroup.swift b/SessionMessagingKit/Open Groups/Types/HTTPQueryParam+OpenGroup.swift index a9af9824ad..4eb7f6c206 100644 --- a/SessionMessagingKit/Open Groups/Types/HTTPQueryParam+OpenGroup.swift +++ b/SessionMessagingKit/Open Groups/Types/HTTPQueryParam+OpenGroup.swift @@ -3,7 +3,7 @@ // stringlint:disable import Foundation -import SessionSnodeKit +import SessionNetworkingKit public extension HTTPQueryParam { static let publicKey: HTTPQueryParam = "public_key" diff --git a/SessionMessagingKit/Open Groups/Types/Request+OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/Types/Request+OpenGroupAPI.swift index 6242a05447..5c8d72187f 100644 --- a/SessionMessagingKit/Open Groups/Types/Request+OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/Types/Request+OpenGroupAPI.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: Request - OpenGroupAPI diff --git a/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift b/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift index 9da8faf919..e5e0b9bd34 100644 --- a/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift +++ b/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift @@ -3,7 +3,7 @@ // stringlint:disable import Foundation -import SessionSnodeKit +import SessionNetworkingKit extension OpenGroupAPI { public enum Endpoint: EndpointType { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift index 9670210fdc..cdd2921bcf 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift @@ -5,7 +5,7 @@ import AVFAudio import GRDB import WebRTC import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift index 4186c65e0b..4c5f597204 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension MessageReceiver { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift index 95a548578b..e771404d9a 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension MessageReceiver { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift index 8396652b28..ec2ecadc46 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit extension MessageReceiver { internal static func handleNewLegacyClosedGroup( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LibSession.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LibSession.swift index acfe478541..f209de30ed 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LibSession.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LibSession.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension MessageReceiver { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index b5d2893a4d..1ad39c5a05 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -6,7 +6,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit extension MessageReceiver { internal static func handleMessageRequestResponse( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift index ae75d068a5..a30f849c81 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension MessageReceiver { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index e096a96a9d..6c03cc8ad9 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension MessageReceiver { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift index 58caa31955..c22b57d576 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit extension MessageSender { private typealias PreparedGroupData = ( diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 059032f9c6..13a32bb3dd 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -4,7 +4,7 @@ import Foundation import GRDB import SessionUIKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index bb681dd1b5..507a201df1 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension MessageSender { diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 510819e3f4..a31872dccb 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -3,7 +3,7 @@ // stringlint:disable import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift index 663bafb174..f29520550b 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift @@ -1,7 +1,7 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit extension PushNotificationAPI { struct LegacyUnsubscribeRequest: Codable { diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift index fefdbad9de..2267c9e130 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift @@ -1,7 +1,7 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit extension PushNotificationAPI { public struct NotificationMetadata: Codable, Equatable { diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift index 5138d5d8f5..971b969560 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift @@ -3,7 +3,7 @@ // stringlint:disable import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension PushNotificationAPI { diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift index 1d29c882d8..4f4b7da70c 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift @@ -3,7 +3,7 @@ // stringlint:disable import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension PushNotificationAPI { diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index 5c4bcd9c7c..2b4201f5a1 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -5,7 +5,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - KeychainStorage diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift index 36ed02e3e2..a5d92afb4c 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift @@ -3,7 +3,7 @@ // stringlint:disable import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public extension PushNotificationAPI { diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/Request+PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Types/Request+PushNotificationAPI.swift index 78000ad2ce..c63988f5d1 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/Request+PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Types/Request+PushNotificationAPI.swift @@ -1,7 +1,7 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: Request - PushNotificationAPI diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CommunityPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CommunityPoller.swift index 835329ec9c..39b2d0956f 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CommunityPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CommunityPoller.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Cache diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift index e9290f1800..804ed95182 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Singleton diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift index 667ec2909c..1ec82b4124 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Cache diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/PollerType.swift b/SessionMessagingKit/Sending & Receiving/Pollers/PollerType.swift index 8cdacd5c99..91ef8cf1b9 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/PollerType.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/PollerType.swift @@ -4,7 +4,7 @@ import Foundation import Combine -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift index b632d6d87d..2f3ca1906a 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - SwarmPollerType diff --git a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift index 3f5da3a998..3486db29e6 100644 --- a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift +++ b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Singleton diff --git a/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift b/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift index 05553088ed..89d21fdc30 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public extension MessageViewModel { diff --git a/SessionMessagingKit/Utilities/Authentication+SessionMessagingKit.swift b/SessionMessagingKit/Utilities/Authentication+SessionMessagingKit.swift index 4a1069c1ca..745e1e4418 100644 --- a/SessionMessagingKit/Utilities/Authentication+SessionMessagingKit.swift +++ b/SessionMessagingKit/Utilities/Authentication+SessionMessagingKit.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Authentication Types diff --git a/SessionMessagingKit/Utilities/DisplayPictureManager.swift b/SessionMessagingKit/Utilities/DisplayPictureManager.swift index f544f6eb3e..a96b8634fc 100644 --- a/SessionMessagingKit/Utilities/DisplayPictureManager.swift +++ b/SessionMessagingKit/Utilities/DisplayPictureManager.swift @@ -4,7 +4,7 @@ import UIKit import Combine import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Singleton diff --git a/SessionMessagingKit/Utilities/MessageWrapper.swift b/SessionMessagingKit/Utilities/MessageWrapper.swift index 1e7f97ba3c..3ff9796b7b 100644 --- a/SessionMessagingKit/Utilities/MessageWrapper.swift +++ b/SessionMessagingKit/Utilities/MessageWrapper.swift @@ -1,7 +1,7 @@ // stringlint:disable import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public enum MessageWrapper { diff --git a/SessionMessagingKit/Utilities/SNProtoEnvelope+Conversion.swift b/SessionMessagingKit/Utilities/SNProtoEnvelope+Conversion.swift index 98d331cd1d..ed46bc066c 100644 --- a/SessionMessagingKit/Utilities/SNProtoEnvelope+Conversion.swift +++ b/SessionMessagingKit/Utilities/SNProtoEnvelope+Conversion.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public extension SNProtoEnvelope { diff --git a/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift b/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift index 0ca3ed5345..85d0809b20 100644 --- a/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift +++ b/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift @@ -6,7 +6,7 @@ import GRDB import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit @testable import SessionUtilitiesKit diff --git a/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift b/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift index 39d2bff89d..a226aab1df 100644 --- a/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift +++ b/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift @@ -6,7 +6,7 @@ import GRDB import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit @testable import SessionUtilitiesKit diff --git a/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift b/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift index f9de94bac5..07bda1e05f 100644 --- a/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift +++ b/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift @@ -4,12 +4,12 @@ import Foundation import GRDB import SessionUtil import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit class LibSessionGroupInfoSpec: QuickSpec { @@ -31,7 +31,7 @@ class LibSessionGroupInfoSpec: QuickSpec { migrationTargets: [ SNUtilitiesKit.self, SNMessagingKit.self, - SNSnodeKit.self + SNNetworkingKit.self ], using: dependencies, initialData: { db in diff --git a/SessionMessagingKitTests/LibSession/LibSessionGroupMembersSpec.swift b/SessionMessagingKitTests/LibSession/LibSessionGroupMembersSpec.swift index 5e2884cd3e..8001ede737 100644 --- a/SessionMessagingKitTests/LibSession/LibSessionGroupMembersSpec.swift +++ b/SessionMessagingKitTests/LibSession/LibSessionGroupMembersSpec.swift @@ -8,7 +8,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit class LibSessionGroupMembersSpec: QuickSpec { diff --git a/SessionMessagingKitTests/LibSession/LibSessionSpec.swift b/SessionMessagingKitTests/LibSession/LibSessionSpec.swift index d135064fcf..f23341efdf 100644 --- a/SessionMessagingKitTests/LibSession/LibSessionSpec.swift +++ b/SessionMessagingKitTests/LibSession/LibSessionSpec.swift @@ -8,7 +8,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit class LibSessionSpec: QuickSpec { diff --git a/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift b/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift index db9aa0df35..ee3f4d94ac 100644 --- a/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import Quick diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift index fa13eeea02..73ae2a3cc6 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import Quick diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index 556a2adee0..24635b39af 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -4,7 +4,7 @@ import UIKit import Combine import GRDB import SessionUtil -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import Quick diff --git a/SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift b/SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift index 88e568f21b..b9e37aca22 100644 --- a/SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import Quick diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift index debc33045b..be86b9ed76 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift @@ -9,7 +9,7 @@ import SessionUtil import SessionUtilitiesKit import SessionUIKit -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit class MessageReceiverGroupsSpec: QuickSpec { @@ -31,7 +31,7 @@ class MessageReceiverGroupsSpec: QuickSpec { customWriter: try! DatabaseQueue(), migrationTargets: [ SNUtilitiesKit.self, - SNSnodeKit.self, + SNNetworkingKit.self, SNMessagingKit.self ], using: dependencies, diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift index 1bd4a1b614..2f55e63806 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift @@ -10,7 +10,7 @@ import Quick import Nimble @testable import SessionMessagingKit -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class MessageSenderGroupsSpec: QuickSpec { override class func spec() { diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageSenderSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageSenderSpec.swift index 5e67562442..6ddb79b9bf 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageSenderSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageSenderSpec.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import Quick diff --git a/SessionMessagingKitTests/Sending & Receiving/Pollers/CommunityPollerSpec.swift b/SessionMessagingKitTests/Sending & Receiving/Pollers/CommunityPollerSpec.swift index 742f51d783..6f966ebf9c 100644 --- a/SessionMessagingKitTests/Sending & Receiving/Pollers/CommunityPollerSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/Pollers/CommunityPollerSpec.swift @@ -3,7 +3,7 @@ import UIKit import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import Quick diff --git a/SessionMessagingKitTests/_TestUtilities/MockPoller.swift b/SessionMessagingKitTests/_TestUtilities/MockPoller.swift index b3f044e3bd..794fa81829 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockPoller.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockPoller.swift @@ -2,7 +2,7 @@ import Foundation import Combine -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit @testable import SessionMessagingKit diff --git a/SessionMessagingKitTests/_TestUtilities/MockSwarmPoller.swift b/SessionMessagingKitTests/_TestUtilities/MockSwarmPoller.swift index 43ab428f96..d20f1da875 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockSwarmPoller.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockSwarmPoller.swift @@ -2,7 +2,7 @@ import Foundation import Combine -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit @testable import SessionMessagingKit diff --git a/SessionSnodeKit/Configuration.swift b/SessionNetworkingKit/Configuration.swift similarity index 89% rename from SessionSnodeKit/Configuration.swift rename to SessionNetworkingKit/Configuration.swift index af6ed9cc8d..18a73d2615 100644 --- a/SessionSnodeKit/Configuration.swift +++ b/SessionNetworkingKit/Configuration.swift @@ -4,10 +4,10 @@ import Foundation import GRDB import SessionUtilitiesKit -public enum SNSnodeKit: MigratableTarget { // Just to make the external API nice +public enum SNNetworkingKit: MigratableTarget { // Just to make the external API nice public static func migrations() -> TargetMigrations { return TargetMigrations( - identifier: .snodeKit, + identifier: .networkingKit, migrations: [ [ _001_InitialSetupMigration.self, diff --git a/SessionSnodeKit/Crypto/Crypto+SessionSnodeKit.swift b/SessionNetworkingKit/Crypto/Crypto+SessionNetworkingKit.swift similarity index 100% rename from SessionSnodeKit/Crypto/Crypto+SessionSnodeKit.swift rename to SessionNetworkingKit/Crypto/Crypto+SessionNetworkingKit.swift diff --git a/SessionSnodeKit/Database/Migrations/_001_InitialSetupMigration.swift b/SessionNetworkingKit/Database/Migrations/_001_InitialSetupMigration.swift similarity index 96% rename from SessionSnodeKit/Database/Migrations/_001_InitialSetupMigration.swift rename to SessionNetworkingKit/Database/Migrations/_001_InitialSetupMigration.swift index 02f160fe0c..9473c323f8 100644 --- a/SessionSnodeKit/Database/Migrations/_001_InitialSetupMigration.swift +++ b/SessionNetworkingKit/Database/Migrations/_001_InitialSetupMigration.swift @@ -7,7 +7,7 @@ import GRDB import SessionUtilitiesKit enum _001_InitialSetupMigration: Migration { - static let target: TargetMigrations.Identifier = .snodeKit + static let target: TargetMigrations.Identifier = .networkingKit static let identifier: String = "initialSetup" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionNetworkingKit/Database/Migrations/_002_SetupStandardJobs.swift similarity index 96% rename from SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift rename to SessionNetworkingKit/Database/Migrations/_002_SetupStandardJobs.swift index e92355cc9e..845a42fe16 100644 --- a/SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift +++ b/SessionNetworkingKit/Database/Migrations/_002_SetupStandardJobs.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit /// This migration sets up the standard jobs, since we want these jobs to run before any "once-off" jobs we do this migration /// before running the `YDBToGRDBMigration` enum _002_SetupStandardJobs: Migration { - static let target: TargetMigrations.Identifier = .snodeKit + static let target: TargetMigrations.Identifier = .networkingKit static let identifier: String = "SetupStandardJobs" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionNetworkingKit/Database/Migrations/_003_YDBToGRDBMigration.swift similarity index 88% rename from SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift rename to SessionNetworkingKit/Database/Migrations/_003_YDBToGRDBMigration.swift index 4e826bc308..382874faf3 100644 --- a/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionNetworkingKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -5,7 +5,7 @@ import GRDB import SessionUtilitiesKit enum _003_YDBToGRDBMigration: Migration { - static let target: TargetMigrations.Identifier = .snodeKit + static let target: TargetMigrations.Identifier = .networkingKit static let identifier: String = "YDBToGRDBMigration" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionSnodeKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift b/SessionNetworkingKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift similarity index 93% rename from SessionSnodeKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift rename to SessionNetworkingKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift index 486665167c..989df981c8 100644 --- a/SessionSnodeKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift +++ b/SessionNetworkingKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift @@ -8,7 +8,7 @@ import SessionUtilitiesKit /// ignore their hashes when subsequently trying to fetch new messages (which results in the storage server returning /// messages from the beginning of time) enum _004_FlagMessageHashAsDeletedOrInvalid: Migration { - static let target: TargetMigrations.Identifier = .snodeKit + static let target: TargetMigrations.Identifier = .networkingKit static let identifier: String = "FlagMessageHashAsDeletedOrInvalid" static let minExpectedRunDuration: TimeInterval = 0.2 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionSnodeKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift b/SessionNetworkingKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift similarity index 97% rename from SessionSnodeKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift rename to SessionNetworkingKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift index acc361590d..6565fc40d1 100644 --- a/SessionSnodeKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift +++ b/SessionNetworkingKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift @@ -6,7 +6,7 @@ import SessionUtilitiesKit /// This migration adds a primary key to `SnodeReceivedMessageInfo` based on the key and hash to speed up lookup enum _005_AddSnodeReveivedMessageInfoPrimaryKey: Migration { - static let target: TargetMigrations.Identifier = .snodeKit + static let target: TargetMigrations.Identifier = .networkingKit static let identifier: String = "AddSnodeReveivedMessageInfoPrimaryKey" static let minExpectedRunDuration: TimeInterval = 0.2 static let createdTables: [(TableRecord & FetchableRecord).Type] = [SnodeReceivedMessageInfo.self] diff --git a/SessionSnodeKit/Database/Migrations/_006_DropSnodeCache.swift b/SessionNetworkingKit/Database/Migrations/_006_DropSnodeCache.swift similarity index 94% rename from SessionSnodeKit/Database/Migrations/_006_DropSnodeCache.swift rename to SessionNetworkingKit/Database/Migrations/_006_DropSnodeCache.swift index b2a3d41bd2..6cc16ea4ef 100644 --- a/SessionSnodeKit/Database/Migrations/_006_DropSnodeCache.swift +++ b/SessionNetworkingKit/Database/Migrations/_006_DropSnodeCache.swift @@ -6,7 +6,7 @@ import SessionUtilitiesKit /// This migration drops the current `SnodePool` and `SnodeSet` and their associated jobs as they are handled by `libSession` now enum _006_DropSnodeCache: Migration { - static let target: TargetMigrations.Identifier = .snodeKit + static let target: TargetMigrations.Identifier = .networkingKit static let identifier: String = "DropSnodeCache" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionSnodeKit/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift b/SessionNetworkingKit/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift similarity index 98% rename from SessionSnodeKit/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift rename to SessionNetworkingKit/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift index aa74f45ff4..779eecee5e 100644 --- a/SessionSnodeKit/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift +++ b/SessionNetworkingKit/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift @@ -6,7 +6,7 @@ import SessionUtilitiesKit /// This migration splits the old `key` structure used for `SnodeReceivedMessageInfo` into separate columns for more efficient querying enum _007_SplitSnodeReceivedMessageInfo: Migration { - static let target: TargetMigrations.Identifier = .snodeKit + static let target: TargetMigrations.Identifier = .networkingKit static let identifier: String = "SplitSnodeReceivedMessageInfo" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [SnodeReceivedMessageInfo.self] diff --git a/SessionSnodeKit/Database/Migrations/_008_ResetUserConfigLastHashes.swift b/SessionNetworkingKit/Database/Migrations/_008_ResetUserConfigLastHashes.swift similarity index 94% rename from SessionSnodeKit/Database/Migrations/_008_ResetUserConfigLastHashes.swift rename to SessionNetworkingKit/Database/Migrations/_008_ResetUserConfigLastHashes.swift index 468ee6999c..1eb3e6d265 100644 --- a/SessionSnodeKit/Database/Migrations/_008_ResetUserConfigLastHashes.swift +++ b/SessionNetworkingKit/Database/Migrations/_008_ResetUserConfigLastHashes.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit /// This migration resets the `lastHash` value for all user config namespaces to force the app to fetch the latest config /// messages in case there are multi-part config message we had previously seen and failed to merge enum _008_ResetUserConfigLastHashes: Migration { - static let target: TargetMigrations.Identifier = .snodeKit + static let target: TargetMigrations.Identifier = .networkingKit static let identifier: String = "ResetUserConfigLastHashes" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift b/SessionNetworkingKit/Database/Models/SnodeReceivedMessageInfo.swift similarity index 100% rename from SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift rename to SessionNetworkingKit/Database/Models/SnodeReceivedMessageInfo.swift diff --git a/SessionSnodeKit/LibSession/LibSession+Networking.swift b/SessionNetworkingKit/LibSession/LibSession+Networking.swift similarity index 100% rename from SessionSnodeKit/LibSession/LibSession+Networking.swift rename to SessionNetworkingKit/LibSession/LibSession+Networking.swift diff --git a/SessionSnodeKit/Meta/Info.plist b/SessionNetworkingKit/Meta/Info.plist similarity index 100% rename from SessionSnodeKit/Meta/Info.plist rename to SessionNetworkingKit/Meta/Info.plist diff --git a/SessionNetworkingKit/Meta/SessionNetworkingKit.h b/SessionNetworkingKit/Meta/SessionNetworkingKit.h new file mode 100644 index 0000000000..dd9ec08864 --- /dev/null +++ b/SessionNetworkingKit/Meta/SessionNetworkingKit.h @@ -0,0 +1,4 @@ +#import + +FOUNDATION_EXPORT double SessionNetworkingKitVersionNumber; +FOUNDATION_EXPORT const unsigned char SessionNetworkingKitVersionString[]; diff --git a/SessionSnodeKit/Models/AppVersionResponse.swift b/SessionNetworkingKit/Models/AppVersionResponse.swift similarity index 100% rename from SessionSnodeKit/Models/AppVersionResponse.swift rename to SessionNetworkingKit/Models/AppVersionResponse.swift diff --git a/SessionSnodeKit/Models/DeleteAllBeforeRequest.swift b/SessionNetworkingKit/Models/DeleteAllBeforeRequest.swift similarity index 100% rename from SessionSnodeKit/Models/DeleteAllBeforeRequest.swift rename to SessionNetworkingKit/Models/DeleteAllBeforeRequest.swift diff --git a/SessionSnodeKit/Models/DeleteAllBeforeResponse.swift b/SessionNetworkingKit/Models/DeleteAllBeforeResponse.swift similarity index 100% rename from SessionSnodeKit/Models/DeleteAllBeforeResponse.swift rename to SessionNetworkingKit/Models/DeleteAllBeforeResponse.swift diff --git a/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift b/SessionNetworkingKit/Models/DeleteAllMessagesRequest.swift similarity index 100% rename from SessionSnodeKit/Models/DeleteAllMessagesRequest.swift rename to SessionNetworkingKit/Models/DeleteAllMessagesRequest.swift diff --git a/SessionSnodeKit/Models/DeleteAllMessagesResponse.swift b/SessionNetworkingKit/Models/DeleteAllMessagesResponse.swift similarity index 100% rename from SessionSnodeKit/Models/DeleteAllMessagesResponse.swift rename to SessionNetworkingKit/Models/DeleteAllMessagesResponse.swift diff --git a/SessionSnodeKit/Models/DeleteMessagesRequest.swift b/SessionNetworkingKit/Models/DeleteMessagesRequest.swift similarity index 100% rename from SessionSnodeKit/Models/DeleteMessagesRequest.swift rename to SessionNetworkingKit/Models/DeleteMessagesRequest.swift diff --git a/SessionSnodeKit/Models/DeleteMessagesResponse.swift b/SessionNetworkingKit/Models/DeleteMessagesResponse.swift similarity index 100% rename from SessionSnodeKit/Models/DeleteMessagesResponse.swift rename to SessionNetworkingKit/Models/DeleteMessagesResponse.swift diff --git a/SessionSnodeKit/Models/FileUploadResponse.swift b/SessionNetworkingKit/Models/FileUploadResponse.swift similarity index 100% rename from SessionSnodeKit/Models/FileUploadResponse.swift rename to SessionNetworkingKit/Models/FileUploadResponse.swift diff --git a/SessionSnodeKit/Models/GetExpiriesRequest.swift b/SessionNetworkingKit/Models/GetExpiriesRequest.swift similarity index 100% rename from SessionSnodeKit/Models/GetExpiriesRequest.swift rename to SessionNetworkingKit/Models/GetExpiriesRequest.swift diff --git a/SessionSnodeKit/Models/GetExpiriesResponse.swift b/SessionNetworkingKit/Models/GetExpiriesResponse.swift similarity index 100% rename from SessionSnodeKit/Models/GetExpiriesResponse.swift rename to SessionNetworkingKit/Models/GetExpiriesResponse.swift diff --git a/SessionSnodeKit/Models/GetMessagesRequest.swift b/SessionNetworkingKit/Models/GetMessagesRequest.swift similarity index 100% rename from SessionSnodeKit/Models/GetMessagesRequest.swift rename to SessionNetworkingKit/Models/GetMessagesRequest.swift diff --git a/SessionSnodeKit/Models/GetMessagesResponse.swift b/SessionNetworkingKit/Models/GetMessagesResponse.swift similarity index 100% rename from SessionSnodeKit/Models/GetMessagesResponse.swift rename to SessionNetworkingKit/Models/GetMessagesResponse.swift diff --git a/SessionSnodeKit/Models/GetNetworkTimestampResponse.swift b/SessionNetworkingKit/Models/GetNetworkTimestampResponse.swift similarity index 100% rename from SessionSnodeKit/Models/GetNetworkTimestampResponse.swift rename to SessionNetworkingKit/Models/GetNetworkTimestampResponse.swift diff --git a/SessionSnodeKit/Models/LegacyGetMessagesRequest.swift b/SessionNetworkingKit/Models/LegacyGetMessagesRequest.swift similarity index 100% rename from SessionSnodeKit/Models/LegacyGetMessagesRequest.swift rename to SessionNetworkingKit/Models/LegacyGetMessagesRequest.swift diff --git a/SessionSnodeKit/Models/LegacySendMessageRequest.swift b/SessionNetworkingKit/Models/LegacySendMessageRequest.swift similarity index 100% rename from SessionSnodeKit/Models/LegacySendMessageRequest.swift rename to SessionNetworkingKit/Models/LegacySendMessageRequest.swift diff --git a/SessionSnodeKit/Models/ONSResolveRequest.swift b/SessionNetworkingKit/Models/ONSResolveRequest.swift similarity index 100% rename from SessionSnodeKit/Models/ONSResolveRequest.swift rename to SessionNetworkingKit/Models/ONSResolveRequest.swift diff --git a/SessionSnodeKit/Models/ONSResolveResponse.swift b/SessionNetworkingKit/Models/ONSResolveResponse.swift similarity index 100% rename from SessionSnodeKit/Models/ONSResolveResponse.swift rename to SessionNetworkingKit/Models/ONSResolveResponse.swift diff --git a/SessionSnodeKit/Models/OxenDaemonRPCRequest.swift b/SessionNetworkingKit/Models/OxenDaemonRPCRequest.swift similarity index 100% rename from SessionSnodeKit/Models/OxenDaemonRPCRequest.swift rename to SessionNetworkingKit/Models/OxenDaemonRPCRequest.swift diff --git a/SessionSnodeKit/Models/RevokeSubaccountRequest.swift b/SessionNetworkingKit/Models/RevokeSubaccountRequest.swift similarity index 100% rename from SessionSnodeKit/Models/RevokeSubaccountRequest.swift rename to SessionNetworkingKit/Models/RevokeSubaccountRequest.swift diff --git a/SessionSnodeKit/Models/RevokeSubaccountResponse.swift b/SessionNetworkingKit/Models/RevokeSubaccountResponse.swift similarity index 100% rename from SessionSnodeKit/Models/RevokeSubaccountResponse.swift rename to SessionNetworkingKit/Models/RevokeSubaccountResponse.swift diff --git a/SessionSnodeKit/Models/SendMessageRequest.swift b/SessionNetworkingKit/Models/SendMessageRequest.swift similarity index 100% rename from SessionSnodeKit/Models/SendMessageRequest.swift rename to SessionNetworkingKit/Models/SendMessageRequest.swift diff --git a/SessionSnodeKit/Models/SendMessageResponse.swift b/SessionNetworkingKit/Models/SendMessageResponse.swift similarity index 100% rename from SessionSnodeKit/Models/SendMessageResponse.swift rename to SessionNetworkingKit/Models/SendMessageResponse.swift diff --git a/SessionSnodeKit/Models/SnodeAuthenticatedRequestBody.swift b/SessionNetworkingKit/Models/SnodeAuthenticatedRequestBody.swift similarity index 100% rename from SessionSnodeKit/Models/SnodeAuthenticatedRequestBody.swift rename to SessionNetworkingKit/Models/SnodeAuthenticatedRequestBody.swift diff --git a/SessionSnodeKit/Models/SnodeBatchRequest.swift b/SessionNetworkingKit/Models/SnodeBatchRequest.swift similarity index 100% rename from SessionSnodeKit/Models/SnodeBatchRequest.swift rename to SessionNetworkingKit/Models/SnodeBatchRequest.swift diff --git a/SessionSnodeKit/Models/SnodeMessage.swift b/SessionNetworkingKit/Models/SnodeMessage.swift similarity index 100% rename from SessionSnodeKit/Models/SnodeMessage.swift rename to SessionNetworkingKit/Models/SnodeMessage.swift diff --git a/SessionSnodeKit/Models/SnodeReceivedMessage.swift b/SessionNetworkingKit/Models/SnodeReceivedMessage.swift similarity index 100% rename from SessionSnodeKit/Models/SnodeReceivedMessage.swift rename to SessionNetworkingKit/Models/SnodeReceivedMessage.swift diff --git a/SessionSnodeKit/Models/SnodeRecursiveResponse.swift b/SessionNetworkingKit/Models/SnodeRecursiveResponse.swift similarity index 100% rename from SessionSnodeKit/Models/SnodeRecursiveResponse.swift rename to SessionNetworkingKit/Models/SnodeRecursiveResponse.swift diff --git a/SessionSnodeKit/Models/SnodeRequest.swift b/SessionNetworkingKit/Models/SnodeRequest.swift similarity index 100% rename from SessionSnodeKit/Models/SnodeRequest.swift rename to SessionNetworkingKit/Models/SnodeRequest.swift diff --git a/SessionSnodeKit/Models/SnodeResponse.swift b/SessionNetworkingKit/Models/SnodeResponse.swift similarity index 100% rename from SessionSnodeKit/Models/SnodeResponse.swift rename to SessionNetworkingKit/Models/SnodeResponse.swift diff --git a/SessionSnodeKit/Models/SnodeSwarmItem.swift b/SessionNetworkingKit/Models/SnodeSwarmItem.swift similarity index 100% rename from SessionSnodeKit/Models/SnodeSwarmItem.swift rename to SessionNetworkingKit/Models/SnodeSwarmItem.swift diff --git a/SessionSnodeKit/Models/UnrevokeSubaccountRequest.swift b/SessionNetworkingKit/Models/UnrevokeSubaccountRequest.swift similarity index 100% rename from SessionSnodeKit/Models/UnrevokeSubaccountRequest.swift rename to SessionNetworkingKit/Models/UnrevokeSubaccountRequest.swift diff --git a/SessionSnodeKit/Models/UnrevokeSubaccountResponse.swift b/SessionNetworkingKit/Models/UnrevokeSubaccountResponse.swift similarity index 100% rename from SessionSnodeKit/Models/UnrevokeSubaccountResponse.swift rename to SessionNetworkingKit/Models/UnrevokeSubaccountResponse.swift diff --git a/SessionSnodeKit/Models/UpdateExpiryAllRequest.swift b/SessionNetworkingKit/Models/UpdateExpiryAllRequest.swift similarity index 100% rename from SessionSnodeKit/Models/UpdateExpiryAllRequest.swift rename to SessionNetworkingKit/Models/UpdateExpiryAllRequest.swift diff --git a/SessionSnodeKit/Models/UpdateExpiryAllResponse.swift b/SessionNetworkingKit/Models/UpdateExpiryAllResponse.swift similarity index 100% rename from SessionSnodeKit/Models/UpdateExpiryAllResponse.swift rename to SessionNetworkingKit/Models/UpdateExpiryAllResponse.swift diff --git a/SessionSnodeKit/Models/UpdateExpiryRequest.swift b/SessionNetworkingKit/Models/UpdateExpiryRequest.swift similarity index 100% rename from SessionSnodeKit/Models/UpdateExpiryRequest.swift rename to SessionNetworkingKit/Models/UpdateExpiryRequest.swift diff --git a/SessionSnodeKit/Models/UpdateExpiryResponse.swift b/SessionNetworkingKit/Models/UpdateExpiryResponse.swift similarity index 100% rename from SessionSnodeKit/Models/UpdateExpiryResponse.swift rename to SessionNetworkingKit/Models/UpdateExpiryResponse.swift diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionNetworkingKit/Networking/SnodeAPI.swift similarity index 100% rename from SessionSnodeKit/Networking/SnodeAPI.swift rename to SessionNetworkingKit/Networking/SnodeAPI.swift diff --git a/SessionSnodeKit/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift b/SessionNetworkingKit/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift similarity index 100% rename from SessionSnodeKit/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift rename to SessionNetworkingKit/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift diff --git a/SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Database.swift b/SessionNetworkingKit/SessionNetworkAPI/SessionNetworkAPI+Database.swift similarity index 100% rename from SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Database.swift rename to SessionNetworkingKit/SessionNetworkAPI/SessionNetworkAPI+Database.swift diff --git a/SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Models.swift b/SessionNetworkingKit/SessionNetworkAPI/SessionNetworkAPI+Models.swift similarity index 100% rename from SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Models.swift rename to SessionNetworkingKit/SessionNetworkAPI/SessionNetworkAPI+Models.swift diff --git a/SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Network.swift b/SessionNetworkingKit/SessionNetworkAPI/SessionNetworkAPI+Network.swift similarity index 96% rename from SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Network.swift rename to SessionNetworkingKit/SessionNetworkAPI/SessionNetworkAPI+Network.swift index fb26688ac1..79feb74a36 100644 --- a/SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Network.swift +++ b/SessionNetworkingKit/SessionNetworkAPI/SessionNetworkAPI+Network.swift @@ -21,7 +21,7 @@ extension SessionNetworkAPI { public func initialize(using dependencies: Dependencies) { self.dependencies = dependencies cancellable = getInfo(using: dependencies) - .subscribe(on: Threading.workQueue, using: dependencies) + .subscribe(on: SessionNetworkAPI.workQueue, using: dependencies) .receive(on: SessionNetworkAPI.workQueue) .sink(receiveCompletion: { _ in }, receiveValue: { _ in }) } @@ -32,7 +32,7 @@ extension SessionNetworkAPI { let staleTimestampMs: Int64 = dependencies[singleton: .storage].read { db in db[.staleTimestampMs] }.defaulting(to: 0) guard staleTimestampMs < dependencies[cache: .snodeAPI].currentOffsetTimestampMs() else { return Just(()) - .delay(for: .milliseconds(500), scheduler: Threading.workQueue) + .delay(for: .milliseconds(500), scheduler: SessionNetworkAPI.workQueue) .setFailureType(to: Error.self) .flatMapStorageWritePublisher(using: dependencies) { [dependencies] db, info -> Bool in db[.lastUpdatedTimestampMs] = dependencies[cache: .snodeAPI].currentOffsetTimestampMs() diff --git a/SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI.swift b/SessionNetworkingKit/SessionNetworkAPI/SessionNetworkAPI.swift similarity index 100% rename from SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI.swift rename to SessionNetworkingKit/SessionNetworkAPI/SessionNetworkAPI.swift diff --git a/SessionSnodeKit/SnodeAPI/Request+SnodeAPI.swift b/SessionNetworkingKit/SnodeAPI/Request+SnodeAPI.swift similarity index 100% rename from SessionSnodeKit/SnodeAPI/Request+SnodeAPI.swift rename to SessionNetworkingKit/SnodeAPI/Request+SnodeAPI.swift diff --git a/SessionSnodeKit/SnodeAPI/ResponseInfo+SnodeAPI.swift b/SessionNetworkingKit/SnodeAPI/ResponseInfo+SnodeAPI.swift similarity index 100% rename from SessionSnodeKit/SnodeAPI/ResponseInfo+SnodeAPI.swift rename to SessionNetworkingKit/SnodeAPI/ResponseInfo+SnodeAPI.swift diff --git a/SessionSnodeKit/SnodeAPI/SnodeAPI.swift b/SessionNetworkingKit/SnodeAPI/SnodeAPI.swift similarity index 100% rename from SessionSnodeKit/SnodeAPI/SnodeAPI.swift rename to SessionNetworkingKit/SnodeAPI/SnodeAPI.swift diff --git a/SessionSnodeKit/SnodeAPI/SnodeAPIEndpoint.swift b/SessionNetworkingKit/SnodeAPI/SnodeAPIEndpoint.swift similarity index 100% rename from SessionSnodeKit/SnodeAPI/SnodeAPIEndpoint.swift rename to SessionNetworkingKit/SnodeAPI/SnodeAPIEndpoint.swift diff --git a/SessionSnodeKit/SnodeAPI/SnodeAPIError.swift b/SessionNetworkingKit/SnodeAPI/SnodeAPIError.swift similarity index 100% rename from SessionSnodeKit/SnodeAPI/SnodeAPIError.swift rename to SessionNetworkingKit/SnodeAPI/SnodeAPIError.swift diff --git a/SessionSnodeKit/SnodeAPI/SnodeAPINamespace.swift b/SessionNetworkingKit/SnodeAPI/SnodeAPINamespace.swift similarity index 100% rename from SessionSnodeKit/SnodeAPI/SnodeAPINamespace.swift rename to SessionNetworkingKit/SnodeAPI/SnodeAPINamespace.swift diff --git a/SessionSnodeKit/Types/BatchRequest.swift b/SessionNetworkingKit/Types/BatchRequest.swift similarity index 100% rename from SessionSnodeKit/Types/BatchRequest.swift rename to SessionNetworkingKit/Types/BatchRequest.swift diff --git a/SessionSnodeKit/Types/BatchResponse.swift b/SessionNetworkingKit/Types/BatchResponse.swift similarity index 100% rename from SessionSnodeKit/Types/BatchResponse.swift rename to SessionNetworkingKit/Types/BatchResponse.swift diff --git a/SessionSnodeKit/Types/BencodeResponse.swift b/SessionNetworkingKit/Types/BencodeResponse.swift similarity index 100% rename from SessionSnodeKit/Types/BencodeResponse.swift rename to SessionNetworkingKit/Types/BencodeResponse.swift diff --git a/SessionSnodeKit/Types/ContentProxy.swift b/SessionNetworkingKit/Types/ContentProxy.swift similarity index 100% rename from SessionSnodeKit/Types/ContentProxy.swift rename to SessionNetworkingKit/Types/ContentProxy.swift diff --git a/SessionSnodeKit/Types/Destination.swift b/SessionNetworkingKit/Types/Destination.swift similarity index 100% rename from SessionSnodeKit/Types/Destination.swift rename to SessionNetworkingKit/Types/Destination.swift diff --git a/SessionSnodeKit/Types/HTTPHeader.swift b/SessionNetworkingKit/Types/HTTPHeader.swift similarity index 100% rename from SessionSnodeKit/Types/HTTPHeader.swift rename to SessionNetworkingKit/Types/HTTPHeader.swift diff --git a/SessionSnodeKit/Types/HTTPMethod.swift b/SessionNetworkingKit/Types/HTTPMethod.swift similarity index 100% rename from SessionSnodeKit/Types/HTTPMethod.swift rename to SessionNetworkingKit/Types/HTTPMethod.swift diff --git a/SessionSnodeKit/Types/HTTPQueryParam.swift b/SessionNetworkingKit/Types/HTTPQueryParam.swift similarity index 100% rename from SessionSnodeKit/Types/HTTPQueryParam.swift rename to SessionNetworkingKit/Types/HTTPQueryParam.swift diff --git a/SessionSnodeKit/Types/IPv4.swift b/SessionNetworkingKit/Types/IPv4.swift similarity index 100% rename from SessionSnodeKit/Types/IPv4.swift rename to SessionNetworkingKit/Types/IPv4.swift diff --git a/SessionSnodeKit/Types/JSON.swift b/SessionNetworkingKit/Types/JSON.swift similarity index 100% rename from SessionSnodeKit/Types/JSON.swift rename to SessionNetworkingKit/Types/JSON.swift diff --git a/SessionSnodeKit/Types/Network.swift b/SessionNetworkingKit/Types/Network.swift similarity index 100% rename from SessionSnodeKit/Types/Network.swift rename to SessionNetworkingKit/Types/Network.swift diff --git a/SessionSnodeKit/Types/NetworkError.swift b/SessionNetworkingKit/Types/NetworkError.swift similarity index 100% rename from SessionSnodeKit/Types/NetworkError.swift rename to SessionNetworkingKit/Types/NetworkError.swift diff --git a/SessionSnodeKit/Types/PreparedRequest+Sending.swift b/SessionNetworkingKit/Types/PreparedRequest+Sending.swift similarity index 100% rename from SessionSnodeKit/Types/PreparedRequest+Sending.swift rename to SessionNetworkingKit/Types/PreparedRequest+Sending.swift diff --git a/SessionSnodeKit/Types/PreparedRequest.swift b/SessionNetworkingKit/Types/PreparedRequest.swift similarity index 100% rename from SessionSnodeKit/Types/PreparedRequest.swift rename to SessionNetworkingKit/Types/PreparedRequest.swift diff --git a/SessionSnodeKit/Types/ProxiedContentDownloader.swift b/SessionNetworkingKit/Types/ProxiedContentDownloader.swift similarity index 100% rename from SessionSnodeKit/Types/ProxiedContentDownloader.swift rename to SessionNetworkingKit/Types/ProxiedContentDownloader.swift diff --git a/SessionSnodeKit/Types/Request.swift b/SessionNetworkingKit/Types/Request.swift similarity index 100% rename from SessionSnodeKit/Types/Request.swift rename to SessionNetworkingKit/Types/Request.swift diff --git a/SessionNetworkingKit/Types/RequestCategory.swift b/SessionNetworkingKit/Types/RequestCategory.swift new file mode 100644 index 0000000000..e69de29bb2 diff --git a/SessionSnodeKit/Types/ResponseInfo.swift b/SessionNetworkingKit/Types/ResponseInfo.swift similarity index 100% rename from SessionSnodeKit/Types/ResponseInfo.swift rename to SessionNetworkingKit/Types/ResponseInfo.swift diff --git a/SessionNetworkingKit/Types/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift b/SessionNetworkingKit/Types/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift new file mode 100644 index 0000000000..464a3b1b9e --- /dev/null +++ b/SessionNetworkingKit/Types/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift @@ -0,0 +1,9 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +public extension HTTPHeader { + static let tokenServerPubKey: HTTPHeader = "X-FS-Pubkey" + static let tokenServerTimestamp: HTTPHeader = "X-FS-Timestamp" + static let tokenServerSignature: HTTPHeader = "X-FS-Signature" +} diff --git a/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Database.swift b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Database.swift new file mode 100644 index 0000000000..9316b1a440 --- /dev/null +++ b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Database.swift @@ -0,0 +1,32 @@ +// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import AudioToolbox +import GRDB +import DifferenceKit +import SessionUtilitiesKit + +public extension KeyValueStore.StringKey { + static let contractAddress: KeyValueStore.StringKey = "contractAddress" +} + +public extension KeyValueStore.DoubleKey { + static let tokenUsd: KeyValueStore.DoubleKey = "tokenUsd" + static let marketCapUsd: KeyValueStore.DoubleKey = "marketCapUsd" + static let stakingRequirement: KeyValueStore.DoubleKey = "stakingRequirement" + static let stakingRewardPool: KeyValueStore.DoubleKey = "stakingRewardPool" + static let networkStakedTokens: KeyValueStore.DoubleKey = "networkStakedTokens" + static let networkStakedUSD: KeyValueStore.DoubleKey = "networkStakedUSD" +} + +public extension KeyValueStore.IntKey { + static let networkSize: KeyValueStore.IntKey = "networkSize" +} + +public extension KeyValueStore.Int64Key { + static let lastUpdatedTimestampMs: KeyValueStore.Int64Key = "lastUpdatedTimestampMs" + static let staleTimestampMs: KeyValueStore.Int64Key = "staleTimestampMs" + static let priceTimestampMs: KeyValueStore.Int64Key = "priceTimestampMs" +} diff --git a/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Models.swift b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Models.swift new file mode 100644 index 0000000000..cd1cfec84c --- /dev/null +++ b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Models.swift @@ -0,0 +1,75 @@ +// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import Combine +import GRDB +import SessionUtilitiesKit + +extension SessionNetworkAPI { + + // MARK: - Price + + public struct Price: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case tokenUsd = "usd" + case marketCapUsd = "usd_market_cap" + case priceTimestamp = "t_price" + case staleTimestamp = "t_stale" + } + + public let tokenUsd: Double? // Current token price (USD) + public let marketCapUsd: Double? // Current market cap value in (USD) + public let priceTimestamp: Int64? // The timestamp the price data is accurate at. (seconds) + public let staleTimestamp: Int64? // Stale timestamp for the price data. (seconds) + } + + // MARK: - Token + + public struct Token: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case stakingRequirement = "staking_requirement" + case stakingRewardPool = "staking_reward_pool" + case contractAddress = "contract_address" + } + + public let stakingRequirement: Double? // The number of tokens required to stake a node. This is the effective "token amount" per node (SESH) + public let stakingRewardPool: Double? // The number of tokens in the staking reward pool (SESH) + public let contractAddress: String? // Token contract address (42 char Hexadecimal - Including 0x prefix) + } + + + // MARK: - Network Info + + public struct NetworkInfo: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case networkSize = "network_size" // The number of nodes in the Session Network (integer) + case networkStakedTokens = "network_staked_tokens" // + case networkStakedUSD = "network_staked_usd" // + } + + public let networkSize: Int? + public let networkStakedTokens: Double? + public let networkStakedUSD: Double? + } + + // MARK: - Info + + public struct Info: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case timestamp = "t" + case statusCode = "status_code" + case price + case token + case network + } + + public let timestamp: Int64? // Request timestamp. (seconds) + public let statusCode: Int? // Status code of the request. + public let price: Price? + public let token: Token? + public let network: NetworkInfo? + } +} + diff --git a/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Network.swift b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Network.swift new file mode 100644 index 0000000000..947e55d15d --- /dev/null +++ b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Network.swift @@ -0,0 +1,107 @@ +// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import Combine +import GRDB +import SessionUtilitiesKit + +// MARK: - Log.Category + +public extension Log.Category { + static let sessionNetwork: Log.Category = .create("SessionNetwork", defaultLevel: .info) +} + +extension SessionNetworkAPI { + public final class HTTPClient { + private var cancellable: AnyCancellable? + private var dependencies: Dependencies? + + public func initialize(using dependencies: Dependencies) { + self.dependencies = dependencies + cancellable = getInfo(using: dependencies) + .subscribe(on: Threading.workQueue, using: dependencies) + .receive(on: SessionNetworkAPI.workQueue) + .sink(receiveCompletion: { _ in }, receiveValue: { _ in }) + } + + public func getInfo(using dependencies: Dependencies) -> AnyPublisher { + cancellable?.cancel() + + let staleTimestampMs: Int64 = dependencies[singleton: .storage].read { db in db[.staleTimestampMs] }.defaulting(to: 0) + guard staleTimestampMs < dependencies[cache: .snodeAPI].currentOffsetTimestampMs() else { + return Just(()) + .delay(for: .milliseconds(500), scheduler: Threading.workQueue) + .setFailureType(to: Error.self) + .flatMapStorageWritePublisher(using: dependencies) { [dependencies] db, info -> Bool in + db[.lastUpdatedTimestampMs] = dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + return true + } + .eraseToAnyPublisher() + } + + return dependencies[singleton: .storage] + .readPublisher { db -> Network.PreparedRequest in + try SessionNetworkAPI + .prepareInfo( + db, + using: dependencies + ) + } + .flatMap { $0.send(using: dependencies) } + .map { _, info in info } + .flatMapStorageWritePublisher(using: dependencies) { [dependencies] db, info -> Bool in + // Token info + db[.lastUpdatedTimestampMs] = dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + db[.tokenUsd] = info.price?.tokenUsd + db[.marketCapUsd] = info.price?.marketCapUsd + if let priceTimestamp = info.price?.priceTimestamp { + db[.priceTimestampMs] = priceTimestamp * 1000 + } else { + db[.priceTimestampMs] = nil + } + if let staleTimestamp = info.price?.staleTimestamp { + db[.staleTimestampMs] = staleTimestamp * 1000 + } else { + db[.staleTimestampMs] = nil + } + db[.stakingRequirement] = info.token?.stakingRequirement + db[.stakingRewardPool] = info.token?.stakingRewardPool + db[.contractAddress] = info.token?.contractAddress + // Network info + db[.networkSize] = info.network?.networkSize + db[.networkStakedTokens] = info.network?.networkStakedTokens + db[.networkStakedUSD] = info.network?.networkStakedUSD + + return true + } + .catch { error -> AnyPublisher in + Log.error(.sessionNetwork, "Failed to fetch token info due to error: \(error).") + return self.cleanUpSessionNetworkPageData(using: dependencies) + .map { _ in false } + .eraseToAnyPublisher() + + } + .eraseToAnyPublisher() + } + + private func cleanUpSessionNetworkPageData(using dependencies: Dependencies) -> AnyPublisher { + dependencies[singleton: .storage].writePublisher { db in + // Token info + db[.lastUpdatedTimestampMs] = nil + db[.tokenUsd] = nil + db[.marketCapUsd] = nil + db[.priceTimestampMs] = nil + db[.staleTimestampMs] = nil + db[.stakingRequirement] = nil + db[.stakingRewardPool] = nil + db[.contractAddress] = nil + // Network info + db[.networkSize] = nil + db[.networkStakedTokens] = nil + db[.networkStakedUSD] = nil + } + } + } +} diff --git a/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI.swift b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI.swift new file mode 100644 index 0000000000..1d1891a51d --- /dev/null +++ b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI.swift @@ -0,0 +1,131 @@ +// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import Combine +import GRDB +import SessionUtilitiesKit + +public enum SessionNetworkAPI { + public static let workQueue = DispatchQueue(label: "SessionNetworkAPI.workQueue", qos: .userInitiated) + public static let client = HTTPClient() + + // MARK: - Info + + /// General token info. This endpoint combines the `/price` and `/token` endpoint information. + /// + /// `GET/info` + + public static func prepareInfo( + _ db: Database, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try Network.PreparedRequest( + request: Request( + endpoint: Network.NetworkAPI.Endpoint.info, + destination: .server( + method: .get, + server: Network.NetworkAPI.networkAPIServer, + queryParameters: [:], + x25519PublicKey: Network.NetworkAPI.networkAPIServerPublicKey + ) + ), + responseType: Info.self, + requestAndPathBuildTimeout: Network.defaultTimeout, + using: dependencies + ) + .signed(db, with: SessionNetworkAPI.signRequest, using: dependencies) + } + + // MARK: - Authentication + + fileprivate static func signatureHeaders( + _ db: Database, + url: URL, + method: HTTPMethod, + body: Data?, + using dependencies: Dependencies + ) throws -> [HTTPHeader: String] { + let timestamp: UInt64 = UInt64(floor(dependencies.dateNow.timeIntervalSince1970)) + let path: String = url.path + .appending(url.query.map { value in "?\(value)" }) + + let signResult: (publicKey: String, signature: [UInt8]) = try sign( + db, + timestamp: timestamp, + method: method.rawValue, + path: path, + body: body, + using: dependencies + ) + + return [ + HTTPHeader.tokenServerPubKey: signResult.publicKey, + HTTPHeader.tokenServerTimestamp: "\(timestamp)", + HTTPHeader.tokenServerSignature: signResult.signature.toBase64() + ] + } + + private static func sign( + _ db: Database, + timestamp: UInt64, + method: String, + path: String, + body: Data?, + using dependencies: Dependencies + ) throws -> (publicKey: String, signature: [UInt8]) { + let bodyString: String? = { + guard let bodyData: Data = body else { return nil } + return String(data: bodyData, encoding: .utf8) + }() + + guard + let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db), + let blinded07KeyPair: KeyPair = dependencies[singleton: .crypto].generate( + .versionBlinded07KeyPair(ed25519SecretKey: userEdKeyPair.secretKey) + ), + let signatureResult: [UInt8] = dependencies[singleton: .crypto].generate( + .signatureVersionBlind07( + timestamp: timestamp, + method: method, + path: path, + body: bodyString, + ed25519SecretKey: userEdKeyPair.secretKey + ) + ) + else { throw NetworkError.signingFailed } + + return ( + publicKey: SessionId(.versionBlinded07, publicKey: blinded07KeyPair.publicKey).hexString, + signature: signatureResult + ) + } + + private static func signRequest( + _ db: Database, + preparedRequest: Network.PreparedRequest, + using dependencies: Dependencies + ) throws -> Network.Destination { + guard let url: URL = preparedRequest.destination.url else { + throw NetworkError.signingFailed + } + + guard case let .server(info) = preparedRequest.destination else { + throw NetworkError.signingFailed + } + + return .server( + info: info.updated( + with: try signatureHeaders( + db, + url: url, + method: preparedRequest.method, + body: preparedRequest.body, + using: dependencies + ) + ) + ) + } +} + diff --git a/SessionSnodeKit/Types/SwarmDrainBehaviour.swift b/SessionNetworkingKit/Types/SwarmDrainBehaviour.swift similarity index 100% rename from SessionSnodeKit/Types/SwarmDrainBehaviour.swift rename to SessionNetworkingKit/Types/SwarmDrainBehaviour.swift diff --git a/SessionSnodeKit/Types/UpdatableTimestamp.swift b/SessionNetworkingKit/Types/UpdatableTimestamp.swift similarity index 100% rename from SessionSnodeKit/Types/UpdatableTimestamp.swift rename to SessionNetworkingKit/Types/UpdatableTimestamp.swift diff --git a/SessionSnodeKit/Types/ValidatableResponse.swift b/SessionNetworkingKit/Types/ValidatableResponse.swift similarity index 100% rename from SessionSnodeKit/Types/ValidatableResponse.swift rename to SessionNetworkingKit/Types/ValidatableResponse.swift diff --git a/SessionSnodeKit/Utilities/Data+Utilities.swift b/SessionNetworkingKit/Utilities/Data+Utilities.swift similarity index 100% rename from SessionSnodeKit/Utilities/Data+Utilities.swift rename to SessionNetworkingKit/Utilities/Data+Utilities.swift diff --git a/SessionSnodeKit/Utilities/Publisher+Utilities.swift b/SessionNetworkingKit/Utilities/Publisher+Utilities.swift similarity index 100% rename from SessionSnodeKit/Utilities/Publisher+Utilities.swift rename to SessionNetworkingKit/Utilities/Publisher+Utilities.swift diff --git a/SessionSnodeKit/Utilities/RetryWithDependencies.swift b/SessionNetworkingKit/Utilities/RetryWithDependencies.swift similarity index 100% rename from SessionSnodeKit/Utilities/RetryWithDependencies.swift rename to SessionNetworkingKit/Utilities/RetryWithDependencies.swift diff --git a/SessionSnodeKit/Utilities/String+Trimming.swift b/SessionNetworkingKit/Utilities/String+Trimming.swift similarity index 100% rename from SessionSnodeKit/Utilities/String+Trimming.swift rename to SessionNetworkingKit/Utilities/String+Trimming.swift diff --git a/SessionSnodeKit/Utilities/URLResponse+Utilities.swift b/SessionNetworkingKit/Utilities/URLResponse+Utilities.swift similarity index 100% rename from SessionSnodeKit/Utilities/URLResponse+Utilities.swift rename to SessionNetworkingKit/Utilities/URLResponse+Utilities.swift diff --git a/SessionSnodeKitTests/Models/FileUploadResponseSpec.swift b/SessionNetworkingKitTests/Models/FileUploadResponseSpec.swift similarity index 96% rename from SessionSnodeKitTests/Models/FileUploadResponseSpec.swift rename to SessionNetworkingKitTests/Models/FileUploadResponseSpec.swift index 1454fbe383..4339984b89 100644 --- a/SessionSnodeKitTests/Models/FileUploadResponseSpec.swift +++ b/SessionNetworkingKitTests/Models/FileUploadResponseSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class FileUploadResponseSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Models/SnodeRequestSpec.swift b/SessionNetworkingKitTests/Models/SnodeRequestSpec.swift similarity index 98% rename from SessionSnodeKitTests/Models/SnodeRequestSpec.swift rename to SessionNetworkingKitTests/Models/SnodeRequestSpec.swift index 3d6836709b..0405d71e7c 100644 --- a/SessionSnodeKitTests/Models/SnodeRequestSpec.swift +++ b/SessionNetworkingKitTests/Models/SnodeRequestSpec.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class SnodeRequestSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/SessionSnodeKit.xctestplan b/SessionNetworkingKitTests/SessionNetworkingKit.xctestplan similarity index 90% rename from SessionSnodeKitTests/SessionSnodeKit.xctestplan rename to SessionNetworkingKitTests/SessionNetworkingKit.xctestplan index ea699b6efd..52ef80ee0c 100644 --- a/SessionSnodeKitTests/SessionSnodeKit.xctestplan +++ b/SessionNetworkingKitTests/SessionNetworkingKit.xctestplan @@ -17,7 +17,7 @@ "target" : { "containerPath" : "container:Session.xcodeproj", "identifier" : "FDB5DAF92A981C42002C8721", - "name" : "SessionSnodeKitTests" + "name" : "SessionNetworkingKitTests" } } ], diff --git a/SessionSnodeKitTests/Types/BatchRequestSpec.swift b/SessionNetworkingKitTests/Types/BatchRequestSpec.swift similarity index 99% rename from SessionSnodeKitTests/Types/BatchRequestSpec.swift rename to SessionNetworkingKitTests/Types/BatchRequestSpec.swift index 36b33bd18a..05554a2be1 100644 --- a/SessionSnodeKitTests/Types/BatchRequestSpec.swift +++ b/SessionNetworkingKitTests/Types/BatchRequestSpec.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class BatchRequestSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/BatchResponseSpec.swift b/SessionNetworkingKitTests/Types/BatchResponseSpec.swift similarity index 99% rename from SessionSnodeKitTests/Types/BatchResponseSpec.swift rename to SessionNetworkingKitTests/Types/BatchResponseSpec.swift index 3fe9ea5575..eac736ac8b 100644 --- a/SessionSnodeKitTests/Types/BatchResponseSpec.swift +++ b/SessionNetworkingKitTests/Types/BatchResponseSpec.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class BatchResponseSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/BencodeResponseSpec.swift b/SessionNetworkingKitTests/Types/BencodeResponseSpec.swift similarity index 99% rename from SessionSnodeKitTests/Types/BencodeResponseSpec.swift rename to SessionNetworkingKitTests/Types/BencodeResponseSpec.swift index e0e6add5f9..0bd213f4ec 100644 --- a/SessionSnodeKitTests/Types/BencodeResponseSpec.swift +++ b/SessionNetworkingKitTests/Types/BencodeResponseSpec.swift @@ -6,7 +6,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class BencodeResponseSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/DestinationSpec.swift b/SessionNetworkingKitTests/Types/DestinationSpec.swift similarity index 98% rename from SessionSnodeKitTests/Types/DestinationSpec.swift rename to SessionNetworkingKitTests/Types/DestinationSpec.swift index 6e17460ac5..e226a25f21 100644 --- a/SessionSnodeKitTests/Types/DestinationSpec.swift +++ b/SessionNetworkingKitTests/Types/DestinationSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class DestinationSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/HeaderSpec.swift b/SessionNetworkingKitTests/Types/HeaderSpec.swift similarity index 93% rename from SessionSnodeKitTests/Types/HeaderSpec.swift rename to SessionNetworkingKitTests/Types/HeaderSpec.swift index ef25b0c5a7..9f73a41944 100644 --- a/SessionSnodeKitTests/Types/HeaderSpec.swift +++ b/SessionNetworkingKitTests/Types/HeaderSpec.swift @@ -6,7 +6,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class HeaderSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/PreparedRequestSendingSpec.swift b/SessionNetworkingKitTests/Types/PreparedRequestSendingSpec.swift similarity index 99% rename from SessionSnodeKitTests/Types/PreparedRequestSendingSpec.swift rename to SessionNetworkingKitTests/Types/PreparedRequestSendingSpec.swift index a75966aa6b..a1ad6f438b 100644 --- a/SessionSnodeKitTests/Types/PreparedRequestSendingSpec.swift +++ b/SessionNetworkingKitTests/Types/PreparedRequestSendingSpec.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class PreparedRequestSendingSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/PreparedRequestSpec.swift b/SessionNetworkingKitTests/Types/PreparedRequestSpec.swift similarity index 99% rename from SessionSnodeKitTests/Types/PreparedRequestSpec.swift rename to SessionNetworkingKitTests/Types/PreparedRequestSpec.swift index 7ba2a676ac..7b949b5fde 100644 --- a/SessionSnodeKitTests/Types/PreparedRequestSpec.swift +++ b/SessionNetworkingKitTests/Types/PreparedRequestSpec.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class PreparedRequestSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/RequestSpec.swift b/SessionNetworkingKitTests/Types/RequestSpec.swift similarity index 99% rename from SessionSnodeKitTests/Types/RequestSpec.swift rename to SessionNetworkingKitTests/Types/RequestSpec.swift index 9888d30083..0b951cf1d3 100644 --- a/SessionSnodeKitTests/Types/RequestSpec.swift +++ b/SessionNetworkingKitTests/Types/RequestSpec.swift @@ -6,7 +6,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class RequestSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/_TestUtilities/CommonSSKMockExtensions.swift b/SessionNetworkingKitTests/_TestUtilities/CommonSSKMockExtensions.swift similarity index 96% rename from SessionSnodeKitTests/_TestUtilities/CommonSSKMockExtensions.swift rename to SessionNetworkingKitTests/_TestUtilities/CommonSSKMockExtensions.swift index 85e05a67af..d99134ceba 100644 --- a/SessionSnodeKitTests/_TestUtilities/CommonSSKMockExtensions.swift +++ b/SessionNetworkingKitTests/_TestUtilities/CommonSSKMockExtensions.swift @@ -2,7 +2,7 @@ import Foundation -@testable import SessionSnodeKit +@testable import SessionNetworkingKit extension NoResponse: Mocked { static var mock: NoResponse = NoResponse() diff --git a/SessionSnodeKitTests/_TestUtilities/MockNetwork.swift b/SessionNetworkingKitTests/_TestUtilities/MockNetwork.swift similarity index 99% rename from SessionSnodeKitTests/_TestUtilities/MockNetwork.swift rename to SessionNetworkingKitTests/_TestUtilities/MockNetwork.swift index efecb6d9ac..1412423914 100644 --- a/SessionSnodeKitTests/_TestUtilities/MockNetwork.swift +++ b/SessionNetworkingKitTests/_TestUtilities/MockNetwork.swift @@ -4,7 +4,7 @@ import Foundation import Combine import SessionUtilitiesKit -@testable import SessionSnodeKit +@testable import SessionNetworkingKit // MARK: - MockNetwork diff --git a/SessionSnodeKitTests/_TestUtilities/MockSnodeAPICache.swift b/SessionNetworkingKitTests/_TestUtilities/MockSnodeAPICache.swift similarity index 97% rename from SessionSnodeKitTests/_TestUtilities/MockSnodeAPICache.swift rename to SessionNetworkingKitTests/_TestUtilities/MockSnodeAPICache.swift index 75111962b8..90be7933ed 100644 --- a/SessionSnodeKitTests/_TestUtilities/MockSnodeAPICache.swift +++ b/SessionNetworkingKitTests/_TestUtilities/MockSnodeAPICache.swift @@ -6,7 +6,7 @@ import Foundation import Combine import SessionUtilitiesKit -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class MockSnodeAPICache: Mock, SnodeAPICacheType { var hardfork: Int { diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 70d2273560..c2b842fc1b 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -5,7 +5,7 @@ import CallKit import UserNotifications import SessionUIKit import SessionMessagingKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category diff --git a/SessionShareExtension/ShareNavController.swift b/SessionShareExtension/ShareNavController.swift index 475489dd60..e977151317 100644 --- a/SessionShareExtension/ShareNavController.swift +++ b/SessionShareExtension/ShareNavController.swift @@ -7,7 +7,7 @@ import CoreServices import UniformTypeIdentifiers import SignalUtilitiesKit import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import SessionMessagingKit diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index 34246413a9..b460349b73 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -8,7 +8,7 @@ import DifferenceKit import SessionUIKit import SignalUtilitiesKit import SessionMessagingKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableViewDelegate, AttachmentApprovalViewControllerDelegate, ThemedNavigation { diff --git a/SessionSnodeKit/Meta/SessionSnodeKit.h b/SessionSnodeKit/Meta/SessionSnodeKit.h deleted file mode 100644 index 698aa516fd..0000000000 --- a/SessionSnodeKit/Meta/SessionSnodeKit.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -FOUNDATION_EXPORT double SessionSnodeKitVersionNumber; -FOUNDATION_EXPORT const unsigned char SessionSnodeKitVersionString[]; diff --git a/SessionSnodeKit/Utilities/Threading+SSK.swift b/SessionSnodeKit/Utilities/Threading+SSK.swift deleted file mode 100644 index 3424ad438e..0000000000 --- a/SessionSnodeKit/Utilities/Threading+SSK.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation -import SessionUtilitiesKit - -public extension Threading { - static let workQueue = DispatchQueue(label: "SessionSnodeKit.workQueue", qos: .userInitiated) // It's important that this is a serial queue -} diff --git a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift index dcf2b6ea26..7028b12544 100644 --- a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift @@ -5,7 +5,7 @@ import GRDB import Quick import Nimble import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit @@ -23,7 +23,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: AsyncSpec { customWriter: try! DatabaseQueue(), migrationTargets: [ SNUtilitiesKit.self, - SNSnodeKit.self, + SNNetworkingKit.self, SNMessagingKit.self, DeprecatedUIKitMigrationTarget.self ], diff --git a/SessionTests/Conversations/Settings/ThreadNotificationSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadNotificationSettingsViewModelSpec.swift index b339cbe090..227c06bf86 100644 --- a/SessionTests/Conversations/Settings/ThreadNotificationSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadNotificationSettingsViewModelSpec.swift @@ -5,7 +5,7 @@ import GRDB import Quick import Nimble import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit @@ -23,7 +23,7 @@ class ThreadNotificationSettingsViewModelSpec: AsyncSpec { customWriter: try! DatabaseQueue(), migrationTargets: [ SNUtilitiesKit.self, - SNSnodeKit.self, + SNNetworkingKit.self, SNMessagingKit.self, DeprecatedUIKitMigrationTarget.self ], diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index 5f9fdb51ac..ec9d79c0a2 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -5,7 +5,7 @@ import GRDB import Quick import Nimble import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit @testable import SessionUIKit @@ -31,7 +31,7 @@ class ThreadSettingsViewModelSpec: AsyncSpec { customWriter: try! DatabaseQueue(), migrationTargets: [ SNUtilitiesKit.self, - SNSnodeKit.self, + SNNetworkingKit.self, SNMessagingKit.self, DeprecatedUIKitMigrationTarget.self ], diff --git a/SessionTests/Database/DatabaseSpec.swift b/SessionTests/Database/DatabaseSpec.swift index 6e0561e696..5c63cd7fae 100644 --- a/SessionTests/Database/DatabaseSpec.swift +++ b/SessionTests/Database/DatabaseSpec.swift @@ -6,7 +6,7 @@ import Quick import Nimble import SessionUtil import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit @testable import Session @testable import SessionMessagingKit @@ -41,7 +41,7 @@ class DatabaseSpec: QuickSpec { let allMigrations: [Storage.KeyedMigration] = SynchronousStorage.sortedMigrationInfo( migrationTargets: [ SNUtilitiesKit.self, - SNSnodeKit.self, + SNNetworkingKit.self, SNMessagingKit.self, DeprecatedUIKitMigrationTarget.self ] @@ -77,7 +77,7 @@ class DatabaseSpec: QuickSpec { mockStorage.perform( migrationTargets: [ SNUtilitiesKit.self, - SNSnodeKit.self, + SNNetworkingKit.self, SNMessagingKit.self, DeprecatedUIKitMigrationTarget.self ], diff --git a/SessionTests/Session.xctestplan b/SessionTests/Session.xctestplan index fcfc4d45ad..a9c2927686 100644 --- a/SessionTests/Session.xctestplan +++ b/SessionTests/Session.xctestplan @@ -34,7 +34,7 @@ { "containerPath" : "container:Session.xcodeproj", "identifier" : "C3C2A59E255385C100C340D1", - "name" : "SessionSnodeKit" + "name" : "SessionNetworkingKit" }, { "containerPath" : "container:Session.xcodeproj", @@ -87,7 +87,7 @@ "target" : { "containerPath" : "container:Session.xcodeproj", "identifier" : "FDB5DAF92A981C42002C8721", - "name" : "SessionSnodeKitTests" + "name" : "SessionNetworkingKitTests" } }, { diff --git a/SessionTests/Settings/NotificationContentViewModelSpec.swift b/SessionTests/Settings/NotificationContentViewModelSpec.swift index ceda704ea2..65c099d860 100644 --- a/SessionTests/Settings/NotificationContentViewModelSpec.swift +++ b/SessionTests/Settings/NotificationContentViewModelSpec.swift @@ -6,7 +6,7 @@ import Quick import Nimble import SessionUtil import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit @@ -23,7 +23,7 @@ class NotificationContentViewModelSpec: AsyncSpec { customWriter: try! DatabaseQueue(), migrationTargets: [ SNUtilitiesKit.self, - SNSnodeKit.self, + SNNetworkingKit.self, SNMessagingKit.self, DeprecatedUIKitMigrationTarget.self ], diff --git a/SessionUtilitiesKit/Database/Types/TargetMigrations.swift b/SessionUtilitiesKit/Database/Types/TargetMigrations.swift index b320516d5c..860647c618 100644 --- a/SessionUtilitiesKit/Database/Types/TargetMigrations.swift +++ b/SessionUtilitiesKit/Database/Types/TargetMigrations.swift @@ -23,7 +23,7 @@ public struct TargetMigrations: Comparable { // changing them will result in the migrations running again case session case utilitiesKit - case snodeKit + case networkingKit = "snodeKit" case messagingKit case _deprecatedUIKit = "uiKit" case test diff --git a/SessionUtilitiesKit/General/Logging.swift b/SessionUtilitiesKit/General/Logging.swift index 2dff548f87..9bd2646f9e 100644 --- a/SessionUtilitiesKit/General/Logging.swift +++ b/SessionUtilitiesKit/General/Logging.swift @@ -22,6 +22,14 @@ public extension FeatureStorage { ) } + static func logLevel(group: Log.Group) -> FeatureConfig { + return Dependencies.create( + identifier: "\(Log.Group.identifierPrefix)\(group.name)", + groupIdentifier: "logging", + defaultOption: group.defaultLevel + ) + } + static let allLogLevels: FeatureConfig = Dependencies.create( identifier: "allLogLevels", groupIdentifier: "logging" @@ -50,33 +58,66 @@ public enum Log { case off case `default` + + var label: String { + switch self { + case .off: return "off" + case .verbose: return "verbose" + case .debug: return "debug" + case .info: return "info" + case .warn: return "warn" + case .error: return "error" + case .critical: return "critical" + case .default: return "default" + } + } + } + + public struct Group: Hashable { + public let name: String + public let defaultLevel: Log.Level + + fileprivate static let identifierPrefix: String = "group:" + + private init(name: String, defaultLevel: Log.Level) { + self.name = name + + switch AllLoggingCategories.existingGroup(for: name) { + case .some(let existingGroup): self.defaultLevel = existingGroup.defaultLevel + case .none: + self.defaultLevel = defaultLevel + AllLoggingCategories.register(group: self) + } + } + + @discardableResult public static func create( + _ group: String, + defaultLevel: Log.Level + ) -> Log.Group { + return Log.Group(name: group, defaultLevel: defaultLevel) + } } public struct Category: Hashable { public let rawValue: String - fileprivate let customPrefix: String + fileprivate let group: Group? fileprivate let customSuffix: String public let defaultLevel: Log.Level fileprivate static let identifierPrefix: String = "logLevel-" fileprivate var identifier: String { "\(Category.identifierPrefix)\(rawValue)" } - private init(rawValue: String, customPrefix: String, customSuffix: String, defaultLevel: Log.Level) { + private init(rawValue: String, group: Group?, customSuffix: String, defaultLevel: Log.Level) { + self.rawValue = rawValue + self.group = group + self.customSuffix = customSuffix + /// If we've already registered this category then assume the original has the correct `defaultLevel` and only /// modify the `customPrefix` value switch AllLoggingCategories.existingCategory(for: rawValue) { - case .some(let existingCategory): - self.rawValue = existingCategory.rawValue - self.customPrefix = customPrefix - self.customSuffix = customSuffix - self.defaultLevel = existingCategory.defaultLevel - + case .some(let existingCategory): self.defaultLevel = existingCategory.defaultLevel case .none: - self.rawValue = rawValue - self.customPrefix = customPrefix - self.customSuffix = customSuffix self.defaultLevel = defaultLevel - AllLoggingCategories.register(category: self) } } @@ -86,25 +127,25 @@ public enum Log { self.init( rawValue: identifier.substring(from: Category.identifierPrefix.count), - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .default ) } - public init(rawValue: String, customPrefix: String = "", customSuffix: String = "") { - self.init(rawValue: rawValue, customPrefix: customPrefix, customSuffix: customSuffix, defaultLevel: .default) + public init(rawValue: String, group: Group? = nil, customSuffix: String = "") { + self.init(rawValue: rawValue, group: group, customSuffix: customSuffix, defaultLevel: .default) } @discardableResult public static func create( _ rawValue: String, - customPrefix: String = "", + group: Group? = nil, customSuffix: String = "", defaultLevel: Log.Level ) -> Log.Category { return Log.Category( rawValue: rawValue, - customPrefix: customPrefix, + group: group, customSuffix: customSuffix, defaultLevel: defaultLevel ) @@ -653,12 +694,15 @@ public actor Logger: LoggerType { let defaultLogLevel: Log.Level = dependencies[feature: .logLevel(cat: .default)] let lowestCatLevel: Log.Level = categories .reduce(into: [], { result, next in - guard dependencies[feature: .logLevel(cat: next)] != .default else { - result.append(defaultLogLevel) - return - } + let explicitLevel: Log.Level = dependencies[feature: .logLevel(cat: next)] + let groupLevel: Log.Level? = next.group.map { dependencies[feature: .logLevel(group: $0)] } - result.append(dependencies[feature: .logLevel(cat: next)]) + switch (explicitLevel, groupLevel) { + case (.default, .none): result.append(defaultLogLevel) + case (.default, .default): result.append(defaultLogLevel) + case (_, .none): result.append(explicitLevel) + case (_, .some(let groupLevel)): result.append(min(explicitLevel, groupLevel)) + } }) .min() .defaulting(to: defaultLogLevel) @@ -678,15 +722,15 @@ public actor Logger: LoggerType { /// No point doubling up but we want to allow categories which match the `primaryPrefix` so that we /// have a mechanism for providing a different "default" log level for a specific target .filter { $0.rawValue != primaryPrefix } - .map { "\($0.customPrefix)\($0.rawValue)\($0.customSuffix)" } + .map { "\($0.group.map { "\($0.name):" } ?? "")\($0.rawValue)\($0.customSuffix)" } ) .joined(separator: ", ") - return "[\(prefixes)] " + return "[\(prefixes)]" }() /// Clean up the message if needed (replace double periods with single, trim whitespace, truncate pubkeys) - let logMessage: String = logPrefix + let cleanedMessage: String = logPrefix .appending(message) .replacingOccurrences(of: "...", with: "|||") .replacingOccurrences(of: "..", with: ".") @@ -709,16 +753,17 @@ public actor Logger: LoggerType { return updatedText } - + let ddLogMessage: String = "\(logPrefix) ".appending(cleanedMessage) + let consoleLogMessage: String = "\(logPrefix)[\(level)] ".appending(cleanedMessage) switch level { case .off, .default: return - case .verbose: DDLogVerbose("💙 \(logMessage)", file: file, function: function, line: line) - case .debug: DDLogDebug("💚 \(logMessage)", file: file, function: function, line: line) - case .info: DDLogInfo("💛 \(logMessage)", file: file, function: function, line: line) - case .warn: DDLogWarn("🧡 \(logMessage)", file: file, function: function, line: line) - case .error: DDLogError("❤️ \(logMessage)", file: file, function: function, line: line) - case .critical: DDLogError("🔥 \(logMessage)", file: file, function: function, line: line) + case .verbose: DDLogVerbose("💙 \(ddLogMessage)", file: file, function: function, line: line) + case .debug: DDLogDebug("💚 \(ddLogMessage)", file: file, function: function, line: line) + case .info: DDLogInfo("💛 \(ddLogMessage)", file: file, function: function, line: line) + case .warn: DDLogWarn("🧡 \(ddLogMessage)", file: file, function: function, line: line) + case .error: DDLogError("❤️ \(ddLogMessage)", file: file, function: function, line: line) + case .critical: DDLogError("🔥 \(ddLogMessage)", file: file, function: function, line: line) } let mainCategory: String = (categories.first?.rawValue ?? "General") @@ -730,7 +775,7 @@ public actor Logger: LoggerType { } #if DEBUG - systemLogger?.log(level, logMessage) + systemLogger?.log(level, consoleLogMessage) #endif } } @@ -859,6 +904,7 @@ extension Log.Level: FeatureOption { public struct AllLoggingCategories: FeatureOption { public static let allCases: [AllLoggingCategories] = [] + @ThreadSafeObject private static var registeredGroupDefaults: Set = [] @ThreadSafeObject private static var registeredCategoryDefaults: Set = [] // MARK: - Initialization @@ -866,7 +912,21 @@ public struct AllLoggingCategories: FeatureOption { public let rawValue: Int public init(rawValue: Int) { - self.rawValue = -1 // `0` is a protected value so can't use it + _ = Log.Category.default // Access the `default` log category to ensure it exists + self.rawValue = -1 // `0` is a protected value so can't use it + } + + fileprivate static func register(group: Log.Group) { + guard + !registeredGroupDefaults.contains(where: { existingGroup in + /// **Note:** We only want to use the `rawValue` to distinguish between logging categories + /// as the `defaultLevel` can change via the dev settings and any additional metadata could + /// be file/class specific + group.name == existingGroup.name + }) + else { return } + + _registeredGroupDefaults.performUpdate { $0.inserting(group) } } fileprivate static func register(category: Log.Category) { @@ -882,10 +942,21 @@ public struct AllLoggingCategories: FeatureOption { _registeredCategoryDefaults.performUpdate { $0.inserting(category) } } + fileprivate static func existingGroup(for name: String) -> Log.Group? { + return AllLoggingCategories.registeredGroupDefaults.first(where: { $0.name == name }) + } + fileprivate static func existingCategory(for cat: String) -> Log.Category? { return AllLoggingCategories.registeredCategoryDefaults.first(where: { $0.rawValue == cat }) } + public func currentValues(using dependencies: Dependencies) -> [Log.Group: Log.Level] { + return AllLoggingCategories.registeredGroupDefaults + .reduce(into: [:]) { result, group in + result[group] = dependencies[feature: .logLevel(group: group)] + } + } + public func currentValues(using dependencies: Dependencies) -> [Log.Category: Log.Level] { return AllLoggingCategories.registeredCategoryDefaults .reduce(into: [:]) { result, cat in diff --git a/SessionUtilitiesKit/LibSession/LibSession.swift b/SessionUtilitiesKit/LibSession/LibSession.swift index 916686f2f8..e787e7b4db 100644 --- a/SessionUtilitiesKit/LibSession/LibSession.swift +++ b/SessionUtilitiesKit/LibSession/LibSession.swift @@ -17,6 +17,10 @@ public extension Log.Category { static let libSession: Log.Category = .create("LibSession", defaultLevel: .info) } +public extension Log.Group { + static let libSession: Log.Group = .create("libSession", defaultLevel: .info) +} + // MARK: - Logging extension LibSession { @@ -29,7 +33,13 @@ extension LibSession { ObservationBuilder.observe(.featureGroup(.allLogLevels), using: dependencies) { [dependencies] _ in let currentLogLevels: [Log.Category: Log.Level] = dependencies[feature: .allLogLevels] .currentValues(using: dependencies) - let cDefaultLevel: LOG_LEVEL = (currentLogLevels[.default]?.libSession ?? LOG_LEVEL_OFF) + let currentGroupLogLevels: [Log.Group: Log.Level] = dependencies[feature: .allLogLevels] + .currentValues(using: dependencies) + let targetDefault: Log.Level? = min( + (currentLogLevels[.default] ?? .off), + (currentGroupLogLevels[.libSession] ?? .off) + ) + let cDefaultLevel: LOG_LEVEL = (targetDefault?.libSession ?? LOG_LEVEL_OFF) session_logger_set_level_default(cDefaultLevel) session_logger_reset_level(cDefaultLevel) @@ -57,20 +67,45 @@ extension LibSession { DispatchQueue.global(qos: .background).async { /// Logs from libSession come through in the format: /// `[yyyy-MM-dd hh:mm:ss] [+{lifetime}s] [{cat}:{lvl}|log.hpp:{line}] {message}` - /// We want to remove the extra data because it doesn't help the logs + /// + /// We want to simplify the message because our logging already includes category and timestamp information: + /// `[+{lifetime}s] {message}` let processedMessage: String = { - let logParts: [String] = msg.components(separatedBy: "] ") + let trimmedMsg = msg.trimmingCharacters(in: .whitespacesAndNewlines) - guard logParts.count == 4 else { return msg.trimmingCharacters(in: .whitespacesAndNewlines) } + guard + let timestampRegex: NSRegularExpression = LibSession.timestampRegex, + let messageStartRegex: NSRegularExpression = LibSession.messageStartRegex + else { return trimmedMsg } - let message: String = String(logParts[3]).trimmingCharacters(in: .whitespacesAndNewlines) + let fullRange = NSRange(trimmedMsg.startIndex.. Date: Thu, 7 Aug 2025 15:34:13 +1000 Subject: [PATCH 02/66] Fixed some missing renames --- Session.xcodeproj/project.pbxproj | 4 ---- SessionMessagingKit/Crypto/Crypto+Attachments.swift | 2 +- .../Database/Migrations/_026_MessageDeduplicationTable.swift | 2 +- .../Database/Migrations/_028_RenameAttachments.swift | 2 +- .../Database/Models/MessageDeduplication.swift | 2 +- .../Jobs/RetrieveDefaultOpenGroupRoomsJob.swift | 2 +- .../Sending & Receiving/AttachmentUploader.swift | 2 +- SessionMessagingKit/Utilities/AttachmentManager.swift | 2 +- SessionMessagingKit/Utilities/ExtensionHelper.swift | 2 +- .../Database/Models/MessageDeduplicationSpec.swift | 2 +- SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift | 2 +- .../_TestUtilities/MockExtensionHelper.swift | 2 +- SessionTests/Onboarding/OnboardingSpec.swift | 2 +- 13 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 88ed056459..7f35e099db 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -1018,7 +1018,6 @@ FDE33BBE2D5C3AF100E56F42 /* _023_GroupsExpiredFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE33BBD2D5C3AE800E56F42 /* _023_GroupsExpiredFlag.swift */; }; FDE519F72AB7CDC700450C53 /* Result+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE519F62AB7CDC700450C53 /* Result+Utilities.swift */; }; FDE5218E2E03A06B00061B8E /* AttachmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE5218D2E03A06700061B8E /* AttachmentManager.swift */; }; - FDE521902E04CCEB00061B8E /* AVURLAsset+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE5218F2E04CCE600061B8E /* AVURLAsset+Utilities.swift */; }; FDE521942E050B1100061B8E /* DismissCallbackAVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE521932E050B0800061B8E /* DismissCallbackAVPlayerViewController.swift */; }; FDE5219A2E08DBB800061B8E /* ImageLoading+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE521992E08DBB000061B8E /* ImageLoading+Convenience.swift */; }; FDE5219C2E08E76C00061B8E /* SessionAsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE5219B2E08E76600061B8E /* SessionAsyncImage.swift */; }; @@ -2281,7 +2280,6 @@ FDE33BBD2D5C3AE800E56F42 /* _023_GroupsExpiredFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _023_GroupsExpiredFlag.swift; sourceTree = ""; }; FDE519F62AB7CDC700450C53 /* Result+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Utilities.swift"; sourceTree = ""; }; FDE5218D2E03A06700061B8E /* AttachmentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentManager.swift; sourceTree = ""; }; - FDE5218F2E04CCE600061B8E /* AVURLAsset+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVURLAsset+Utilities.swift"; sourceTree = ""; }; FDE521932E050B0800061B8E /* DismissCallbackAVPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissCallbackAVPlayerViewController.swift; sourceTree = ""; }; FDE521992E08DBB000061B8E /* ImageLoading+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImageLoading+Convenience.swift"; sourceTree = ""; }; FDE5219B2E08E76600061B8E /* SessionAsyncImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionAsyncImage.swift; sourceTree = ""; }; @@ -3951,7 +3949,6 @@ isa = PBXGroup; children = ( FDB3DA892E2482A400148F8D /* AVURLAsset+Utilities.swift */, - FDE5218F2E04CCE600061B8E /* AVURLAsset+Utilities.swift */, 94C58AC82D2E036E00609195 /* Permissions.swift */, FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */, FD78EA052DDEC8F100D55B50 /* AsyncSequence+Utilities.swift */, @@ -6342,7 +6339,6 @@ FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */, FD09797D27FBDB2000936362 /* Notification+Utilities.swift in Sources */, FDC6D7602862B3F600B04575 /* Dependencies.swift in Sources */, - FDE521902E04CCEB00061B8E /* AVURLAsset+Utilities.swift in Sources */, FD6673FF2D77F9C100041530 /* ScreenLock.swift in Sources */, FD78E9F02DD6D61200D55B50 /* Data+Image.swift in Sources */, FDB3DA8B2E24834000148F8D /* AVURLAsset+Utilities.swift in Sources */, diff --git a/SessionMessagingKit/Crypto/Crypto+Attachments.swift b/SessionMessagingKit/Crypto/Crypto+Attachments.swift index 6caca2f459..ceb7cca191 100644 --- a/SessionMessagingKit/Crypto/Crypto+Attachments.swift +++ b/SessionMessagingKit/Crypto/Crypto+Attachments.swift @@ -4,7 +4,7 @@ import Foundation import CommonCrypto -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Encryption diff --git a/SessionMessagingKit/Database/Migrations/_026_MessageDeduplicationTable.swift b/SessionMessagingKit/Database/Migrations/_026_MessageDeduplicationTable.swift index fd993ac86b..5addd66869 100644 --- a/SessionMessagingKit/Database/Migrations/_026_MessageDeduplicationTable.swift +++ b/SessionMessagingKit/Database/Migrations/_026_MessageDeduplicationTable.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit /// The different platforms use different approaches for message deduplication but in the future we want to shift the database logic into /// `libSession` so it makes sense to try to define a longer-term deduplication approach we we can use in `libSession`, additonally diff --git a/SessionMessagingKit/Database/Migrations/_028_RenameAttachments.swift b/SessionMessagingKit/Database/Migrations/_028_RenameAttachments.swift index 2c92c21d11..a4e8969695 100644 --- a/SessionMessagingKit/Database/Migrations/_028_RenameAttachments.swift +++ b/SessionMessagingKit/Database/Migrations/_028_RenameAttachments.swift @@ -3,7 +3,7 @@ import Foundation import UniformTypeIdentifiers import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit /// This migration renames all attachments to use a hash of the download url for the filename instead of a random UUID (means we can diff --git a/SessionMessagingKit/Database/Models/MessageDeduplication.swift b/SessionMessagingKit/Database/Models/MessageDeduplication.swift index 15269d25b2..6a73d52936 100644 --- a/SessionMessagingKit/Database/Models/MessageDeduplication.swift +++ b/SessionMessagingKit/Database/Models/MessageDeduplication.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift b/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift index a864b6c2fe..06d2b8e3b9 100644 --- a/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift +++ b/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Sending & Receiving/AttachmentUploader.swift b/SessionMessagingKit/Sending & Receiving/AttachmentUploader.swift index 532c5fe5ec..dec4a6495e 100644 --- a/SessionMessagingKit/Sending & Receiving/AttachmentUploader.swift +++ b/SessionMessagingKit/Sending & Receiving/AttachmentUploader.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - AttachmentUploader diff --git a/SessionMessagingKit/Utilities/AttachmentManager.swift b/SessionMessagingKit/Utilities/AttachmentManager.swift index 33f95cdb4c..621a0a6d1e 100644 --- a/SessionMessagingKit/Utilities/AttachmentManager.swift +++ b/SessionMessagingKit/Utilities/AttachmentManager.swift @@ -7,7 +7,7 @@ import Combine import UniformTypeIdentifiers import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Singleton diff --git a/SessionMessagingKit/Utilities/ExtensionHelper.swift b/SessionMessagingKit/Utilities/ExtensionHelper.swift index 0b04520659..ed44a08963 100644 --- a/SessionMessagingKit/Utilities/ExtensionHelper.swift +++ b/SessionMessagingKit/Utilities/ExtensionHelper.swift @@ -1,7 +1,7 @@ // Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Singleton diff --git a/SessionMessagingKitTests/Database/Models/MessageDeduplicationSpec.swift b/SessionMessagingKitTests/Database/Models/MessageDeduplicationSpec.swift index 458c7cc357..bd36080603 100644 --- a/SessionMessagingKitTests/Database/Models/MessageDeduplicationSpec.swift +++ b/SessionMessagingKitTests/Database/Models/MessageDeduplicationSpec.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import Quick diff --git a/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift b/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift index 16d88729ea..ca8c0919b6 100644 --- a/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift +++ b/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtil -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import Quick diff --git a/SessionMessagingKitTests/_TestUtilities/MockExtensionHelper.swift b/SessionMessagingKitTests/_TestUtilities/MockExtensionHelper.swift index e68a787acd..06a4562e50 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockExtensionHelper.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockExtensionHelper.swift @@ -1,7 +1,7 @@ // Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit @testable import SessionMessagingKit diff --git a/SessionTests/Onboarding/OnboardingSpec.swift b/SessionTests/Onboarding/OnboardingSpec.swift index e6a4e41559..214fd1d28d 100644 --- a/SessionTests/Onboarding/OnboardingSpec.swift +++ b/SessionTests/Onboarding/OnboardingSpec.swift @@ -9,7 +9,7 @@ import SessionUIKit import SessionUtilitiesKit @testable import Session -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit class OnboardingSpec: AsyncSpec { From a758139b1b4e2e85fc33b34bbccb76d1da2692d9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 7 Aug 2025 16:31:14 +1000 Subject: [PATCH 03/66] Fixed a couple of build issues --- .../Utilities/ExtensionHelperSpec.swift | 14 +++++++------- SessionTests/Onboarding/OnboardingSpec.swift | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift b/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift index ca8c0919b6..5cd9216cab 100644 --- a/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift +++ b/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift @@ -738,7 +738,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) @@ -2332,7 +2332,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) @@ -2369,7 +2369,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) @@ -2385,7 +2385,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) @@ -2422,7 +2422,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) @@ -2438,7 +2438,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) @@ -2480,7 +2480,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) diff --git a/SessionTests/Onboarding/OnboardingSpec.swift b/SessionTests/Onboarding/OnboardingSpec.swift index 214fd1d28d..e351b5a8b5 100644 --- a/SessionTests/Onboarding/OnboardingSpec.swift +++ b/SessionTests/Onboarding/OnboardingSpec.swift @@ -26,7 +26,7 @@ class OnboardingSpec: AsyncSpec { customWriter: try! DatabaseQueue(), migrationTargets: [ SNUtilitiesKit.self, - SNSnodeKit.self, + SNNetworkingKit.self, SNMessagingKit.self, DeprecatedUIKitMigrationTarget.self ], From 2d8675fcc103665bac6560776617becda8ea89d9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 8 Aug 2025 13:04:35 +1000 Subject: [PATCH 04/66] Moved all migrations into one target, simplified migration logic --- Session.xcodeproj/project.pbxproj | 388 ++++++++---------- Session/Meta/AppDelegate.swift | 3 - SessionMessagingKit/Configuration.swift | 99 +++-- .../_001_SUK_InitialSetupMigration.swift | 6 +- .../_002_SUK_SetupStandardJobs.swift | 6 +- .../_003_SUK_YDBToGRDBMigration.swift | 5 +- .../_004_SNK_InitialSetupMigration.swift | 5 +- .../_005_SNK_SetupStandardJobs.swift | 5 +- ...t => _006_SMK_InitialSetupMigration.swift} | 13 +- ...swift => _007_SMK_SetupStandardJobs.swift} | 5 +- .../_008_SNK_YDBToGRDBMigration.swift | 6 +- ...wift => _009_SMK_YDBToGRDBMigration.swift} | 5 +- ...10_FlagMessageHashAsDeletedOrInvalid.swift | 5 +- ...cyYDB.swift => _011_RemoveLegacyYDB.swift} | 5 +- .../Migrations/_012_AddJobPriority.swift | 6 +- ... => _013_FixDeletedMessageReadState.swift} | 5 +- ...ft => _014_FixHiddenModAdminSupport.swift} | 5 +- ...> _015_HomeQueryOptimisationIndexes.swift} | 5 +- .../Migrations/_016_ThemePreferences.swift | 27 +- ...ojiReacts.swift => _017_EmojiReacts.swift} | 5 +- ...n.swift => _018_OpenGroupPermission.swift} | 5 +- ...oFTS.swift => _019_AddThreadIdToFTS.swift} | 7 +- .../Migrations/_020_AddJobUniqueHash.swift | 6 +- ...ddSnodeReveivedMessageInfoPrimaryKey.swift | 6 +- .../Migrations/_022_DropSnodeCache.swift | 5 +- .../_023_SplitSnodeReceivedMessageInfo.swift | 6 +- .../_024_ResetUserConfigLastHashes.swift | 6 +- ...wift => _025_AddPendingReadReceipts.swift} | 5 +- ...Needed.swift => _026_AddFTSIfNeeded.swift} | 7 +- ...es.swift => _027_SessionUtilChanges.swift} | 7 +- ..._028_GenerateInitialUserConfigDumps.swift} | 5 +- ... _029_BlockCommunityMessageRequests.swift} | 5 +- ...MakeBrokenProfileTimestampsNullable.swift} | 5 +- ...ft => _031_RebuildFTSIfNeeded_2_4_5.swift} | 13 +- ...2_DisappearingMessagesConfiguration.swift} | 5 +- ...t => _033_ScheduleAppUpdateCheckJob.swift} | 5 +- ...swift => _034_AddMissingWhisperFlag.swift} | 5 +- ....swift => _035_ReworkRecipientState.swift} | 7 +- ....swift => _036_GroupsRebuildChanges.swift} | 7 +- ...lag.swift => _037_GroupsExpiredFlag.swift} | 5 +- ...=> _038_FixBustedInteractionVariant.swift} | 5 +- ...9_DropLegacyClosedGroupKeyPairTable.swift} | 5 +- ...t => _040_MessageDeduplicationTable.swift} | 9 +- ...41_RenameTableSettingToKeyValueStore.swift | 6 +- ...ft => _042_MoveSettingsToLibSession.swift} | 5 +- ...nts.swift => _043_RenameAttachments.swift} | 5 +- ...lag.swift => _044_AddProMessageFlag.swift} | 5 +- .../Models/MessageDeduplication.swift | 10 +- .../Models/MessageDeduplicationSpec.swift | 4 +- .../Jobs/DisplayPictureDownloadJobSpec.swift | 5 +- .../Jobs/MessageSendJobSpec.swift | 5 +- ...RetrieveDefaultOpenGroupRoomsJobSpec.swift | 5 +- .../LibSession/LibSessionGroupInfoSpec.swift | 6 +- .../LibSessionGroupMembersSpec.swift | 5 +- .../LibSession/LibSessionSpec.swift | 5 +- .../Open Groups/OpenGroupManagerSpec.swift | 5 +- .../MessageReceiverGroupsSpec.swift | 6 +- .../MessageSenderGroupsSpec.swift | 5 +- .../MessageSenderSpec.swift | 5 +- .../Pollers/CommunityPollerSpec.swift | 5 +- .../Utilities/ExtensionHelperSpec.swift | 5 +- SessionNetworkingKit/Configuration.swift | 35 -- .../ShareNavController.swift | 1 - ...eadDisappearingMessagesViewModelSpec.swift | 7 +- ...eadNotificationSettingsViewModelSpec.swift | 7 +- .../ThreadSettingsViewModelSpec.swift | 7 +- SessionTests/Database/DatabaseSpec.swift | 178 ++++---- SessionTests/Onboarding/OnboardingSpec.swift | 7 +- .../NotificationContentViewModelSpec.swift | 7 +- SessionUtilitiesKit/Configuration.swift | 31 +- SessionUtilitiesKit/Database/Storage.swift | 71 +--- .../Database/Types/Migration.swift | 16 +- .../Database/Types/TargetMigrations.swift | 76 ---- .../DatabaseMigrator+Utilities.swift | 22 - .../Database/Models/IdentitySpec.swift | 4 +- .../JobRunner/JobRunnerSpec.swift | 12 +- SignalUtilitiesKit/Utilities/AppSetup.swift | 10 +- _SharedTestUtilities/SynchronousStorage.swift | 18 +- 78 files changed, 473 insertions(+), 883 deletions(-) rename SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift => SessionMessagingKit/Database/Migrations/_001_SUK_InitialSetupMigration.swift (94%) rename SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift => SessionMessagingKit/Database/Migrations/_002_SUK_SetupStandardJobs.swift (89%) rename SessionNetworkingKit/Database/Migrations/_003_YDBToGRDBMigration.swift => SessionMessagingKit/Database/Migrations/_003_SUK_YDBToGRDBMigration.swift (70%) rename SessionNetworkingKit/Database/Migrations/_001_InitialSetupMigration.swift => SessionMessagingKit/Database/Migrations/_004_SNK_InitialSetupMigration.swift (90%) rename SessionNetworkingKit/Database/Migrations/_002_SetupStandardJobs.swift => SessionMessagingKit/Database/Migrations/_005_SNK_SetupStandardJobs.swift (91%) rename SessionMessagingKit/Database/Migrations/{_001_InitialSetupMigration.swift => _006_SMK_InitialSetupMigration.swift} (97%) rename SessionMessagingKit/Database/Migrations/{_002_SetupStandardJobs.swift => _007_SMK_SetupStandardJobs.swift} (93%) rename SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift => SessionMessagingKit/Database/Migrations/_008_SNK_YDBToGRDBMigration.swift (69%) rename SessionMessagingKit/Database/Migrations/{_003_YDBToGRDBMigration.swift => _009_SMK_YDBToGRDBMigration.swift} (79%) rename SessionNetworkingKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift => SessionMessagingKit/Database/Migrations/_010_FlagMessageHashAsDeletedOrInvalid.swift (81%) rename SessionMessagingKit/Database/Migrations/{_004_RemoveLegacyYDB.swift => _011_RemoveLegacyYDB.swift} (78%) rename SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift => SessionMessagingKit/Database/Migrations/_012_AddJobPriority.swift (90%) rename SessionMessagingKit/Database/Migrations/{_005_FixDeletedMessageReadState.swift => _013_FixDeletedMessageReadState.swift} (83%) rename SessionMessagingKit/Database/Migrations/{_006_FixHiddenModAdminSupport.swift => _014_FixHiddenModAdminSupport.swift} (85%) rename SessionMessagingKit/Database/Migrations/{_007_HomeQueryOptimisationIndexes.swift => _015_HomeQueryOptimisationIndexes.swift} (81%) rename Session/Database/Migrations/_001_ThemePreferences.swift => SessionMessagingKit/Database/Migrations/_016_ThemePreferences.swift (78%) rename SessionMessagingKit/Database/Migrations/{_008_EmojiReacts.swift => _017_EmojiReacts.swift} (91%) rename SessionMessagingKit/Database/Migrations/{_009_OpenGroupPermission.swift => _018_OpenGroupPermission.swift} (83%) rename SessionMessagingKit/Database/Migrations/{_010_AddThreadIdToFTS.swift => _019_AddThreadIdToFTS.swift} (82%) rename SessionUtilitiesKit/Database/Migrations/_005_AddJobUniqueHash.swift => SessionMessagingKit/Database/Migrations/_020_AddJobUniqueHash.swift (76%) rename SessionNetworkingKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift => SessionMessagingKit/Database/Migrations/_021_AddSnodeReveivedMessageInfoPrimaryKey.swift (91%) rename SessionNetworkingKit/Database/Migrations/_006_DropSnodeCache.swift => SessionMessagingKit/Database/Migrations/_022_DropSnodeCache.swift (86%) rename SessionNetworkingKit/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift => SessionMessagingKit/Database/Migrations/_023_SplitSnodeReceivedMessageInfo.swift (96%) rename SessionNetworkingKit/Database/Migrations/_008_ResetUserConfigLastHashes.swift => SessionMessagingKit/Database/Migrations/_024_ResetUserConfigLastHashes.swift (84%) rename SessionMessagingKit/Database/Migrations/{_011_AddPendingReadReceipts.swift => _025_AddPendingReadReceipts.swift} (89%) rename SessionMessagingKit/Database/Migrations/{_012_AddFTSIfNeeded.swift => _026_AddFTSIfNeeded.swift} (80%) rename SessionMessagingKit/Database/Migrations/{_013_SessionUtilChanges.swift => _027_SessionUtilChanges.swift} (98%) rename SessionMessagingKit/Database/Migrations/{_014_GenerateInitialUserConfigDumps.swift => _028_GenerateInitialUserConfigDumps.swift} (98%) rename SessionMessagingKit/Database/Migrations/{_015_BlockCommunityMessageRequests.swift => _029_BlockCommunityMessageRequests.swift} (94%) rename SessionMessagingKit/Database/Migrations/{_016_MakeBrokenProfileTimestampsNullable.swift => _030_MakeBrokenProfileTimestampsNullable.swift} (89%) rename SessionMessagingKit/Database/Migrations/{_017_RebuildFTSIfNeeded_2_4_5.swift => _031_RebuildFTSIfNeeded_2_4_5.swift} (85%) rename SessionMessagingKit/Database/Migrations/{_018_DisappearingMessagesConfiguration.swift => _032_DisappearingMessagesConfiguration.swift} (97%) rename SessionMessagingKit/Database/Migrations/{_019_ScheduleAppUpdateCheckJob.swift => _033_ScheduleAppUpdateCheckJob.swift} (84%) rename SessionMessagingKit/Database/Migrations/{_020_AddMissingWhisperFlag.swift => _034_AddMissingWhisperFlag.swift} (81%) rename SessionMessagingKit/Database/Migrations/{_021_ReworkRecipientState.swift => _035_ReworkRecipientState.swift} (97%) rename SessionMessagingKit/Database/Migrations/{_022_GroupsRebuildChanges.swift => _036_GroupsRebuildChanges.swift} (97%) rename SessionMessagingKit/Database/Migrations/{_023_GroupsExpiredFlag.swift => _037_GroupsExpiredFlag.swift} (76%) rename SessionMessagingKit/Database/Migrations/{_024_FixBustedInteractionVariant.swift => _038_FixBustedInteractionVariant.swift} (83%) rename SessionMessagingKit/Database/Migrations/{_025_DropLegacyClosedGroupKeyPairTable.swift => _039_DropLegacyClosedGroupKeyPairTable.swift} (74%) rename SessionMessagingKit/Database/Migrations/{_026_MessageDeduplicationTable.swift => _040_MessageDeduplicationTable.swift} (98%) rename SessionUtilitiesKit/Database/Migrations/_006_RenameTableSettingToKeyValueStore.swift => SessionMessagingKit/Database/Migrations/_041_RenameTableSettingToKeyValueStore.swift (68%) rename SessionMessagingKit/Database/Migrations/{_027_MoveSettingsToLibSession.swift => _042_MoveSettingsToLibSession.swift} (97%) rename SessionMessagingKit/Database/Migrations/{_028_RenameAttachments.swift => _043_RenameAttachments.swift} (99%) rename SessionMessagingKit/Database/Migrations/{_029_AddProMessageFlag.swift => _044_AddProMessageFlag.swift} (76%) delete mode 100644 SessionNetworkingKit/Configuration.swift delete mode 100644 SessionUtilitiesKit/Database/Types/TargetMigrations.swift delete mode 100644 SessionUtilitiesKit/Database/Utilities/DatabaseMigrator+Utilities.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 7f35e099db..f4c84c3d12 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -98,7 +98,7 @@ 7B4EF25A2934743000CB351D /* SessionTableViewTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4EF2592934743000CB351D /* SessionTableViewTitleView.swift */; }; 7B50D64D28AC7CF80086CCEC /* silence.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 7B50D64C28AC7CF80086CCEC /* silence.aiff */; }; 7B5233C42900E90F00F8F375 /* SessionLabelCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5233C32900E90F00F8F375 /* SessionLabelCarouselView.swift */; }; - 7B5233C6290636D700F8F375 /* _018_DisappearingMessagesConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5233C5290636D700F8F375 /* _018_DisappearingMessagesConfiguration.swift */; }; + 7B5233C6290636D700F8F375 /* _032_DisappearingMessagesConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5233C5290636D700F8F375 /* _032_DisappearingMessagesConfiguration.swift */; }; 7B5802992AAEF1B50050EEB1 /* OpenGroupInvitationView_SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5802982AAEF1B50050EEB1 /* OpenGroupInvitationView_SwiftUI.swift */; }; 7B7037432834B81F000DCF35 /* ReactionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037422834B81F000DCF35 /* ReactionContainerView.swift */; }; 7B7037452834BCC0000DCF35 /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037442834BCC0000DCF35 /* ReactionView.swift */; }; @@ -107,7 +107,7 @@ 7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */; }; 7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */; }; 7B81682328A4C1210069F315 /* UpdateTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682228A4C1210069F315 /* UpdateTypes.swift */; }; - 7B81682828B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */; }; + 7B81682828B310D50069F315 /* _015_HomeQueryOptimisationIndexes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682728B310D50069F315 /* _015_HomeQueryOptimisationIndexes.swift */; }; 7B81682A28B6F1420069F315 /* ReactionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682928B6F1420069F315 /* ReactionResponse.swift */; }; 7B81682C28B72F480069F315 /* PendingChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682B28B72F480069F315 /* PendingChange.swift */; }; 7B81FB5A2AB01B17002FB267 /* LoadingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81FB582AB01AA8002FB267 /* LoadingIndicatorView.swift */; }; @@ -129,7 +129,7 @@ 7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA68908272A27BE00EFC32F /* SessionCall.swift */; }; 7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */; }; 7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */; }; - 7BAA7B6628D2DE4700AE1489 /* _009_OpenGroupPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAA7B6528D2DE4700AE1489 /* _009_OpenGroupPermission.swift */; }; + 7BAA7B6628D2DE4700AE1489 /* _018_OpenGroupPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAA7B6528D2DE4700AE1489 /* _018_OpenGroupPermission.swift */; }; 7BAADFCC27B0EF23007BCF92 /* CallVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAADFCB27B0EF23007BCF92 /* CallVideoView.swift */; }; 7BAADFCE27B215FE007BCF92 /* UIView+Draggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAADFCD27B215FE007BCF92 /* UIView+Draggable.swift */; }; 7BAF54CF27ACCEEC003D12F8 /* GlobalSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54CC27ACCEEC003D12F8 /* GlobalSearchViewController.swift */; }; @@ -174,7 +174,6 @@ 9422EE2B2B8C3A97004C740D /* String+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9422EE2A2B8C3A97004C740D /* String+Utilities.swift */; }; 942ADDD42D9F9613006E0BB0 /* NewTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 942ADDD32D9F960C006E0BB0 /* NewTagView.swift */; }; 943C6D822B75E061004ACE64 /* Message+DisappearingMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943C6D812B75E061004ACE64 /* Message+DisappearingMessages.swift */; }; - 945D9C582D6FDBE7003C4C0C /* _005_AddJobUniqueHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945D9C572D6FDBE7003C4C0C /* _005_AddJobUniqueHash.swift */; }; 946F5A732D5DA3AC00A5ADCE /* Punycode in Frameworks */ = {isa = PBXBuildFile; productRef = 946F5A722D5DA3AC00A5ADCE /* Punycode */; }; 9473386E2BDF5F3E00B9E169 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9473386D2BDF5F3E00B9E169 /* InfoPlist.xcstrings */; }; 9479981C2DD44ADC008F5CD5 /* ThreadNotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9479981B2DD44AC5008F5CD5 /* ThreadNotificationSettingsViewModel.swift */; }; @@ -183,7 +182,6 @@ 947D7FD72D509FC900E8E413 /* SessionNetworkAPI+Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD22D509FC900E8E413 /* SessionNetworkAPI+Network.swift */; }; 947D7FD82D509FC900E8E413 /* SessionNetworkAPI+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD02D509FC900E8E413 /* SessionNetworkAPI+Database.swift */; }; 947D7FDE2D5180F200E8E413 /* SessionNetworkScreen+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FDB2D5180F200E8E413 /* SessionNetworkScreen+ViewModel.swift */; }; - 947D7FE32D5181F400E8E413 /* _006_RenameTableSettingToKeyValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FE22D5181F400E8E413 /* _006_RenameTableSettingToKeyValueStore.swift */; }; 947D7FE72D51837200E8E413 /* ArrowCapsule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FE42D51837200E8E413 /* ArrowCapsule.swift */; }; 947D7FE82D51837200E8E413 /* PopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FE52D51837200E8E413 /* PopoverView.swift */; }; 947D7FE92D51837200E8E413 /* Text+CopyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FE62D51837200E8E413 /* Text+CopyButton.swift */; }; @@ -202,7 +200,7 @@ 94C58AC92D2E037200609195 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C58AC82D2E036E00609195 /* Permissions.swift */; }; 94CD95BB2E08D9E00097754D /* SessionProBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD95BA2E08D9D40097754D /* SessionProBadge.swift */; }; 94CD95BD2E09083C0097754D /* LibSession+Pro.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD95BC2E0908340097754D /* LibSession+Pro.swift */; }; - 94CD95C12E0CBF430097754D /* _029_AddProMessageFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD95C02E0CBF1C0097754D /* _029_AddProMessageFlag.swift */; }; + 94CD95C12E0CBF430097754D /* _044_AddProMessageFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD95C02E0CBF1C0097754D /* _044_AddProMessageFlag.swift */; }; 94CD962D2E1B85920097754D /* InputViewButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD962B2E1B85920097754D /* InputViewButton.swift */; }; 94CD962E2E1B85920097754D /* InputTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD962A2E1B85920097754D /* InputTextView.swift */; }; 94CD96302E1B88430097754D /* CGRect+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD962F2E1B88430097754D /* CGRect+Utilities.swift */; }; @@ -375,7 +373,6 @@ C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */; }; C3C2A5A3255385C100C340D1 /* SessionNetworkingKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A5A1255385C100C340D1 /* SessionNetworkingKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; C3C2A5A7255385C100C340D1 /* SessionNetworkingKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B9255385ED00C340D1 /* Configuration.swift */; }; C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */; }; C3C2A681255388CC00C340D1 /* SessionUtilitiesKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C3C2A6C62553896A00C340D1 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; @@ -430,7 +427,7 @@ FD02CC142C3677E6009AB976 /* Request+OpenGroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD02CC132C3677E6009AB976 /* Request+OpenGroupAPI.swift */; }; FD02CC162C3681EF009AB976 /* RevokeSubaccountResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD02CC152C3681EF009AB976 /* RevokeSubaccountResponse.swift */; }; FD05593D2DFA3A2800DC48CE /* VoipPayloadKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05593C2DFA3A2200DC48CE /* VoipPayloadKey.swift */; }; - FD05594E2E012D2700DC48CE /* _028_RenameAttachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05594D2E012D1A00DC48CE /* _028_RenameAttachments.swift */; }; + FD05594E2E012D2700DC48CE /* _043_RenameAttachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05594D2E012D1A00DC48CE /* _043_RenameAttachments.swift */; }; FD0559562E026E1B00DC48CE /* ObservingDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0559542E026CC900DC48CE /* ObservingDatabase.swift */; }; FD0606C32BCE13ED00C3816E /* MessageRequestFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0606C22BCE13ED00C3816E /* MessageRequestFooterView.swift */; }; FD078E5427E197CA000769AF /* OpenGroupManagerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */; }; @@ -452,7 +449,7 @@ FD09799927FFC1A300936362 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09799827FFC1A300936362 /* Attachment.swift */; }; FD09799B27FFC82D00936362 /* Quote.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09799A27FFC82D00936362 /* Quote.swift */; }; FD09B7E328865FDA00ED0B66 /* HighlightMentionBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09B7E228865FDA00ED0B66 /* HighlightMentionBackgroundView.swift */; }; - FD09B7E5288670BB00ED0B66 /* _008_EmojiReacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09B7E4288670BB00ED0B66 /* _008_EmojiReacts.swift */; }; + FD09B7E5288670BB00ED0B66 /* _017_EmojiReacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09B7E4288670BB00ED0B66 /* _017_EmojiReacts.swift */; }; FD09B7E7288670FD00ED0B66 /* Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09B7E6288670FD00ED0B66 /* Reaction.swift */; }; FD09C5E628260FF9000CE219 /* MediaGalleryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E528260FF9000CE219 /* MediaGalleryViewModel.swift */; }; FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E728264937000CE219 /* MediaDetailViewController.swift */; }; @@ -475,18 +472,12 @@ FD16AB5B2A1DD7CA0083D849 /* PlaceholderIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */; }; FD16AB5F2A1DD98F0083D849 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */; }; FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */; }; - FD17D79927F40AB800122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */; }; - FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */; }; + FD17D79927F40AB800122BE0 /* _009_SMK_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79827F40AB800122BE0 /* _009_SMK_YDBToGRDBMigration.swift */; }; FD17D7A127F40D2500122BE0 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD28A4F527EAD44C00FF65E7 /* Storage.swift */; }; - FD17D7A227F40F0500122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */; }; - FD17D7A427F40F8100122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7A327F40F8100122BE0 /* _003_YDBToGRDBMigration.swift */; }; + FD17D7A227F40F0500122BE0 /* _006_SMK_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79527F3E04600122BE0 /* _006_SMK_InitialSetupMigration.swift */; }; FD17D7AE27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */; }; FD17D7B827F51ECA00122BE0 /* Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B727F51ECA00122BE0 /* Migration.swift */; }; - FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */; }; - FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */; }; - FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */; }; FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E427F6A09900122BE0 /* Identity.swift */; }; - FD17D7E727F6A16700122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */; }; FD19363C2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD19363B2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift */; }; FD19363F2ACA66DE004BCF0F /* DatabaseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD19363E2ACA66DE004BCF0F /* DatabaseSpec.swift */; }; FD1A553E2E14BE11003761E4 /* PagedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A553D2E14BE0E003761E4 /* PagedData.swift */; }; @@ -494,7 +485,7 @@ FD1A55432E179AED003761E4 /* ObservableKeyEvent+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A55422E179AE6003761E4 /* ObservableKeyEvent+Utilities.swift */; }; FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */; }; FD1C98E4282E3C5B00B76F9E /* UINavigationBar+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */; }; - FD1D732E2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */; }; + FD1D732E2A86114600E3F410 /* _029_BlockCommunityMessageRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D732D2A86114600E3F410 /* _029_BlockCommunityMessageRequests.swift */; }; FD22726B2C32911C004D8A6C /* SendReadReceiptsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272532C32911A004D8A6C /* SendReadReceiptsJob.swift */; }; FD22726C2C32911C004D8A6C /* GroupLeavingJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272542C32911A004D8A6C /* GroupLeavingJob.swift */; }; FD22726D2C32911C004D8A6C /* CheckForAppUpdatesJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272552C32911A004D8A6C /* CheckForAppUpdatesJob.swift */; }; @@ -544,9 +535,7 @@ FD2272D42C34ECE1004D8A6C /* BencodeEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272D32C34ECE1004D8A6C /* BencodeEncoder.swift */; }; FD2272D62C34ED6A004D8A6C /* RetryWithDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */; }; FD2272D82C34EDE7004D8A6C /* SnodeAPIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272D72C34EDE6004D8A6C /* SnodeAPIEndpoint.swift */; }; - FD2272D92C34EED6004D8A6C /* _001_ThemePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9F828A5F14A003AE748 /* _001_ThemePreferences.swift */; }; FD2272DD2C34EFFA004D8A6C /* AppSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272DC2C34EFFA004D8A6C /* AppSetup.swift */; }; - FD2272DE2C34F11F004D8A6C /* _001_ThemePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9F828A5F14A003AE748 /* _001_ThemePreferences.swift */; }; FD2272E02C3502BE004D8A6C /* Setting+Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272DF2C3502BE004D8A6C /* Setting+Theme.swift */; }; FD2272E62C351378004D8A6C /* SUIKImageFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272E52C351378004D8A6C /* SUIKImageFormat.swift */; }; FD2272EA2C351CA7004D8A6C /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272E92C351CA7004D8A6C /* Threading.swift */; }; @@ -622,8 +611,8 @@ FD336F742CABB97800C0B51B /* PreparedRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0150302CA24310005B08A1 /* PreparedRequestSpec.swift */; }; FD336F752CABB97800C0B51B /* RequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0150312CA24310005B08A1 /* RequestSpec.swift */; }; FD336F762CABB97800C0B51B /* BencodeResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD383722AFDD6D7001367F2 /* BencodeResponseSpec.swift */; }; - FD3559462CC1FF200088F2A9 /* _020_AddMissingWhisperFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3559452CC1FF140088F2A9 /* _020_AddMissingWhisperFlag.swift */; }; - FD368A6829DE8F9C000DBF1E /* _012_AddFTSIfNeeded.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */; }; + FD3559462CC1FF200088F2A9 /* _034_AddMissingWhisperFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3559452CC1FF140088F2A9 /* _034_AddMissingWhisperFlag.swift */; }; + FD368A6829DE8F9C000DBF1E /* _026_AddFTSIfNeeded.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD368A6729DE8F9B000DBF1E /* _026_AddFTSIfNeeded.swift */; }; FD368A6A29DE9E30000DBF1E /* UIContextualAction+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD368A6929DE9E30000DBF1E /* UIContextualAction+Utilities.swift */; }; FD3765DF2AD8F03100DC1489 /* MockSnodeAPICache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765DE2AD8F03100DC1489 /* MockSnodeAPICache.swift */; }; FD3765E02AD8F05100DC1489 /* MockSnodeAPICache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765DE2AD8F03100DC1489 /* MockSnodeAPICache.swift */; }; @@ -651,13 +640,12 @@ FD37EA0128A60473003AE748 /* UIKit+Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0028A60473003AE748 /* UIKit+Theme.swift */; }; FD37EA0328A9FDCC003AE748 /* HelpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0228A9FDCC003AE748 /* HelpViewModel.swift */; }; FD37EA0528AA00C1003AE748 /* NotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0428AA00C1003AE748 /* NotificationSettingsViewModel.swift */; }; - FD37EA0D28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0C28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift */; }; - FD37EA0F28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0E28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift */; }; + FD37EA0D28AB2A45003AE748 /* _013_FixDeletedMessageReadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0C28AB2A45003AE748 /* _013_FixDeletedMessageReadState.swift */; }; + FD37EA0F28AB3330003AE748 /* _014_FixHiddenModAdminSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0E28AB3330003AE748 /* _014_FixHiddenModAdminSupport.swift */; }; FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA1428AB42CB003AE748 /* IdentitySpec.swift */; }; FD37EA1728AC5605003AE748 /* NotificationContentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA1628AC5605003AE748 /* NotificationContentViewModel.swift */; }; FD37EA1928AC5CCA003AE748 /* NotificationSoundViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA1828AC5CCA003AE748 /* NotificationSoundViewModel.swift */; }; FD39352C28F382920084DADA /* VersionFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD39352B28F382920084DADA /* VersionFooterView.swift */; }; - FD39353628F7C3390084DADA /* _004_FlagMessageHashAsDeletedOrInvalid.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD39353528F7C3390084DADA /* _004_FlagMessageHashAsDeletedOrInvalid.swift */; }; FD3AABE928306BBD00E5099A /* ThreadPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */; }; FD3C906727E416AF00CD579F /* BlindedIdLookupSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906627E416AF00CD579F /* BlindedIdLookupSpec.swift */; }; FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */; }; @@ -674,7 +662,7 @@ FD428B192B4B576F006D0888 /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD428B182B4B576F006D0888 /* AppContext.swift */; }; FD428B1B2B4B6098006D0888 /* Notifications+Lifecycle.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD428B1A2B4B6098006D0888 /* Notifications+Lifecycle.swift */; }; FD428B1F2B4B758B006D0888 /* AppReadiness.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD428B1E2B4B758B006D0888 /* AppReadiness.swift */; }; - FD428B232B4B9969006D0888 /* _017_RebuildFTSIfNeeded_2_4_5.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD428B222B4B9969006D0888 /* _017_RebuildFTSIfNeeded_2_4_5.swift */; }; + FD428B232B4B9969006D0888 /* _031_RebuildFTSIfNeeded_2_4_5.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD428B222B4B9969006D0888 /* _031_RebuildFTSIfNeeded_2_4_5.swift */; }; FD42ECCE2E287CD4002D03EA /* ThemeColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD42ECCD2E287CD1002D03EA /* ThemeColor.swift */; }; FD42ECD02E289261002D03EA /* ThemeLinearGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD42ECCF2E289257002D03EA /* ThemeLinearGradient.swift */; }; FD42ECD22E3071DE002D03EA /* ThemeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD42ECD12E3071DC002D03EA /* ThemeText.swift */; }; @@ -703,7 +691,7 @@ FD4BB22B2D63F20700D0DC3D /* MigrationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4BB22A2D63F20600D0DC3D /* MigrationHelper.swift */; }; FD4BB22C2D63FA8600D0DC3D /* (null) in Sources */ = {isa = PBXBuildFile; }; FD4C4E9C2B02E2A300C72199 /* DisplayPictureError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4C4E9B2B02E2A300C72199 /* DisplayPictureError.swift */; }; - FD4C53AF2CC1D62E003B10F4 /* _021_ReworkRecipientState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4C53AE2CC1D61E003B10F4 /* _021_ReworkRecipientState.swift */; }; + FD4C53AF2CC1D62E003B10F4 /* _035_ReworkRecipientState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4C53AE2CC1D61E003B10F4 /* _035_ReworkRecipientState.swift */; }; FD52090328B4680F006098F6 /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090228B4680F006098F6 /* RadioButton.swift */; }; FD52090528B4915F006098F6 /* PrivacySettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */; }; FD52CB5A2E12166F00A4DA70 /* OnboardingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52CB592E12166D00A4DA70 /* OnboardingSpec.swift */; }; @@ -727,7 +715,6 @@ FD5E93D12C100FD70038C25A /* FileUploadResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */; }; FD5E93D22C12B0580038C25A /* AppVersionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383727B3863200C60D73 /* AppVersionResponse.swift */; }; FD61FCF92D308CC9005752DE /* GroupMemberSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD61FCF82D308CC5005752DE /* GroupMemberSpec.swift */; }; - FD61FCFB2D34A5EA005752DE /* _007_SplitSnodeReceivedMessageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD61FCFA2D34A5DE005752DE /* _007_SplitSnodeReceivedMessageInfo.swift */; }; FD65318A2AA025C500DFEEAA /* TestDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6531892AA025C500DFEEAA /* TestDependencies.swift */; }; FD65318B2AA025C500DFEEAA /* TestDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6531892AA025C500DFEEAA /* TestDependencies.swift */; }; FD65318C2AA025C500DFEEAA /* TestDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6531892AA025C500DFEEAA /* TestDependencies.swift */; }; @@ -751,17 +738,15 @@ FD6A393B2C2AD3A300762359 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A393A2C2AD3A300762359 /* Nimble */; }; FD6A393D2C2AD3AC00762359 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A393C2C2AD3AC00762359 /* Nimble */; }; FD6A39412C2AD3B600762359 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A39402C2AD3B600762359 /* Nimble */; }; - FD6A7A6D2818C61500035AC1 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */; }; FD6C67242CF6E72E00B350A7 /* NoopSessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6C67232CF6E72900B350A7 /* NoopSessionCallManager.swift */; }; FD6D9CF92CA152B300F706A8 /* Session+SNUIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754BB2C9B9A8E002A2623 /* Session+SNUIKit.swift */; }; FD6DA9CF2D015B440092085A /* Lucide in Frameworks */ = {isa = PBXBuildFile; productRef = FD6DA9CE2D015B440092085A /* Lucide */; }; FD6DA9D22D0160F10092085A /* Lucide in Frameworks */ = {isa = PBXBuildFile; productRef = FD6DA9D12D0160F10092085A /* Lucide */; }; - FD6DF00B2ACFE40D0084BA4C /* _005_AddSnodeReveivedMessageInfoPrimaryKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6DF00A2ACFE40D0084BA4C /* _005_AddSnodeReveivedMessageInfoPrimaryKey.swift */; }; FD6E4C8A2A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */; }; FD705A92278D051200F16121 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A91278D051200F16121 /* ReusableView.swift */; }; - FD70F25C2DC1F184003729B7 /* _026_MessageDeduplicationTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD70F25B2DC1F176003729B7 /* _026_MessageDeduplicationTable.swift */; }; + FD70F25C2DC1F184003729B7 /* _040_MessageDeduplicationTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD70F25B2DC1F176003729B7 /* _040_MessageDeduplicationTable.swift */; }; FD7115EB28C5D78E00B47552 /* ThreadSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115EA28C5D78E00B47552 /* ThreadSettingsViewModel.swift */; }; - FD7115F228C6CB3900B47552 /* _010_AddThreadIdToFTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */; }; + FD7115F228C6CB3900B47552 /* _019_AddThreadIdToFTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F128C6CB3900B47552 /* _019_AddThreadIdToFTS.swift */; }; FD7115F428C71EB200B47552 /* ThreadDisappearingMessagesSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F328C71EB200B47552 /* ThreadDisappearingMessagesSettingsViewModel.swift */; }; FD7115F828C8151C00B47552 /* DisposableBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F728C8151C00B47552 /* DisposableBarButtonItem.swift */; }; FD7115FA28C8153400B47552 /* UIBarButtonItem+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F928C8153400B47552 /* UIBarButtonItem+Combine.swift */; }; @@ -815,13 +800,13 @@ FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */; }; FD7728962849E7E90018502F /* String+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728952849E7E90018502F /* String+Utilities.swift */; }; FD7728982849E8110018502F /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728972849E8110018502F /* UITableView+ReusableView.swift */; }; - FD778B6429B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */; }; + FD778B6429B189FF001BAC6B /* _028_GenerateInitialUserConfigDumps.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD778B6329B189FF001BAC6B /* _028_GenerateInitialUserConfigDumps.swift */; }; FD78E9EE2DD6D32500D55B50 /* ImageDataManager+Singleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB11A622DD5BDDD00BEF49F /* ImageDataManager+Singleton.swift */; }; FD78E9F02DD6D61200D55B50 /* Data+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754CB2C9BAF37002A2623 /* Data+Image.swift */; }; FD78E9F22DDA9EA200D55B50 /* MockImageDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78E9F12DDA9E9B00D55B50 /* MockImageDataManager.swift */; }; FD78E9F42DDABA4F00D55B50 /* AttachmentUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78E9F32DDABA4200D55B50 /* AttachmentUploader.swift */; }; FD78E9F62DDD43AD00D55B50 /* Mutation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78E9F52DDD43AB00D55B50 /* Mutation.swift */; }; - FD78E9FA2DDD74D200D55B50 /* _027_MoveSettingsToLibSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78E9F72DDD742100D55B50 /* _027_MoveSettingsToLibSession.swift */; }; + FD78E9FA2DDD74D200D55B50 /* _042_MoveSettingsToLibSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78E9F72DDD742100D55B50 /* _042_MoveSettingsToLibSession.swift */; }; FD78E9FD2DDD97F200D55B50 /* Setting.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78E9FC2DDD97F000D55B50 /* Setting.swift */; }; FD78EA022DDEBC3200D55B50 /* DebounceTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78EA012DDEBC2C00D55B50 /* DebounceTaskManager.swift */; }; FD78EA042DDEC3C500D55B50 /* MultiTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78EA032DDEC3C000D55B50 /* MultiTaskManager.swift */; }; @@ -829,7 +814,6 @@ FD78EA0A2DDFE45E00D55B50 /* Interaction+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78EA092DDFE45900D55B50 /* Interaction+UI.swift */; }; FD78EA0B2DDFE45E00D55B50 /* Interaction+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78EA092DDFE45900D55B50 /* Interaction+UI.swift */; }; FD78EA0D2DDFEDE200D55B50 /* LibSession+Local.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78EA0C2DDFEDDF00D55B50 /* LibSession+Local.swift */; }; - FD7F74572BAA9D31006DDFD8 /* _006_DropSnodeCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7F74562BAA9D31006DDFD8 /* _006_DropSnodeCache.swift */; }; FD7F745B2BAAA35E006DDFD8 /* LibSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7F745A2BAAA35E006DDFD8 /* LibSession.swift */; }; FD7F745F2BAAA3B4006DDFD8 /* TypeConversion+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7F745E2BAAA3B4006DDFD8 /* TypeConversion+Utilities.swift */; }; FD83B9B327CF200A005E1583 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; platformFilter = ios; }; @@ -850,7 +834,7 @@ FD860CB62D66913F00BBE29C /* ThemePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CB52D66913B00BBE29C /* ThemePreviewView.swift */; }; FD860CB82D66BC9900BBE29C /* AppIconViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CB72D66BC9500BBE29C /* AppIconViewModel.swift */; }; FD860CBA2D66BF2A00BBE29C /* AppIconGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CB92D66BF2300BBE29C /* AppIconGridView.swift */; }; - FD860CBC2D6E7A9F00BBE29C /* _024_FixBustedInteractionVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CBB2D6E7A9400BBE29C /* _024_FixBustedInteractionVariant.swift */; }; + FD860CBC2D6E7A9F00BBE29C /* _038_FixBustedInteractionVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CBB2D6E7A9400BBE29C /* _038_FixBustedInteractionVariant.swift */; }; FD860CBE2D6E7DAA00BBE29C /* DeveloperSettingsViewModel+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CBD2D6E7DA000BBE29C /* DeveloperSettingsViewModel+Testing.swift */; }; FD860CC92D6ED2ED00BBE29C /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = FD860CC82D6ED2ED00BBE29C /* DifferenceKit */; }; FD86FDA32BC5020600EC251B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = FD86FDA22BC5020600EC251B /* PrivacyInfo.xcprivacy */; }; @@ -873,13 +857,11 @@ FD8A5B252DC05B16004C689B /* Number+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B242DC05B16004C689B /* Number+Utilities.swift */; }; FD8A5B292DC060E2004C689B /* Double+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B282DC060DD004C689B /* Double+Utilities.swift */; }; FD8A5B302DC18D61004C689B /* GeneralCacheSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B2F2DC18D5E004C689B /* GeneralCacheSpec.swift */; }; - FD8A5B322DC191B4004C689B /* _025_DropLegacyClosedGroupKeyPairTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B312DC191AB004C689B /* _025_DropLegacyClosedGroupKeyPairTable.swift */; }; - FD8A5B342DC1A732004C689B /* _008_ResetUserConfigLastHashes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B332DC1A726004C689B /* _008_ResetUserConfigLastHashes.swift */; }; + FD8A5B322DC191B4004C689B /* _039_DropLegacyClosedGroupKeyPairTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B312DC191AB004C689B /* _039_DropLegacyClosedGroupKeyPairTable.swift */; }; FD8ECF7B29340FFD00C0D1BB /* LibSession+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7A29340FFD00C0D1BB /* LibSession+SessionMessagingKit.swift */; }; - FD8ECF7D2934293A00C0D1BB /* _013_SessionUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */; }; + FD8ECF7D2934293A00C0D1BB /* _027_SessionUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _027_SessionUtilChanges.swift */; }; FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */; }; FD8FD7622C37B7BD001E38C7 /* Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71162B28E1451400B47552 /* Position.swift */; }; - FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */; }; FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7432804EF1B004C14C5 /* JobRunner.swift */; }; FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */; }; FD96F3A529DBC3DC00401309 /* MessageSendJobSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */; }; @@ -929,7 +911,7 @@ FDB3DA8D2E24881B00148F8D /* ImageLoading+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB3DA8C2E24881200148F8D /* ImageLoading+Convenience.swift */; }; FDB4BBC72838B91E00B7C95D /* LinkPreviewError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB4BBC62838B91E00B7C95D /* LinkPreviewError.swift */; }; FDB5DAC12A9443A5002C8721 /* MessageSender+Groups.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DAC02A9443A5002C8721 /* MessageSender+Groups.swift */; }; - FDB5DAC72A9447E7002C8721 /* _022_GroupsRebuildChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DAC62A9447E7002C8721 /* _022_GroupsRebuildChanges.swift */; }; + FDB5DAC72A9447E7002C8721 /* _036_GroupsRebuildChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DAC62A9447E7002C8721 /* _036_GroupsRebuildChanges.swift */; }; FDB5DAD42A9483F3002C8721 /* GroupUpdateInviteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DAD32A9483F3002C8721 /* GroupUpdateInviteMessage.swift */; }; FDB5DADA2A95D839002C8721 /* GroupUpdateInfoChangeMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DAD92A95D839002C8721 /* GroupUpdateInfoChangeMessage.swift */; }; FDB5DADC2A95D840002C8721 /* GroupUpdateMemberChangeMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DADB2A95D840002C8721 /* GroupUpdateMemberChangeMessage.swift */; }; @@ -953,7 +935,6 @@ FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */; }; FDB7400D28EBEC240094D718 /* DateHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */; }; FDBA8A842D597975007C19C0 /* FailedGroupInvitesAndPromotionsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBA8A832D59796F007C19C0 /* FailedGroupInvitesAndPromotionsJob.swift */; }; - FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */; }; FDBB25E72988BBBE00F1508E /* UIContextualAction+Theming.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */; }; FDBEE52E2B6A18B900C143A0 /* UserDefaultsConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBEE52D2B6A18B900C143A0 /* UserDefaultsConfig.swift */; }; FDC0F0042BFECE12002CBFB9 /* TimeUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC0F0032BFECE12002CBFB9 /* TimeUnit.swift */; }; @@ -1007,15 +988,33 @@ FDD20C162A09E64A003898FB /* GetExpiriesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD20C152A09E64A003898FB /* GetExpiriesRequest.swift */; }; FDD20C182A09E7D3003898FB /* GetExpiriesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD20C172A09E7D3003898FB /* GetExpiriesResponse.swift */; }; FDD20C1A2A0A03AC003898FB /* DeleteInboxResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD20C192A0A03AC003898FB /* DeleteInboxResponse.swift */; }; + FDD23ADF2E457CAA0057E853 /* _016_ThemePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9F828A5F14A003AE748 /* _016_ThemePreferences.swift */; }; + FDD23AE02E457CD40057E853 /* _004_SNK_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79F27F40CC800122BE0 /* _004_SNK_InitialSetupMigration.swift */; }; + FDD23AE12E457CDE0057E853 /* _005_SNK_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A6C2818C61500035AC1 /* _005_SNK_SetupStandardJobs.swift */; }; + FDD23AE22E457CE50057E853 /* _008_SNK_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7A327F40F8100122BE0 /* _008_SNK_YDBToGRDBMigration.swift */; }; + FDD23AE32E457CFE0057E853 /* _010_FlagMessageHashAsDeletedOrInvalid.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD39353528F7C3390084DADA /* _010_FlagMessageHashAsDeletedOrInvalid.swift */; }; + FDD23AE42E458C810057E853 /* _021_AddSnodeReveivedMessageInfoPrimaryKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6DF00A2ACFE40D0084BA4C /* _021_AddSnodeReveivedMessageInfoPrimaryKey.swift */; }; + FDD23AE52E458C940057E853 /* _022_DropSnodeCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7F74562BAA9D31006DDFD8 /* _022_DropSnodeCache.swift */; }; + FDD23AE62E458CAA0057E853 /* _023_SplitSnodeReceivedMessageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD61FCFA2D34A5DE005752DE /* _023_SplitSnodeReceivedMessageInfo.swift */; }; + FDD23AE72E458DBC0057E853 /* _001_SUK_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C927F546D900122BE0 /* _001_SUK_InitialSetupMigration.swift */; }; + FDD23AE82E458DD40057E853 /* _002_SUK_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SUK_SetupStandardJobs.swift */; }; + FDD23AE92E458E020057E853 /* _003_SUK_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E627F6A16700122BE0 /* _003_SUK_YDBToGRDBMigration.swift */; }; + FDD23AEA2E458EB00057E853 /* _012_AddJobPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E22988B13800F1508E /* _012_AddJobPriority.swift */; }; + FDD23AEB2E458F4D0057E853 /* _020_AddJobUniqueHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945D9C572D6FDBE7003C4C0C /* _020_AddJobUniqueHash.swift */; }; + FDD23AEC2E458F980057E853 /* _024_ResetUserConfigLastHashes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B332DC1A726004C689B /* _024_ResetUserConfigLastHashes.swift */; }; + FDD23AED2E4590A10057E853 /* _041_RenameTableSettingToKeyValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FE22D5181F400E8E413 /* _041_RenameTableSettingToKeyValueStore.swift */; }; + FDD23AEE2E459E470057E853 /* _001_SUK_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C927F546D900122BE0 /* _001_SUK_InitialSetupMigration.swift */; }; + FDD23AEF2E459EC90057E853 /* _012_AddJobPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E22988B13800F1508E /* _012_AddJobPriority.swift */; }; + FDD23AF02E459EDD0057E853 /* _020_AddJobUniqueHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945D9C572D6FDBE7003C4C0C /* _020_AddJobUniqueHash.swift */; }; FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; }; FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; }; FDD82C3F2A205D0A00425F05 /* ProcessResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */; }; - FDDD554E2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDD554D2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift */; }; + FDDD554E2C1FCB77006CBF03 /* _033_ScheduleAppUpdateCheckJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDD554D2C1FCB77006CBF03 /* _033_ScheduleAppUpdateCheckJob.swift */; }; FDDF074429C3E3D000E5E8B5 /* FetchRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */; }; FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */; }; FDE125232A837E4E002DA685 /* MainAppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE125222A837E4E002DA685 /* MainAppContext.swift */; }; FDE33BBC2D5C124900E56F42 /* DispatchTimeInterval+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE33BBB2D5C124300E56F42 /* DispatchTimeInterval+Utilities.swift */; }; - FDE33BBE2D5C3AF100E56F42 /* _023_GroupsExpiredFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE33BBD2D5C3AE800E56F42 /* _023_GroupsExpiredFlag.swift */; }; + FDE33BBE2D5C3AF100E56F42 /* _037_GroupsExpiredFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE33BBD2D5C3AE800E56F42 /* _037_GroupsExpiredFlag.swift */; }; FDE519F72AB7CDC700450C53 /* Result+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE519F62AB7CDC700450C53 /* Result+Utilities.swift */; }; FDE5218E2E03A06B00061B8E /* AttachmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE5218D2E03A06700061B8E /* AttachmentManager.swift */; }; FDE521942E050B1100061B8E /* DismissCallbackAVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE521932E050B0800061B8E /* DismissCallbackAVPlayerViewController.swift */; }; @@ -1066,7 +1065,7 @@ FDE754FA2C9BB0B0002A2623 /* NotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754F52C9BB0AF002A2623 /* NotificationPresenter.swift */; }; FDE754FE2C9BB0D0002A2623 /* Threading+SMK.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754FD2C9BB0D0002A2623 /* Threading+SMK.swift */; }; FDE755002C9BB0FA002A2623 /* SessionEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754FF2C9BB0FA002A2623 /* SessionEnvironment.swift */; }; - FDE755022C9BB122002A2623 /* _011_AddPendingReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755012C9BB122002A2623 /* _011_AddPendingReadReceipts.swift */; }; + FDE755022C9BB122002A2623 /* _025_AddPendingReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755012C9BB122002A2623 /* _025_AddPendingReadReceipts.swift */; }; FDE755052C9BB4EE002A2623 /* BencodeDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755032C9BB4ED002A2623 /* BencodeDecoder.swift */; }; FDE755062C9BB4EE002A2623 /* Bencode.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755042C9BB4ED002A2623 /* Bencode.swift */; }; FDE755182C9BC169002A2623 /* UIAlertAction+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755132C9BC169002A2623 /* UIAlertAction+Utilities.swift */; }; @@ -1087,7 +1086,7 @@ FDEF57712C44D2D300131302 /* GeoLite2-Country-Blocks-IPv4 in Resources */ = {isa = PBXBuildFile; fileRef = FDEF57702C44D2D300131302 /* GeoLite2-Country-Blocks-IPv4 */; }; FDF01FAD2A9ECC4200CAF969 /* SingletonConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF01FAC2A9ECC4200CAF969 /* SingletonConfig.swift */; }; FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */; }; - FDF0B7422804EA4F004C14C5 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7412804EA4F004C14C5 /* _002_SetupStandardJobs.swift */; }; + FDF0B7422804EA4F004C14C5 /* _007_SMK_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7412804EA4F004C14C5 /* _007_SMK_SetupStandardJobs.swift */; }; FDF0B74928060D13004C14C5 /* QuotedReplyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B74828060D13004C14C5 /* QuotedReplyModel.swift */; }; FDF0B74B28061F7A004C14C5 /* InteractionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B74A28061F7A004C14C5 /* InteractionAttachment.swift */; }; FDF0B7512807BA56004C14C5 /* NotificationsManagerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7502807BA56004C14C5 /* NotificationsManagerType.swift */; }; @@ -1101,7 +1100,7 @@ FDF2220F281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */; }; FDF22211281B5E0B000A4995 /* TableRecord+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF22210281B5E0B000A4995 /* TableRecord+Utilities.swift */; }; FDF2F0222DAE1AF500491E8A /* MessageReceiver+LegacyClosedGroups.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF2F0212DAE1AEF00491E8A /* MessageReceiver+LegacyClosedGroups.swift */; }; - FDF40CDE2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */; }; + FDF40CDE2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF40CDD2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift */; }; FDF71EA32B072C2800A8D6B5 /* LibSessionMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF71EA22B072C2800A8D6B5 /* LibSessionMessage.swift */; }; FDF71EA52B07363500A8D6B5 /* MessageReceiver+LibSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF71EA42B07363500A8D6B5 /* MessageReceiver+LibSession.swift */; }; FDF8487F29405994007DCAE5 /* HTTPHeader+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487D29405993007DCAE5 /* HTTPHeader+OpenGroup.swift */; }; @@ -1151,7 +1150,7 @@ FDFDE126282D05380098B17F /* MediaInteractiveDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */; }; FDFDE128282D05530098B17F /* MediaPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE127282D05530098B17F /* MediaPresentationContext.swift */; }; FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */; }; - FDFE75B12ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */; }; + FDFE75B12ABD2D2400655640 /* _030_MakeBrokenProfileTimestampsNullable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFE75B02ABD2D2400655640 /* _030_MakeBrokenProfileTimestampsNullable.swift */; }; FDFE75B42ABD46B600655640 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; }; FDFE75B52ABD46B700655640 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; }; FDFF9FDF2A787F57005E0628 /* JSONEncoder+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */; }; @@ -1458,7 +1457,7 @@ 7B4EF2592934743000CB351D /* SessionTableViewTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTableViewTitleView.swift; sourceTree = ""; }; 7B50D64C28AC7CF80086CCEC /* silence.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = silence.aiff; sourceTree = ""; }; 7B5233C32900E90F00F8F375 /* SessionLabelCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionLabelCarouselView.swift; sourceTree = ""; }; - 7B5233C5290636D700F8F375 /* _018_DisappearingMessagesConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _018_DisappearingMessagesConfiguration.swift; sourceTree = ""; }; + 7B5233C5290636D700F8F375 /* _032_DisappearingMessagesConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _032_DisappearingMessagesConfiguration.swift; sourceTree = ""; }; 7B5802982AAEF1B50050EEB1 /* OpenGroupInvitationView_SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupInvitationView_SwiftUI.swift; sourceTree = ""; }; 7B7037422834B81F000DCF35 /* ReactionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionContainerView.swift; sourceTree = ""; }; 7B7037442834BCC0000DCF35 /* ReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionView.swift; sourceTree = ""; }; @@ -1467,7 +1466,7 @@ 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniCallView.swift; sourceTree = ""; }; 7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallRingTonePlayer.swift; sourceTree = ""; }; 7B81682228A4C1210069F315 /* UpdateTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateTypes.swift; sourceTree = ""; }; - 7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _007_HomeQueryOptimisationIndexes.swift; sourceTree = ""; }; + 7B81682728B310D50069F315 /* _015_HomeQueryOptimisationIndexes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _015_HomeQueryOptimisationIndexes.swift; sourceTree = ""; }; 7B81682928B6F1420069F315 /* ReactionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionResponse.swift; sourceTree = ""; }; 7B81682B28B72F480069F315 /* PendingChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingChange.swift; sourceTree = ""; }; 7B81FB582AB01AA8002FB267 /* LoadingIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicatorView.swift; sourceTree = ""; }; @@ -1490,7 +1489,7 @@ 7BA68908272A27BE00EFC32F /* SessionCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCall.swift; sourceTree = ""; }; 7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+CXCallController.swift"; sourceTree = ""; }; 7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+CXProvider.swift"; sourceTree = ""; }; - 7BAA7B6528D2DE4700AE1489 /* _009_OpenGroupPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _009_OpenGroupPermission.swift; sourceTree = ""; }; + 7BAA7B6528D2DE4700AE1489 /* _018_OpenGroupPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _018_OpenGroupPermission.swift; sourceTree = ""; }; 7BAADFCB27B0EF23007BCF92 /* CallVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVideoView.swift; sourceTree = ""; }; 7BAADFCD27B215FE007BCF92 /* UIView+Draggable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Draggable.swift"; sourceTree = ""; }; 7BAF54CC27ACCEEC003D12F8 /* GlobalSearchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalSearchViewController.swift; sourceTree = ""; }; @@ -1542,7 +1541,7 @@ 94367C422C6C828500814252 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; 943C6D812B75E061004ACE64 /* Message+DisappearingMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+DisappearingMessages.swift"; sourceTree = ""; }; 943C6D832B86B5F1004ACE64 /* Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; - 945D9C572D6FDBE7003C4C0C /* _005_AddJobUniqueHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _005_AddJobUniqueHash.swift; sourceTree = ""; }; + 945D9C572D6FDBE7003C4C0C /* _020_AddJobUniqueHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _020_AddJobUniqueHash.swift; sourceTree = ""; }; 9471CAA72CACFB4E00090FB7 /* GenerateLicenses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateLicenses.swift; sourceTree = ""; }; 9473386D2BDF5F3E00B9E169 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; 9479981B2DD44AC5008F5CD5 /* ThreadNotificationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadNotificationSettingsViewModel.swift; sourceTree = ""; }; @@ -1554,7 +1553,7 @@ 947D7FD92D5180F200E8E413 /* SessionNetworkScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionNetworkScreen.swift; sourceTree = ""; }; 947D7FDA2D5180F200E8E413 /* SessionNetworkScreen+Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkScreen+Models.swift"; sourceTree = ""; }; 947D7FDB2D5180F200E8E413 /* SessionNetworkScreen+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkScreen+ViewModel.swift"; sourceTree = ""; }; - 947D7FE22D5181F400E8E413 /* _006_RenameTableSettingToKeyValueStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _006_RenameTableSettingToKeyValueStore.swift; sourceTree = ""; }; + 947D7FE22D5181F400E8E413 /* _041_RenameTableSettingToKeyValueStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _041_RenameTableSettingToKeyValueStore.swift; sourceTree = ""; }; 947D7FE42D51837200E8E413 /* ArrowCapsule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowCapsule.swift; sourceTree = ""; }; 947D7FE52D51837200E8E413 /* PopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverView.swift; sourceTree = ""; }; 947D7FE62D51837200E8E413 /* Text+CopyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Text+CopyButton.swift"; sourceTree = ""; }; @@ -1572,7 +1571,7 @@ 94C58AC82D2E036E00609195 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = ""; }; 94CD95BA2E08D9D40097754D /* SessionProBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionProBadge.swift; sourceTree = ""; }; 94CD95BC2E0908340097754D /* LibSession+Pro.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LibSession+Pro.swift"; sourceTree = ""; }; - 94CD95C02E0CBF1C0097754D /* _029_AddProMessageFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _029_AddProMessageFlag.swift; sourceTree = ""; }; + 94CD95C02E0CBF1C0097754D /* _044_AddProMessageFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _044_AddProMessageFlag.swift; sourceTree = ""; }; 94CD962A2E1B85920097754D /* InputTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputTextView.swift; sourceTree = ""; }; 94CD962B2E1B85920097754D /* InputViewButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputViewButton.swift; sourceTree = ""; }; 94CD962F2E1B88430097754D /* CGRect+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGRect+Utilities.swift"; sourceTree = ""; }; @@ -1743,7 +1742,6 @@ C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionNetworkingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C3C2A5A1255385C100C340D1 /* SessionNetworkingKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionNetworkingKit.h; sourceTree = ""; }; C3C2A5A2255385C100C340D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C3C2A5B9255385ED00C340D1 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; C3C2A5CE2553860700C340D1 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; C3C2A5D22553860900C340D1 /* String+Trimming.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Trimming.swift"; sourceTree = ""; }; C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Utilities.swift"; sourceTree = ""; }; @@ -1802,7 +1800,7 @@ FD02CC132C3677E6009AB976 /* Request+OpenGroupAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Request+OpenGroupAPI.swift"; sourceTree = ""; }; FD02CC152C3681EF009AB976 /* RevokeSubaccountResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RevokeSubaccountResponse.swift; sourceTree = ""; }; FD05593C2DFA3A2200DC48CE /* VoipPayloadKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipPayloadKey.swift; sourceTree = ""; }; - FD05594D2E012D1A00DC48CE /* _028_RenameAttachments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _028_RenameAttachments.swift; sourceTree = ""; }; + FD05594D2E012D1A00DC48CE /* _043_RenameAttachments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _043_RenameAttachments.swift; sourceTree = ""; }; FD0559542E026CC900DC48CE /* ObservingDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservingDatabase.swift; sourceTree = ""; }; FD0606C22BCE13ED00C3816E /* MessageRequestFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestFooterView.swift; sourceTree = ""; }; FD0969F82A69FFE700C5C365 /* Mocked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mocked.swift; sourceTree = ""; }; @@ -1821,7 +1819,7 @@ FD09799827FFC1A300936362 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = ""; }; FD09799A27FFC82D00936362 /* Quote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quote.swift; sourceTree = ""; }; FD09B7E228865FDA00ED0B66 /* HighlightMentionBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightMentionBackgroundView.swift; sourceTree = ""; }; - FD09B7E4288670BB00ED0B66 /* _008_EmojiReacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _008_EmojiReacts.swift; sourceTree = ""; }; + FD09B7E4288670BB00ED0B66 /* _017_EmojiReacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _017_EmojiReacts.swift; sourceTree = ""; }; FD09B7E6288670FD00ED0B66 /* Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reaction.swift; sourceTree = ""; }; FD09C5E528260FF9000CE219 /* MediaGalleryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryViewModel.swift; sourceTree = ""; }; FD09C5E728264937000CE219 /* MediaDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDetailViewController.swift; sourceTree = ""; }; @@ -1841,19 +1839,17 @@ FD12A8462AD63C3400EEBA0D /* PagedObservationSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedObservationSource.swift; sourceTree = ""; }; FD12A8482AD63C4700EEBA0D /* SessionNavItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionNavItem.swift; sourceTree = ""; }; FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfilePictureView+Convenience.swift"; sourceTree = ""; }; - FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = ""; }; - FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = ""; }; - FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = ""; }; - FD17D7A327F40F8100122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = ""; }; + FD17D79527F3E04600122BE0 /* _006_SMK_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _006_SMK_InitialSetupMigration.swift; sourceTree = ""; }; + FD17D79827F40AB800122BE0 /* _009_SMK_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _009_SMK_YDBToGRDBMigration.swift; sourceTree = ""; }; + FD17D79F27F40CC800122BE0 /* _004_SNK_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_SNK_InitialSetupMigration.swift; sourceTree = ""; }; + FD17D7A327F40F8100122BE0 /* _008_SNK_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _008_SNK_YDBToGRDBMigration.swift; sourceTree = ""; }; FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeReceivedMessageInfo.swift; sourceTree = ""; }; FD17D7AF27F4225C00122BE0 /* Set+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Utilities.swift"; sourceTree = ""; }; FD17D7B727F51ECA00122BE0 /* Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Migration.swift; sourceTree = ""; }; - FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetMigrations.swift; sourceTree = ""; }; FD17D7BE27F51F8200122BE0 /* ColumnExpressible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnExpressible.swift; sourceTree = ""; }; - FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DatabaseMigrator+Utilities.swift"; sourceTree = ""; }; - FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = ""; }; + FD17D7C927F546D900122BE0 /* _001_SUK_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_SUK_InitialSetupMigration.swift; sourceTree = ""; }; FD17D7E427F6A09900122BE0 /* Identity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Identity.swift; sourceTree = ""; }; - FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = ""; }; + FD17D7E627F6A16700122BE0 /* _003_SUK_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_SUK_YDBToGRDBMigration.swift; sourceTree = ""; }; FD19363B2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ResponseInfo+SnodeAPI.swift"; sourceTree = ""; }; FD19363E2ACA66DE004BCF0F /* DatabaseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseSpec.swift; sourceTree = ""; }; FD1A553D2E14BE0E003761E4 /* PagedData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedData.swift; sourceTree = ""; }; @@ -1861,7 +1857,7 @@ FD1A55422E179AE6003761E4 /* ObservableKeyEvent+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObservableKeyEvent+Utilities.swift"; sourceTree = ""; }; FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PersistableRecord+Utilities.swift"; sourceTree = ""; }; FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Utilities.swift"; sourceTree = ""; }; - FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _015_BlockCommunityMessageRequests.swift; sourceTree = ""; }; + FD1D732D2A86114600E3F410 /* _029_BlockCommunityMessageRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _029_BlockCommunityMessageRequests.swift; sourceTree = ""; }; FD2272532C32911A004D8A6C /* SendReadReceiptsJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendReadReceiptsJob.swift; sourceTree = ""; }; FD2272542C32911A004D8A6C /* GroupLeavingJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupLeavingJob.swift; sourceTree = ""; }; FD2272552C32911A004D8A6C /* CheckForAppUpdatesJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckForAppUpdatesJob.swift; sourceTree = ""; }; @@ -1943,8 +1939,8 @@ FD336F5F2CAA28CF00C0B51B /* MockSwarmPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSwarmPoller.swift; sourceTree = ""; }; FD336F6B2CAA29C200C0B51B /* CommunityPollerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityPollerSpec.swift; sourceTree = ""; }; FD336F6E2CAA37CB00C0B51B /* MockCommunityPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCommunityPoller.swift; sourceTree = ""; }; - FD3559452CC1FF140088F2A9 /* _020_AddMissingWhisperFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _020_AddMissingWhisperFlag.swift; sourceTree = ""; }; - FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _012_AddFTSIfNeeded.swift; sourceTree = ""; }; + FD3559452CC1FF140088F2A9 /* _034_AddMissingWhisperFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _034_AddMissingWhisperFlag.swift; sourceTree = ""; }; + FD368A6729DE8F9B000DBF1E /* _026_AddFTSIfNeeded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _026_AddFTSIfNeeded.swift; sourceTree = ""; }; FD368A6929DE9E30000DBF1E /* UIContextualAction+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Utilities.swift"; sourceTree = ""; }; FD3765DE2AD8F03100DC1489 /* MockSnodeAPICache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSnodeAPICache.swift; sourceTree = ""; }; FD3765E12AD8F53B00DC1489 /* CommonSSKMockExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonSSKMockExtensions.swift; sourceTree = ""; }; @@ -1965,7 +1961,7 @@ FD37E9DA28A244E9003AE748 /* ThemeMessagePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeMessagePreviewView.swift; sourceTree = ""; }; FD37E9DC28A384EB003AE748 /* PrimaryColorSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryColorSelectionView.swift; sourceTree = ""; }; FD37E9F528A5F106003AE748 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; - FD37E9F828A5F14A003AE748 /* _001_ThemePreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_ThemePreferences.swift; sourceTree = ""; }; + FD37E9F828A5F14A003AE748 /* _016_ThemePreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _016_ThemePreferences.swift; sourceTree = ""; }; FD37E9FE28A5F2CD003AE748 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; FD37EA0028A60473003AE748 /* UIKit+Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIKit+Theme.swift"; sourceTree = ""; }; FD37EA0228A9FDCC003AE748 /* HelpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpViewModel.swift; sourceTree = ""; }; @@ -1973,13 +1969,13 @@ FD37EA0628AA2CCA003AE748 /* SessionTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTableViewController.swift; sourceTree = ""; }; FD37EA0828AA2D27003AE748 /* SessionTableViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTableViewModel.swift; sourceTree = ""; }; FD37EA0A28AB12E2003AE748 /* SessionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCell.swift; sourceTree = ""; }; - FD37EA0C28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _005_FixDeletedMessageReadState.swift; sourceTree = ""; }; - FD37EA0E28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _006_FixHiddenModAdminSupport.swift; sourceTree = ""; }; + FD37EA0C28AB2A45003AE748 /* _013_FixDeletedMessageReadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_FixDeletedMessageReadState.swift; sourceTree = ""; }; + FD37EA0E28AB3330003AE748 /* _014_FixHiddenModAdminSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _014_FixHiddenModAdminSupport.swift; sourceTree = ""; }; FD37EA1428AB42CB003AE748 /* IdentitySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentitySpec.swift; sourceTree = ""; }; FD37EA1628AC5605003AE748 /* NotificationContentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentViewModel.swift; sourceTree = ""; }; FD37EA1828AC5CCA003AE748 /* NotificationSoundViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSoundViewModel.swift; sourceTree = ""; }; FD39352B28F382920084DADA /* VersionFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionFooterView.swift; sourceTree = ""; }; - FD39353528F7C3390084DADA /* _004_FlagMessageHashAsDeletedOrInvalid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_FlagMessageHashAsDeletedOrInvalid.swift; sourceTree = ""; }; + FD39353528F7C3390084DADA /* _010_FlagMessageHashAsDeletedOrInvalid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _010_FlagMessageHashAsDeletedOrInvalid.swift; sourceTree = ""; }; FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerViewModel.swift; sourceTree = ""; }; FD3C906627E416AF00CD579F /* BlindedIdLookupSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlindedIdLookupSpec.swift; sourceTree = ""; }; FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModel.swift; sourceTree = ""; }; @@ -1995,7 +1991,7 @@ FD428B182B4B576F006D0888 /* AppContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContext.swift; sourceTree = ""; }; FD428B1A2B4B6098006D0888 /* Notifications+Lifecycle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notifications+Lifecycle.swift"; sourceTree = ""; }; FD428B1E2B4B758B006D0888 /* AppReadiness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReadiness.swift; sourceTree = ""; }; - FD428B222B4B9969006D0888 /* _017_RebuildFTSIfNeeded_2_4_5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _017_RebuildFTSIfNeeded_2_4_5.swift; sourceTree = ""; }; + FD428B222B4B9969006D0888 /* _031_RebuildFTSIfNeeded_2_4_5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _031_RebuildFTSIfNeeded_2_4_5.swift; sourceTree = ""; }; FD42ECCD2E287CD1002D03EA /* ThemeColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeColor.swift; sourceTree = ""; }; FD42ECCF2E289257002D03EA /* ThemeLinearGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeLinearGradient.swift; sourceTree = ""; }; FD42ECD12E3071DC002D03EA /* ThemeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeText.swift; sourceTree = ""; }; @@ -2014,7 +2010,7 @@ FD4B200D283492210034334B /* InsetLockableTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetLockableTableView.swift; sourceTree = ""; }; FD4BB22A2D63F20600D0DC3D /* MigrationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationHelper.swift; sourceTree = ""; }; FD4C4E9B2B02E2A300C72199 /* DisplayPictureError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayPictureError.swift; sourceTree = ""; }; - FD4C53AE2CC1D61E003B10F4 /* _021_ReworkRecipientState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _021_ReworkRecipientState.swift; sourceTree = ""; }; + FD4C53AE2CC1D61E003B10F4 /* _035_ReworkRecipientState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _035_ReworkRecipientState.swift; sourceTree = ""; }; FD52090228B4680F006098F6 /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettingsViewModel.swift; sourceTree = ""; }; FD52090628B49738006098F6 /* ConfirmationModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationModal.swift; sourceTree = ""; }; @@ -2037,22 +2033,22 @@ FD5CE3442A3C5D96001A6DE3 /* DecryptExportedKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecryptExportedKey.swift; sourceTree = ""; }; FD5D201D27B0D87C00FEA984 /* SessionId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionId.swift; sourceTree = ""; }; FD61FCF82D308CC5005752DE /* GroupMemberSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberSpec.swift; sourceTree = ""; }; - FD61FCFA2D34A5DE005752DE /* _007_SplitSnodeReceivedMessageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _007_SplitSnodeReceivedMessageInfo.swift; sourceTree = ""; }; + FD61FCFA2D34A5DE005752DE /* _023_SplitSnodeReceivedMessageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _023_SplitSnodeReceivedMessageInfo.swift; sourceTree = ""; }; FD6531892AA025C500DFEEAA /* TestDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDependencies.swift; sourceTree = ""; }; FD6673FC2D77F54400041530 /* ScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockViewController.swift; sourceTree = ""; }; FD6673FE2D77F9BE00041530 /* ScreenLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLock.swift; sourceTree = ""; }; FD66CB272BF3449B00268FAB /* SessionNetworkingKit.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SessionNetworkingKit.xctestplan; sourceTree = ""; }; FD66CB2B2BF344C600268FAB /* SessionMessageKit.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = SessionMessageKit.xctestplan; sourceTree = ""; }; FD6A38F02C2A66B100762359 /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = ""; }; - FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; + FD6A7A6C2818C61500035AC1 /* _005_SNK_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _005_SNK_SetupStandardJobs.swift; sourceTree = ""; }; FD6C67232CF6E72900B350A7 /* NoopSessionCallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopSessionCallManager.swift; sourceTree = ""; }; - FD6DF00A2ACFE40D0084BA4C /* _005_AddSnodeReveivedMessageInfoPrimaryKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _005_AddSnodeReveivedMessageInfoPrimaryKey.swift; sourceTree = ""; }; + FD6DF00A2ACFE40D0084BA4C /* _021_AddSnodeReveivedMessageInfoPrimaryKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _021_AddSnodeReveivedMessageInfoPrimaryKey.swift; sourceTree = ""; }; FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyUnsubscribeRequest.swift; sourceTree = ""; }; FD705A91278D051200F16121 /* ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = ""; }; - FD70F25B2DC1F176003729B7 /* _026_MessageDeduplicationTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _026_MessageDeduplicationTable.swift; sourceTree = ""; }; + FD70F25B2DC1F176003729B7 /* _040_MessageDeduplicationTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _040_MessageDeduplicationTable.swift; sourceTree = ""; }; FD7115EA28C5D78E00B47552 /* ThreadSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSettingsViewModel.swift; sourceTree = ""; }; FD7115EF28C5D7DE00B47552 /* SessionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionHeaderView.swift; sourceTree = ""; }; - FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _010_AddThreadIdToFTS.swift; sourceTree = ""; }; + FD7115F128C6CB3900B47552 /* _019_AddThreadIdToFTS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _019_AddThreadIdToFTS.swift; sourceTree = ""; }; FD7115F328C71EB200B47552 /* ThreadDisappearingMessagesSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDisappearingMessagesSettingsViewModel.swift; sourceTree = ""; }; FD7115F728C8151C00B47552 /* DisposableBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisposableBarButtonItem.swift; sourceTree = ""; }; FD7115F928C8153400B47552 /* UIBarButtonItem+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem+Combine.swift"; sourceTree = ""; }; @@ -2097,18 +2093,18 @@ FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModelSpec.swift; sourceTree = ""; }; FD7728952849E7E90018502F /* String+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Utilities.swift"; sourceTree = ""; }; FD7728972849E8110018502F /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = ""; }; - FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _014_GenerateInitialUserConfigDumps.swift; sourceTree = ""; }; + FD778B6329B189FF001BAC6B /* _028_GenerateInitialUserConfigDumps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _028_GenerateInitialUserConfigDumps.swift; sourceTree = ""; }; FD78E9F12DDA9E9B00D55B50 /* MockImageDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockImageDataManager.swift; sourceTree = ""; }; FD78E9F32DDABA4200D55B50 /* AttachmentUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentUploader.swift; sourceTree = ""; }; FD78E9F52DDD43AB00D55B50 /* Mutation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mutation.swift; sourceTree = ""; }; - FD78E9F72DDD742100D55B50 /* _027_MoveSettingsToLibSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _027_MoveSettingsToLibSession.swift; sourceTree = ""; }; + FD78E9F72DDD742100D55B50 /* _042_MoveSettingsToLibSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _042_MoveSettingsToLibSession.swift; sourceTree = ""; }; FD78E9FC2DDD97F000D55B50 /* Setting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Setting.swift; sourceTree = ""; }; FD78EA012DDEBC2C00D55B50 /* DebounceTaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebounceTaskManager.swift; sourceTree = ""; }; FD78EA032DDEC3C000D55B50 /* MultiTaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiTaskManager.swift; sourceTree = ""; }; FD78EA052DDEC8F100D55B50 /* AsyncSequence+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncSequence+Utilities.swift"; sourceTree = ""; }; FD78EA092DDFE45900D55B50 /* Interaction+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Interaction+UI.swift"; sourceTree = ""; }; FD78EA0C2DDFEDDF00D55B50 /* LibSession+Local.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LibSession+Local.swift"; sourceTree = ""; }; - FD7F74562BAA9D31006DDFD8 /* _006_DropSnodeCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _006_DropSnodeCache.swift; sourceTree = ""; }; + FD7F74562BAA9D31006DDFD8 /* _022_DropSnodeCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _022_DropSnodeCache.swift; sourceTree = ""; }; FD7F745A2BAAA35E006DDFD8 /* LibSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSession.swift; sourceTree = ""; }; FD7F745E2BAAA3B4006DDFD8 /* TypeConversion+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TypeConversion+Utilities.swift"; sourceTree = ""; }; FD7F74692BAB8A6D006DDFD8 /* LibSession+Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LibSession+Networking.swift"; sourceTree = ""; }; @@ -2134,7 +2130,7 @@ FD860CB52D66913B00BBE29C /* ThemePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreviewView.swift; sourceTree = ""; }; FD860CB72D66BC9500BBE29C /* AppIconViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconViewModel.swift; sourceTree = ""; }; FD860CB92D66BF2300BBE29C /* AppIconGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconGridView.swift; sourceTree = ""; }; - FD860CBB2D6E7A9400BBE29C /* _024_FixBustedInteractionVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _024_FixBustedInteractionVariant.swift; sourceTree = ""; }; + FD860CBB2D6E7A9400BBE29C /* _038_FixBustedInteractionVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _038_FixBustedInteractionVariant.swift; sourceTree = ""; }; FD860CBD2D6E7DA000BBE29C /* DeveloperSettingsViewModel+Testing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperSettingsViewModel+Testing.swift"; sourceTree = ""; }; FD86FDA22BC5020600EC251B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; FD87DCF928B74DB300AF0F98 /* ConversationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSettingsViewModel.swift; sourceTree = ""; }; @@ -2146,12 +2142,12 @@ FD8A5B242DC05B16004C689B /* Number+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Number+Utilities.swift"; sourceTree = ""; }; FD8A5B282DC060DD004C689B /* Double+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Utilities.swift"; sourceTree = ""; }; FD8A5B2F2DC18D5E004C689B /* GeneralCacheSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralCacheSpec.swift; sourceTree = ""; }; - FD8A5B312DC191AB004C689B /* _025_DropLegacyClosedGroupKeyPairTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _025_DropLegacyClosedGroupKeyPairTable.swift; sourceTree = ""; }; - FD8A5B332DC1A726004C689B /* _008_ResetUserConfigLastHashes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _008_ResetUserConfigLastHashes.swift; sourceTree = ""; }; + FD8A5B312DC191AB004C689B /* _039_DropLegacyClosedGroupKeyPairTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _039_DropLegacyClosedGroupKeyPairTable.swift; sourceTree = ""; }; + FD8A5B332DC1A726004C689B /* _024_ResetUserConfigLastHashes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _024_ResetUserConfigLastHashes.swift; sourceTree = ""; }; FD8ECF7A29340FFD00C0D1BB /* LibSession+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LibSession+SessionMessagingKit.swift"; sourceTree = ""; }; - FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_SessionUtilChanges.swift; sourceTree = ""; }; + FD8ECF7C2934293A00C0D1BB /* _027_SessionUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _027_SessionUtilChanges.swift; sourceTree = ""; }; FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigDump.swift; sourceTree = ""; }; - FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; + FD9004132818AD0B00ABAAF6 /* _002_SUK_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SUK_SetupStandardJobs.swift; sourceTree = ""; }; FD9401CE2ABD04AC003A4834 /* TRANSLATIONS.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = TRANSLATIONS.md; sourceTree = ""; }; FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSendJobSpec.swift; sourceTree = ""; }; FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARC4RandomNumberGenerator.swift; sourceTree = ""; }; @@ -2194,7 +2190,7 @@ FDB3DA8C2E24881200148F8D /* ImageLoading+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImageLoading+Convenience.swift"; sourceTree = ""; }; FDB4BBC62838B91E00B7C95D /* LinkPreviewError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewError.swift; sourceTree = ""; }; FDB5DAC02A9443A5002C8721 /* MessageSender+Groups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageSender+Groups.swift"; sourceTree = ""; }; - FDB5DAC62A9447E7002C8721 /* _022_GroupsRebuildChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _022_GroupsRebuildChanges.swift; sourceTree = ""; }; + FDB5DAC62A9447E7002C8721 /* _036_GroupsRebuildChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _036_GroupsRebuildChanges.swift; sourceTree = ""; }; FDB5DAD32A9483F3002C8721 /* GroupUpdateInviteMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupUpdateInviteMessage.swift; sourceTree = ""; }; FDB5DAD92A95D839002C8721 /* GroupUpdateInfoChangeMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupUpdateInfoChangeMessage.swift; sourceTree = ""; }; FDB5DADB2A95D840002C8721 /* GroupUpdateMemberChangeMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupUpdateMemberChangeMessage.swift; sourceTree = ""; }; @@ -2210,7 +2206,7 @@ FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Utilities.swift"; sourceTree = ""; }; FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateHeaderCell.swift; sourceTree = ""; }; FDBA8A832D59796F007C19C0 /* FailedGroupInvitesAndPromotionsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedGroupInvitesAndPromotionsJob.swift; sourceTree = ""; }; - FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_AddJobPriority.swift; sourceTree = ""; }; + FDBB25E22988B13800F1508E /* _012_AddJobPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _012_AddJobPriority.swift; sourceTree = ""; }; FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Theming.swift"; sourceTree = ""; }; FDBEE52D2B6A18B900C143A0 /* UserDefaultsConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsConfig.swift; sourceTree = ""; }; FDC0F0032BFECE12002CBFB9 /* TimeUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeUnit.swift; sourceTree = ""; }; @@ -2272,12 +2268,12 @@ FDD383702AFDD0E1001367F2 /* BencodeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BencodeResponse.swift; sourceTree = ""; }; FDD383722AFDD6D7001367F2 /* BencodeResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BencodeResponseSpec.swift; sourceTree = ""; }; FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessResult.swift; sourceTree = ""; }; - FDDD554D2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _019_ScheduleAppUpdateCheckJob.swift; sourceTree = ""; }; + FDDD554D2C1FCB77006CBF03 /* _033_ScheduleAppUpdateCheckJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _033_ScheduleAppUpdateCheckJob.swift; sourceTree = ""; }; FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FetchRequest+Utilities.swift"; sourceTree = ""; }; FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerSpec.swift; sourceTree = ""; }; FDE125222A837E4E002DA685 /* MainAppContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainAppContext.swift; sourceTree = ""; }; FDE33BBB2D5C124300E56F42 /* DispatchTimeInterval+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchTimeInterval+Utilities.swift"; sourceTree = ""; }; - FDE33BBD2D5C3AE800E56F42 /* _023_GroupsExpiredFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _023_GroupsExpiredFlag.swift; sourceTree = ""; }; + FDE33BBD2D5C3AE800E56F42 /* _037_GroupsExpiredFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _037_GroupsExpiredFlag.swift; sourceTree = ""; }; FDE519F62AB7CDC700450C53 /* Result+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Utilities.swift"; sourceTree = ""; }; FDE5218D2E03A06700061B8E /* AttachmentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentManager.swift; sourceTree = ""; }; FDE521932E050B0800061B8E /* DismissCallbackAVPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissCallbackAVPlayerViewController.swift; sourceTree = ""; }; @@ -2331,7 +2327,7 @@ FDE754F52C9BB0AF002A2623 /* NotificationPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationPresenter.swift; sourceTree = ""; }; FDE754FD2C9BB0D0002A2623 /* Threading+SMK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Threading+SMK.swift"; sourceTree = ""; }; FDE754FF2C9BB0FA002A2623 /* SessionEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionEnvironment.swift; sourceTree = ""; }; - FDE755012C9BB122002A2623 /* _011_AddPendingReadReceipts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _011_AddPendingReadReceipts.swift; sourceTree = ""; }; + FDE755012C9BB122002A2623 /* _025_AddPendingReadReceipts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _025_AddPendingReadReceipts.swift; sourceTree = ""; }; FDE755032C9BB4ED002A2623 /* BencodeDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BencodeDecoder.swift; sourceTree = ""; }; FDE755042C9BB4ED002A2623 /* Bencode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bencode.swift; sourceTree = ""; }; FDE755132C9BC169002A2623 /* UIAlertAction+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertAction+Utilities.swift"; sourceTree = ""; }; @@ -2358,7 +2354,7 @@ FDF01FAC2A9ECC4200CAF969 /* SingletonConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingletonConfig.swift; sourceTree = ""; }; FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreview.swift; sourceTree = ""; }; FDF0B73F280402C4004C14C5 /* Job.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Job.swift; sourceTree = ""; }; - FDF0B7412804EA4F004C14C5 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; + FDF0B7412804EA4F004C14C5 /* _007_SMK_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _007_SMK_SetupStandardJobs.swift; sourceTree = ""; }; FDF0B7432804EF1B004C14C5 /* JobRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunner.swift; sourceTree = ""; }; FDF0B74828060D13004C14C5 /* QuotedReplyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuotedReplyModel.swift; sourceTree = ""; }; FDF0B74A28061F7A004C14C5 /* InteractionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionAttachment.swift; sourceTree = ""; }; @@ -2373,7 +2369,7 @@ FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueryInterfaceRequest+Utilities.swift"; sourceTree = ""; }; FDF22210281B5E0B000A4995 /* TableRecord+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TableRecord+Utilities.swift"; sourceTree = ""; }; FDF2F0212DAE1AEF00491E8A /* MessageReceiver+LegacyClosedGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+LegacyClosedGroups.swift"; sourceTree = ""; }; - FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_RemoveLegacyYDB.swift; sourceTree = ""; }; + FDF40CDD2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _011_RemoveLegacyYDB.swift; sourceTree = ""; }; FDF71EA22B072C2800A8D6B5 /* LibSessionMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionMessage.swift; sourceTree = ""; }; FDF71EA42B07363500A8D6B5 /* MessageReceiver+LibSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+LibSession.swift"; sourceTree = ""; }; FDF8487D29405993007DCAE5 /* HTTPHeader+OpenGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPHeader+OpenGroup.swift"; sourceTree = ""; }; @@ -2422,7 +2418,7 @@ FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInteractiveDismiss.swift; sourceTree = ""; }; FDFDE127282D05530098B17F /* MediaPresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPresentationContext.swift; sourceTree = ""; }; FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaZoomAnimationController.swift; sourceTree = ""; }; - FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _016_MakeBrokenProfileTimestampsNullable.swift; sourceTree = ""; }; + FDFE75B02ABD2D2400655640 /* _030_MakeBrokenProfileTimestampsNullable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _030_MakeBrokenProfileTimestampsNullable.swift; sourceTree = ""; }; FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONEncoder+Utilities.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -2979,7 +2975,6 @@ B8A582AB258C64E800AFD84C /* Database */ = { isa = PBXGroup; children = ( - FD17D7C827F546CE00122BE0 /* Migrations */, FD17D7CB27F546F500122BE0 /* Models */, FD17D7B427F51E6700122BE0 /* Types */, FD17D7BB27F51F5C00122BE0 /* Utilities */, @@ -3676,7 +3671,6 @@ FD2272842C33E28D004D8A6C /* SnodeAPI */, FDF8488F29405C13007DCAE5 /* Types */, C3C2A5CD255385F300C340D1 /* Utilities */, - C3C2A5B9255385ED00C340D1 /* Configuration.swift */, ); path = SessionNetworkingKit; sourceTree = ""; @@ -4012,35 +4006,50 @@ FD17D79427F3E03300122BE0 /* Migrations */ = { isa = PBXGroup; children = ( - FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */, - FDF0B7412804EA4F004C14C5 /* _002_SetupStandardJobs.swift */, - FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */, - FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */, - FD37EA0C28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift */, - FD37EA0E28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift */, - 7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */, - FD09B7E4288670BB00ED0B66 /* _008_EmojiReacts.swift */, - 7BAA7B6528D2DE4700AE1489 /* _009_OpenGroupPermission.swift */, - FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */, - FDE755012C9BB122002A2623 /* _011_AddPendingReadReceipts.swift */, - FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */, - FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */, - FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */, - FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */, - FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */, - FD428B222B4B9969006D0888 /* _017_RebuildFTSIfNeeded_2_4_5.swift */, - 7B5233C5290636D700F8F375 /* _018_DisappearingMessagesConfiguration.swift */, - FDDD554D2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift */, - FD3559452CC1FF140088F2A9 /* _020_AddMissingWhisperFlag.swift */, - FD4C53AE2CC1D61E003B10F4 /* _021_ReworkRecipientState.swift */, - FDB5DAC62A9447E7002C8721 /* _022_GroupsRebuildChanges.swift */, - FDE33BBD2D5C3AE800E56F42 /* _023_GroupsExpiredFlag.swift */, - FD860CBB2D6E7A9400BBE29C /* _024_FixBustedInteractionVariant.swift */, - FD8A5B312DC191AB004C689B /* _025_DropLegacyClosedGroupKeyPairTable.swift */, - FD70F25B2DC1F176003729B7 /* _026_MessageDeduplicationTable.swift */, - FD78E9F72DDD742100D55B50 /* _027_MoveSettingsToLibSession.swift */, - FD05594D2E012D1A00DC48CE /* _028_RenameAttachments.swift */, - 94CD95C02E0CBF1C0097754D /* _029_AddProMessageFlag.swift */, + FD17D7C927F546D900122BE0 /* _001_SUK_InitialSetupMigration.swift */, + FD9004132818AD0B00ABAAF6 /* _002_SUK_SetupStandardJobs.swift */, + FD17D7E627F6A16700122BE0 /* _003_SUK_YDBToGRDBMigration.swift */, + FD17D79F27F40CC800122BE0 /* _004_SNK_InitialSetupMigration.swift */, + FD6A7A6C2818C61500035AC1 /* _005_SNK_SetupStandardJobs.swift */, + FD17D79527F3E04600122BE0 /* _006_SMK_InitialSetupMigration.swift */, + FDF0B7412804EA4F004C14C5 /* _007_SMK_SetupStandardJobs.swift */, + FD17D7A327F40F8100122BE0 /* _008_SNK_YDBToGRDBMigration.swift */, + FD17D79827F40AB800122BE0 /* _009_SMK_YDBToGRDBMigration.swift */, + FD39353528F7C3390084DADA /* _010_FlagMessageHashAsDeletedOrInvalid.swift */, + FDF40CDD2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift */, + FDBB25E22988B13800F1508E /* _012_AddJobPriority.swift */, + FD37EA0C28AB2A45003AE748 /* _013_FixDeletedMessageReadState.swift */, + FD37EA0E28AB3330003AE748 /* _014_FixHiddenModAdminSupport.swift */, + 7B81682728B310D50069F315 /* _015_HomeQueryOptimisationIndexes.swift */, + FD37E9F828A5F14A003AE748 /* _016_ThemePreferences.swift */, + FD09B7E4288670BB00ED0B66 /* _017_EmojiReacts.swift */, + 7BAA7B6528D2DE4700AE1489 /* _018_OpenGroupPermission.swift */, + FD7115F128C6CB3900B47552 /* _019_AddThreadIdToFTS.swift */, + 945D9C572D6FDBE7003C4C0C /* _020_AddJobUniqueHash.swift */, + FD6DF00A2ACFE40D0084BA4C /* _021_AddSnodeReveivedMessageInfoPrimaryKey.swift */, + FD7F74562BAA9D31006DDFD8 /* _022_DropSnodeCache.swift */, + FD61FCFA2D34A5DE005752DE /* _023_SplitSnodeReceivedMessageInfo.swift */, + FD8A5B332DC1A726004C689B /* _024_ResetUserConfigLastHashes.swift */, + FDE755012C9BB122002A2623 /* _025_AddPendingReadReceipts.swift */, + FD368A6729DE8F9B000DBF1E /* _026_AddFTSIfNeeded.swift */, + FD8ECF7C2934293A00C0D1BB /* _027_SessionUtilChanges.swift */, + FD778B6329B189FF001BAC6B /* _028_GenerateInitialUserConfigDumps.swift */, + FD1D732D2A86114600E3F410 /* _029_BlockCommunityMessageRequests.swift */, + FDFE75B02ABD2D2400655640 /* _030_MakeBrokenProfileTimestampsNullable.swift */, + FD428B222B4B9969006D0888 /* _031_RebuildFTSIfNeeded_2_4_5.swift */, + 7B5233C5290636D700F8F375 /* _032_DisappearingMessagesConfiguration.swift */, + FDDD554D2C1FCB77006CBF03 /* _033_ScheduleAppUpdateCheckJob.swift */, + FD3559452CC1FF140088F2A9 /* _034_AddMissingWhisperFlag.swift */, + FD4C53AE2CC1D61E003B10F4 /* _035_ReworkRecipientState.swift */, + FDB5DAC62A9447E7002C8721 /* _036_GroupsRebuildChanges.swift */, + FDE33BBD2D5C3AE800E56F42 /* _037_GroupsExpiredFlag.swift */, + FD860CBB2D6E7A9400BBE29C /* _038_FixBustedInteractionVariant.swift */, + FD8A5B312DC191AB004C689B /* _039_DropLegacyClosedGroupKeyPairTable.swift */, + FD70F25B2DC1F176003729B7 /* _040_MessageDeduplicationTable.swift */, + 947D7FE22D5181F400E8E413 /* _041_RenameTableSettingToKeyValueStore.swift */, + FD78E9F72DDD742100D55B50 /* _042_MoveSettingsToLibSession.swift */, + FD05594D2E012D1A00DC48CE /* _043_RenameAttachments.swift */, + 94CD95C02E0CBF1C0097754D /* _044_AddProMessageFlag.swift */, ); path = Migrations; sourceTree = ""; @@ -4048,27 +4057,11 @@ FD17D79D27F40CAA00122BE0 /* Database */ = { isa = PBXGroup; children = ( - FD17D79E27F40CC000122BE0 /* Migrations */, FD17D7A827F41BE300122BE0 /* Models */, ); path = Database; sourceTree = ""; }; - FD17D79E27F40CC000122BE0 /* Migrations */ = { - isa = PBXGroup; - children = ( - FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */, - FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */, - FD17D7A327F40F8100122BE0 /* _003_YDBToGRDBMigration.swift */, - FD39353528F7C3390084DADA /* _004_FlagMessageHashAsDeletedOrInvalid.swift */, - FD6DF00A2ACFE40D0084BA4C /* _005_AddSnodeReveivedMessageInfoPrimaryKey.swift */, - FD7F74562BAA9D31006DDFD8 /* _006_DropSnodeCache.swift */, - FD61FCFA2D34A5DE005752DE /* _007_SplitSnodeReceivedMessageInfo.swift */, - FD8A5B332DC1A726004C689B /* _008_ResetUserConfigLastHashes.swift */, - ); - path = Migrations; - sourceTree = ""; - }; FD17D7A827F41BE300122BE0 /* Models */ = { isa = PBXGroup; children = ( @@ -4083,7 +4076,6 @@ FD17D7BE27F51F8200122BE0 /* ColumnExpressible.swift */, FD17D7B727F51ECA00122BE0 /* Migration.swift */, FD4BB22A2D63F20600D0DC3D /* MigrationHelper.swift */, - FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */, FD7162DA281B6C440060647B /* TypedTableAlias.swift */, FD848B8A283DC509000E298B /* PagedDatabaseObserver.swift */, FD1A553D2E14BE0E003761E4 /* PagedData.swift */, @@ -4094,7 +4086,6 @@ FD17D7BB27F51F5C00122BE0 /* Utilities */ = { isa = PBXGroup; children = ( - FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */, FDF22210281B5E0B000A4995 /* TableRecord+Utilities.swift */, FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */, FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */, @@ -4105,19 +4096,6 @@ path = Utilities; sourceTree = ""; }; - FD17D7C827F546CE00122BE0 /* Migrations */ = { - isa = PBXGroup; - children = ( - FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */, - FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */, - FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */, - FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */, - 945D9C572D6FDBE7003C4C0C /* _005_AddJobUniqueHash.swift */, - 947D7FE22D5181F400E8E413 /* _006_RenameTableSettingToKeyValueStore.swift */, - ); - path = Migrations; - sourceTree = ""; - }; FD17D7CB27F546F500122BE0 /* Models */ = { isa = PBXGroup; children = ( @@ -4259,7 +4237,6 @@ FD37E9F728A5F143003AE748 /* Migrations */ = { isa = PBXGroup; children = ( - FD37E9F828A5F14A003AE748 /* _001_ThemePreferences.swift */, ); path = Migrations; sourceTree = ""; @@ -6028,7 +6005,6 @@ 7BAF54D427ACCF01003D12F8 /* SAEScreenLockViewController.swift in Sources */, B817AD9C26436F73009DF825 /* ThreadPickerVC.swift in Sources */, FD78EA0A2DDFE45E00D55B50 /* Interaction+UI.swift in Sources */, - FD2272DE2C34F11F004D8A6C /* _001_ThemePreferences.swift in Sources */, 7BAF54D327ACCF01003D12F8 /* ShareAppExtensionContext.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -6200,14 +6176,12 @@ FDF848CE29405C5B007DCAE5 /* UpdateExpiryAllRequest.swift in Sources */, FDF848C229405C5A007DCAE5 /* OxenDaemonRPCRequest.swift in Sources */, FDF848DC29405C5B007DCAE5 /* RevokeSubaccountRequest.swift in Sources */, - FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */, FDF848D029405C5B007DCAE5 /* UpdateExpiryResponse.swift in Sources */, FDF848D329405C5B007DCAE5 /* UpdateExpiryAllResponse.swift in Sources */, FDD20C162A09E64A003898FB /* GetExpiriesRequest.swift in Sources */, FDF848BC29405C5A007DCAE5 /* SnodeRecursiveResponse.swift in Sources */, FDF848C029405C5A007DCAE5 /* ONSResolveResponse.swift in Sources */, FD2272AC2C33E337004D8A6C /* IPv4.swift in Sources */, - FD17D7A427F40F8100122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, FD2272D82C34EDE7004D8A6C /* SnodeAPIEndpoint.swift in Sources */, FDFC4D9A29F0C51500992FB6 /* String+Trimming.swift in Sources */, FDB5DAF32A96DD4F002C8721 /* PreparedRequest+Sending.swift in Sources */, @@ -6238,9 +6212,7 @@ FD2272AF2C33E337004D8A6C /* JSON.swift in Sources */, FD2272D62C34ED6A004D8A6C /* RetryWithDependencies.swift in Sources */, FDF848D229405C5B007DCAE5 /* LegacyGetMessagesRequest.swift in Sources */, - FD8A5B342DC1A732004C689B /* _008_ResetUserConfigLastHashes.swift in Sources */, FDF848E529405D6E007DCAE5 /* SnodeAPIError.swift in Sources */, - FD61FCFB2D34A5EA005752DE /* _007_SplitSnodeReceivedMessageInfo.swift in Sources */, FDF848D529405C5B007DCAE5 /* DeleteAllMessagesResponse.swift in Sources */, FD2272B22C33E337004D8A6C /* PreparedRequest.swift in Sources */, FDF848BF29405C5A007DCAE5 /* SnodeResponse.swift in Sources */, @@ -6251,18 +6223,13 @@ FD2272BA2C33E337004D8A6C /* HTTPHeader.swift in Sources */, FDF848CD29405C5B007DCAE5 /* GetNetworkTimestampResponse.swift in Sources */, FDF848DA29405C5B007DCAE5 /* GetMessagesResponse.swift in Sources */, - FD39353628F7C3390084DADA /* _004_FlagMessageHashAsDeletedOrInvalid.swift in Sources */, - FD7F74572BAA9D31006DDFD8 /* _006_DropSnodeCache.swift in Sources */, FDF8489429405C1B007DCAE5 /* SnodeAPI.swift in Sources */, FD2286682C37DA3B00BC06F7 /* LibSession+Networking.swift in Sources */, FD2272A92C33E337004D8A6C /* ContentProxy.swift in Sources */, FDF848C829405C5B007DCAE5 /* ONSResolveRequest.swift in Sources */, - FD6DF00B2ACFE40D0084BA4C /* _005_AddSnodeReveivedMessageInfoPrimaryKey.swift in Sources */, - C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */, FDF848C929405C5B007DCAE5 /* SnodeRequest.swift in Sources */, FDF848CF29405C5B007DCAE5 /* SendMessageRequest.swift in Sources */, FD2272BE2C34B710004D8A6C /* Publisher+Utilities.swift in Sources */, - FD6A7A6D2818C61500035AC1 /* _002_SetupStandardJobs.swift in Sources */, FD3765E72ADE1AA300DC1489 /* UnrevokeSubaccountRequest.swift in Sources */, FD2272BC2C33E337004D8A6C /* URLResponse+Utilities.swift in Sources */, FD2272B52C33E337004D8A6C /* BatchResponse.swift in Sources */, @@ -6283,7 +6250,6 @@ files = ( FD2272C82C34EB0A004D8A6C /* Job.swift in Sources */, FD2272C72C34EAF5004D8A6C /* ColumnExpressible.swift in Sources */, - 945D9C582D6FDBE7003C4C0C /* _005_AddJobUniqueHash.swift in Sources */, FDB3486E2BE8457F00B716C2 /* BackgroundTaskManager.swift in Sources */, 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */, FD428B192B4B576F006D0888 /* AppContext.swift in Sources */, @@ -6296,7 +6262,6 @@ FD42ECD62E3308B5002D03EA /* ObservableKey+SessionUtilitiesKit.swift in Sources */, FDE754D22C9BAF53002A2623 /* JobDependencies.swift in Sources */, FDE755242C9BC1D1002A2623 /* Publisher+Utilities.swift in Sources */, - FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */, 7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */, FD00CDCB2D5317A7006B96D3 /* Scheduler+Utilities.swift in Sources */, FD848B8B283DC509000E298B /* PagedDatabaseObserver.swift in Sources */, @@ -6322,7 +6287,6 @@ FD5931A72A8DA5DA0040147D /* SQLInterpolation+Utilities.swift in Sources */, FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */, FD3765EA2ADE37B400DC1489 /* Authentication.swift in Sources */, - FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */, FDE755202C9BC1A6002A2623 /* CacheConfig.swift in Sources */, FD1A553E2E14BE11003761E4 /* PagedData.swift in Sources */, FD4BB22C2D63FA8600D0DC3D /* (null) in Sources */, @@ -6342,7 +6306,6 @@ FD6673FF2D77F9C100041530 /* ScreenLock.swift in Sources */, FD78E9F02DD6D61200D55B50 /* Data+Image.swift in Sources */, FDB3DA8B2E24834000148F8D /* AVURLAsset+Utilities.swift in Sources */, - FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */, FD848B9328420164000E298B /* UnicodeScalar+Utilities.swift in Sources */, FDE754CE2C9BAF37002A2623 /* ImageFormat.swift in Sources */, FDB11A542DCD7A7F00BEF49F /* Task+Utilities.swift in Sources */, @@ -6352,11 +6315,9 @@ FDE33BBC2D5C124900E56F42 /* DispatchTimeInterval+Utilities.swift in Sources */, FD7115FA28C8153400B47552 /* UIBarButtonItem+Combine.swift in Sources */, FD705A92278D051200F16121 /* ReusableView.swift in Sources */, - FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */, FDE754DD2C9BAF8A002A2623 /* Mnemonic.swift in Sources */, FD52CB652E13B6E900A4DA70 /* ObservationBuilder.swift in Sources */, FDBEE52E2B6A18B900C143A0 /* UserDefaultsConfig.swift in Sources */, - 947D7FE32D5181F400E8E413 /* _006_RenameTableSettingToKeyValueStore.swift in Sources */, FD78EA042DDEC3C500D55B50 /* MultiTaskManager.swift in Sources */, FD78EA062DDEC8F600D55B50 /* AsyncSequence+Utilities.swift in Sources */, FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */, @@ -6393,7 +6354,6 @@ FDE754D42C9BAF6B002A2623 /* UICollectionView+ReusableView.swift in Sources */, FDE754C02C9BAEF6002A2623 /* Array+Utilities.swift in Sources */, FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */, - FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */, FDAA16762AC28A3B00DDBF77 /* UserDefaultsType.swift in Sources */, FDDF074429C3E3D000E5E8B5 /* FetchRequest+Utilities.swift in Sources */, FD7F745B2BAAA35E006DDFD8 /* LibSession.swift in Sources */, @@ -6414,7 +6374,6 @@ FDF01FAD2A9ECC4200CAF969 /* SingletonConfig.swift in Sources */, FDE755182C9BC169002A2623 /* UIAlertAction+Utilities.swift in Sources */, FD7115F828C8151C00B47552 /* DisposableBarButtonItem.swift in Sources */, - FD17D7E727F6A16700122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, FDE7551B2C9BC169002A2623 /* UINavigationController+Utilities.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -6424,10 +6383,11 @@ buildActionMask = 2147483647; files = ( FD8ECF7B29340FFD00C0D1BB /* LibSession+SessionMessagingKit.swift in Sources */, - 7B81682828B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift in Sources */, + FDD23AE62E458CAA0057E853 /* _023_SplitSnodeReceivedMessageInfo.swift in Sources */, + 7B81682828B310D50069F315 /* _015_HomeQueryOptimisationIndexes.swift in Sources */, FD245C52285065D500B966DD /* SignalAttachment.swift in Sources */, FD2273002C352D8E004D8A6C /* LibSession+GroupKeys.swift in Sources */, - FD70F25C2DC1F184003729B7 /* _026_MessageDeduplicationTable.swift in Sources */, + FD70F25C2DC1F184003729B7 /* _040_MessageDeduplicationTable.swift in Sources */, FD2272FB2C352D8E004D8A6C /* LibSession+UserGroups.swift in Sources */, B8856D08256F10F1001CE70E /* DeviceSleepManager.swift in Sources */, FD22726F2C32911C004D8A6C /* ProcessPendingGroupMemberRemovalsJob.swift in Sources */, @@ -6437,7 +6397,7 @@ FDC13D582A17207D007267C7 /* UnsubscribeResponse.swift in Sources */, FD09799927FFC1A300936362 /* Attachment.swift in Sources */, FD245C5F2850662200B966DD /* OWSWindowManager.m in Sources */, - FDF40CDE2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift in Sources */, + FDF40CDE2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift in Sources */, FDF0B74928060D13004C14C5 /* QuotedReplyModel.swift in Sources */, FD78E9F42DDABA4F00D55B50 /* AttachmentUploader.swift in Sources */, FDE754A32C9A8FD1002A2623 /* SwarmPoller.swift in Sources */, @@ -6451,7 +6411,7 @@ FD47E0B12AA6A05800A55E41 /* Authentication+SessionMessagingKit.swift in Sources */, FD2272832C337830004D8A6C /* GroupPoller.swift in Sources */, FD22726C2C32911C004D8A6C /* GroupLeavingJob.swift in Sources */, - FDE33BBE2D5C3AF100E56F42 /* _023_GroupsExpiredFlag.swift in Sources */, + FDE33BBE2D5C3AF100E56F42 /* _037_GroupsExpiredFlag.swift in Sources */, FDF848F729414477007DCAE5 /* CurrentUserPoller.swift in Sources */, C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */, FD2272FD2C352D8E004D8A6C /* LibSession+ConvoInfoVolatile.swift in Sources */, @@ -6466,28 +6426,28 @@ FD2273082C353109004D8A6C /* DisplayPictureManager.swift in Sources */, FDE521A22E0D23AB00061B8E /* ObservableKey+SessionMessagingKit.swift in Sources */, FD2273022C352D8E004D8A6C /* LibSession+GroupInfo.swift in Sources */, - FDDD554E2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift in Sources */, + FDDD554E2C1FCB77006CBF03 /* _033_ScheduleAppUpdateCheckJob.swift in Sources */, FD09798927FD1C5A00936362 /* OpenGroup.swift in Sources */, - 94CD95C12E0CBF430097754D /* _029_AddProMessageFlag.swift in Sources */, + 94CD95C12E0CBF430097754D /* _044_AddProMessageFlag.swift in Sources */, FD2272FC2C352D8E004D8A6C /* LibSession+Contacts.swift in Sources */, FD848B9628422A2A000E298B /* MessageViewModel.swift in Sources */, FD05593D2DFA3A2800DC48CE /* VoipPayloadKey.swift in Sources */, FD2272782C32911C004D8A6C /* AttachmentDownloadJob.swift in Sources */, FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */, - FD428B232B4B9969006D0888 /* _017_RebuildFTSIfNeeded_2_4_5.swift in Sources */, + FD428B232B4B9969006D0888 /* _031_RebuildFTSIfNeeded_2_4_5.swift in Sources */, FD2272742C32911C004D8A6C /* ConfigMessageReceiveJob.swift in Sources */, FDFBB74D2A1F3C4E00CA7350 /* NotificationMetadata.swift in Sources */, FD716E6628502EE200C96BF4 /* CurrentCallProtocol.swift in Sources */, FD22727A2C32911C004D8A6C /* GroupInviteMemberJob.swift in Sources */, - FDB5DAC72A9447E7002C8721 /* _022_GroupsRebuildChanges.swift in Sources */, - FD09B7E5288670BB00ED0B66 /* _008_EmojiReacts.swift in Sources */, + FDB5DAC72A9447E7002C8721 /* _036_GroupsRebuildChanges.swift in Sources */, + FD09B7E5288670BB00ED0B66 /* _017_EmojiReacts.swift in Sources */, FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */, FDD20C1A2A0A03AC003898FB /* DeleteInboxResponse.swift in Sources */, 7B8D5FC428332600008324D9 /* VisibleMessage+Reaction.swift in Sources */, FDC4386527B4DE7600C60D73 /* RoomPollInfo.swift in Sources */, FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */, FD2272FA2C352D8E004D8A6C /* LibSession+SharedGroup.swift in Sources */, - FD37EA0F28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift in Sources */, + FD37EA0F28AB3330003AE748 /* _014_FixHiddenModAdminSupport.swift in Sources */, FD2272772C32911C004D8A6C /* AttachmentUploadJob.swift in Sources */, 7B81682328A4C1210069F315 /* UpdateTypes.swift in Sources */, FDC13D472A16E4CA007267C7 /* SubscribeRequest.swift in Sources */, @@ -6497,7 +6457,7 @@ FDC4386727B4E10E00C60D73 /* Capabilities.swift in Sources */, FDC438A427BB107F00C60D73 /* UserBanRequest.swift in Sources */, FDE754F22C9BB08B002A2623 /* Crypto+SessionMessagingKit.swift in Sources */, - FD4C53AF2CC1D62E003B10F4 /* _021_ReworkRecipientState.swift in Sources */, + FD4C53AF2CC1D62E003B10F4 /* _035_ReworkRecipientState.swift in Sources */, C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */, FD6C67242CF6E72E00B350A7 /* NoopSessionCallManager.swift in Sources */, FDB4BBC72838B91E00B7C95D /* LinkPreviewError.swift in Sources */, @@ -6507,24 +6467,30 @@ FD09798727FD1B7800936362 /* GroupMember.swift in Sources */, FD78EA0D2DDFEDE200D55B50 /* LibSession+Local.swift in Sources */, FDCD2E032A41294E00964D6A /* LegacyGroupOnlyRequest.swift in Sources */, + FDD23AE12E457CDE0057E853 /* _005_SNK_SetupStandardJobs.swift in Sources */, FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */, FD09C5EC282B8F18000CE219 /* AttachmentError.swift in Sources */, FDE754F02C9BB08B002A2623 /* Crypto+Attachments.swift in Sources */, - FD17D79927F40AB800122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, + FD17D79927F40AB800122BE0 /* _009_SMK_YDBToGRDBMigration.swift in Sources */, FDE754A12C9A60A6002A2623 /* Crypto+OpenGroupAPI.swift in Sources */, FDF0B7512807BA56004C14C5 /* NotificationsManagerType.swift in Sources */, FD2272722C32911C004D8A6C /* FailedAttachmentDownloadsJob.swift in Sources */, FDC13D5A2A1721C5007267C7 /* LegacyNotifyRequest.swift in Sources */, + FDD23AEA2E458EB00057E853 /* _012_AddJobPriority.swift in Sources */, FD245C59285065FC00B966DD /* ControlMessage.swift in Sources */, FD6E4C8A2A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift in Sources */, B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */, + FDD23AEC2E458F980057E853 /* _024_ResetUserConfigLastHashes.swift in Sources */, FD245C50285065C700B966DD /* VisibleMessage+Quote.swift in Sources */, + FDD23AE82E458DD40057E853 /* _002_SUK_SetupStandardJobs.swift in Sources */, FD72BDA12BE368C800CF6CF6 /* UIWindowLevel+Utilities.swift in Sources */, FD4C4E9C2B02E2A300C72199 /* DisplayPictureError.swift in Sources */, + FDD23AE22E457CE50057E853 /* _008_SNK_YDBToGRDBMigration.swift in Sources */, FD5C7307284F103B0029977D /* MessageReceiver+MessageRequests.swift in Sources */, C3A71D0B2558989C0043A11F /* MessageWrapper.swift in Sources */, FD3FAB592ADF906300DC5421 /* Profile+CurrentUser.swift in Sources */, FDEF573E2C40F2A100131302 /* GroupUpdateMemberLeftNotificationMessage.swift in Sources */, + FDD23ADF2E457CAA0057E853 /* _016_ThemePreferences.swift in Sources */, FDB11A5D2DD300D300BEF49F /* SNProtoContent+Utilities.swift in Sources */, FDE755002C9BB0FA002A2623 /* SessionEnvironment.swift in Sources */, FDB5DADC2A95D840002C8721 /* GroupUpdateMemberChangeMessage.swift in Sources */, @@ -6540,11 +6506,11 @@ B8F5F60325EDE16F003BF8D4 /* DataExtractionNotification.swift in Sources */, C3A71D1E25589AC30043A11F /* WebSocketProto.swift in Sources */, C3C2A7852553AAF300C340D1 /* SessionProtos.pb.swift in Sources */, - FDF0B7422804EA4F004C14C5 /* _002_SetupStandardJobs.swift in Sources */, + FDF0B7422804EA4F004C14C5 /* _007_SMK_SetupStandardJobs.swift in Sources */, B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, FDF8487F29405994007DCAE5 /* HTTPHeader+OpenGroup.swift in Sources */, - FD8ECF7D2934293A00C0D1BB /* _013_SessionUtilChanges.swift in Sources */, - FD17D7A227F40F0500122BE0 /* _001_InitialSetupMigration.swift in Sources */, + FD8ECF7D2934293A00C0D1BB /* _027_SessionUtilChanges.swift in Sources */, + FD17D7A227F40F0500122BE0 /* _006_SMK_InitialSetupMigration.swift in Sources */, FD245C5D2850660F00B966DD /* OWSAudioPlayer.m in Sources */, FD2272FE2C352D8E004D8A6C /* LibSession+GroupMembers.swift in Sources */, FDF0B7582807F368004C14C5 /* MessageReceiverError.swift in Sources */, @@ -6561,7 +6527,7 @@ FDB5DAC12A9443A5002C8721 /* MessageSender+Groups.swift in Sources */, FDC13D4B2A16ECBA007267C7 /* SubscribeResponse.swift in Sources */, FD2272702C32911C004D8A6C /* DisappearingMessagesJob.swift in Sources */, - FD7115F228C6CB3900B47552 /* _010_AddThreadIdToFTS.swift in Sources */, + FD7115F228C6CB3900B47552 /* _019_AddThreadIdToFTS.swift in Sources */, FD716E6428502DDD00C96BF4 /* CallManagerProtocol.swift in Sources */, 943C6D822B75E061004ACE64 /* Message+DisappearingMessages.swift in Sources */, FDC438C727BB6DF000C60D73 /* DirectMessage.swift in Sources */, @@ -6574,8 +6540,9 @@ FD245C51285065CC00B966DD /* MessageReceiver.swift in Sources */, FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */, FDB5DAE62A95D8B0002C8721 /* GroupUpdateDeleteMemberContentMessage.swift in Sources */, - 7B5233C6290636D700F8F375 /* _018_DisappearingMessagesConfiguration.swift in Sources */, + 7B5233C6290636D700F8F375 /* _032_DisappearingMessagesConfiguration.swift in Sources */, FD5C72FD284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift in Sources */, + FDD23AEB2E458F4D0057E853 /* _020_AddJobUniqueHash.swift in Sources */, FDB11A502DCC6ADE00BEF49F /* ThreadUpdateInfo.swift in Sources */, B8D0A25925E367AC00C1835E /* Notification+MessageReceiver.swift in Sources */, FDC1BD662CFD6C4F002CDC71 /* Config.swift in Sources */, @@ -6584,11 +6551,14 @@ C32C599E256DB02B003C73A2 /* TypingIndicators.swift in Sources */, FDE7549B2C940108002A2623 /* MessageViewModel+DeletionActions.swift in Sources */, FD09799527FE7B8E00936362 /* Interaction.swift in Sources */, - FD37EA0D28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift in Sources */, - 7BAA7B6628D2DE4700AE1489 /* _009_OpenGroupPermission.swift in Sources */, + FD37EA0D28AB2A45003AE748 /* _013_FixDeletedMessageReadState.swift in Sources */, + FDD23AE32E457CFE0057E853 /* _010_FlagMessageHashAsDeletedOrInvalid.swift in Sources */, + 7BAA7B6628D2DE4700AE1489 /* _018_OpenGroupPermission.swift in Sources */, FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */, FD2286692C37DA5500BC06F7 /* PollerType.swift in Sources */, - FD860CBC2D6E7A9F00BBE29C /* _024_FixBustedInteractionVariant.swift in Sources */, + FD860CBC2D6E7A9F00BBE29C /* _038_FixBustedInteractionVariant.swift in Sources */, + FDD23AE72E458DBC0057E853 /* _001_SUK_InitialSetupMigration.swift in Sources */, + FDD23AE42E458C810057E853 /* _021_AddSnodeReveivedMessageInfoPrimaryKey.swift in Sources */, FD22727B2C32911C004D8A6C /* MessageSendJob.swift in Sources */, FD78E9EE2DD6D32500D55B50 /* ImageDataManager+Singleton.swift in Sources */, FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */, @@ -6601,39 +6571,41 @@ FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */, FDC13D492A16EC20007267C7 /* Service.swift in Sources */, FDBA8A842D597975007C19C0 /* FailedGroupInvitesAndPromotionsJob.swift in Sources */, - FD778B6429B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift in Sources */, + FD778B6429B189FF001BAC6B /* _028_GenerateInitialUserConfigDumps.swift in Sources */, FDC13D562A171FE4007267C7 /* UnsubscribeRequest.swift in Sources */, FD1A55432E179AED003761E4 /* ObservableKeyEvent+Utilities.swift in Sources */, C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, + FDD23AE92E458E020057E853 /* _003_SUK_YDBToGRDBMigration.swift in Sources */, FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */, FD22727F2C32911C004D8A6C /* GetExpirationJob.swift in Sources */, FD2272792C32911C004D8A6C /* DisplayPictureDownloadJob.swift in Sources */, FD981BD92DC9A69600564172 /* NotificationUserInfoKey.swift in Sources */, C352A2FF25574B6300338F3E /* (null) in Sources */, + FDD23AE52E458C940057E853 /* _022_DropSnodeCache.swift in Sources */, FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */, B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */, FD2272762C32911C004D8A6C /* ExpirationUpdateJob.swift in Sources */, FD716E722850647600C96BF4 /* Data+Utilities.swift in Sources */, - FD368A6829DE8F9C000DBF1E /* _012_AddFTSIfNeeded.swift in Sources */, + FD368A6829DE8F9C000DBF1E /* _026_AddFTSIfNeeded.swift in Sources */, FD5C7301284F0F7A0029977D /* MessageReceiver+UnsendRequests.swift in Sources */, C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */, FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */, - FDFE75B12ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift in Sources */, + FDFE75B12ABD2D2400655640 /* _030_MakeBrokenProfileTimestampsNullable.swift in Sources */, FD09799B27FFC82D00936362 /* Quote.swift in Sources */, FD2273012C352D8E004D8A6C /* LibSession+Shared.swift in Sources */, C3C2A74425539EB700C340D1 /* Message.swift in Sources */, FD245C682850666300B966DD /* Message+Destination.swift in Sources */, FDF8488029405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift in Sources */, - FD8A5B322DC191B4004C689B /* _025_DropLegacyClosedGroupKeyPairTable.swift in Sources */, + FD8A5B322DC191B4004C689B /* _039_DropLegacyClosedGroupKeyPairTable.swift in Sources */, FD245C632850664600B966DD /* Configuration.swift in Sources */, FD981BC62DC3310B00564172 /* ExtensionHelper.swift in Sources */, - FD78E9FA2DDD74D200D55B50 /* _027_MoveSettingsToLibSession.swift in Sources */, + FD78E9FA2DDD74D200D55B50 /* _042_MoveSettingsToLibSession.swift in Sources */, FD2272FF2C352D8E004D8A6C /* LibSession+UserProfile.swift in Sources */, FD5C7305284F0FF30029977D /* MessageReceiver+VisibleMessages.swift in Sources */, FDB5DAE82A95D96C002C8721 /* MessageReceiver+Groups.swift in Sources */, 94CD95BD2E09083C0097754D /* LibSession+Pro.swift in Sources */, - FD1D732E2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift in Sources */, + FD1D732E2A86114600E3F410 /* _029_BlockCommunityMessageRequests.swift in Sources */, FD2B4B042949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift in Sources */, FDAA167F2AC5290000DDBF77 /* Preferences+NotificationPreviewType.swift in Sources */, FD09797027FA6FF300936362 /* Profile.swift in Sources */, @@ -6648,22 +6620,24 @@ FDB5DADA2A95D839002C8721 /* GroupUpdateInfoChangeMessage.swift in Sources */, FDF71EA32B072C2800A8D6B5 /* LibSessionMessage.swift in Sources */, FDAA167D2AC528A200DDBF77 /* Preferences+Sound.swift in Sources */, + FDD23AED2E4590A10057E853 /* _041_RenameTableSettingToKeyValueStore.swift in Sources */, FDE754FE2C9BB0D0002A2623 /* Threading+SMK.swift in Sources */, FDF0B75E280AAF35004C14C5 /* Preferences.swift in Sources */, FDF2F0222DAE1AF500491E8A /* MessageReceiver+LegacyClosedGroups.swift in Sources */, FD02CC122C367762009AB976 /* Request+PushNotificationAPI.swift in Sources */, - FD05594E2E012D2700DC48CE /* _028_RenameAttachments.swift in Sources */, + FD05594E2E012D2700DC48CE /* _043_RenameAttachments.swift in Sources */, FD22726E2C32911C004D8A6C /* FailedMessageSendsJob.swift in Sources */, FDB5DAD42A9483F3002C8721 /* GroupUpdateInviteMessage.swift in Sources */, FDB5DAE22A95D8A0002C8721 /* GroupUpdateInviteResponseMessage.swift in Sources */, FDB11A522DCC6B0000BEF49F /* OpenGroupUrlInfo.swift in Sources */, - FD3559462CC1FF200088F2A9 /* _020_AddMissingWhisperFlag.swift in Sources */, + FD3559462CC1FF200088F2A9 /* _034_AddMissingWhisperFlag.swift in Sources */, FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */, + FDD23AE02E457CD40057E853 /* _004_SNK_InitialSetupMigration.swift in Sources */, FD5C7303284F0FA50029977D /* MessageReceiver+Calls.swift in Sources */, FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */, FDC438AA27BB12BB00C60D73 /* UserModeratorRequest.swift in Sources */, FDC4385D27B4C18900C60D73 /* Room.swift in Sources */, - FDE755022C9BB122002A2623 /* _011_AddPendingReadReceipts.swift in Sources */, + FDE755022C9BB122002A2623 /* _025_AddPendingReadReceipts.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6678,7 +6652,6 @@ FD7115EB28C5D78E00B47552 /* ThreadSettingsViewModel.swift in Sources */, B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */, 7B4EF25A2934743000CB351D /* SessionTableViewTitleView.swift in Sources */, - FD2272D92C34EED6004D8A6C /* _001_ThemePreferences.swift in Sources */, 942256A12C23F90700C0FDBF /* CustomTopTabBar.swift in Sources */, FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */, 4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */, @@ -6959,12 +6932,14 @@ FD65318D2AA025C500DFEEAA /* TestDependencies.swift in Sources */, FD49E2492B05C1D500FFBBB5 /* MockKeychain.swift in Sources */, FD99D0922D10F5EE005D2E15 /* ThreadSafeSpec.swift in Sources */, + FDD23AF02E459EDD0057E853 /* _020_AddJobUniqueHash.swift in Sources */, FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */, FD9DD2732A72516D00ECB68E /* TestExtensions.swift in Sources */, FD3FAB6E2AF1B28C00DC5421 /* MockFileManager.swift in Sources */, FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */, FDB11A592DD17D0600BEF49F /* MockLogger.swift in Sources */, FD8A5B302DC18D61004C689B /* GeneralCacheSpec.swift in Sources */, + FDD23AEE2E459E470057E853 /* _001_SUK_InitialSetupMigration.swift in Sources */, FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, FD0150402CA2433D005B08A1 /* BencodeDecoderSpec.swift in Sources */, FD0150412CA2433D005B08A1 /* BencodeEncoderSpec.swift in Sources */, @@ -6982,6 +6957,7 @@ FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */, FD0969FB2A6A00B100C5C365 /* Mocked.swift in Sources */, FD0150292CA23DB7005B08A1 /* GRDBExtensions.swift in Sources */, + FDD23AEF2E459EC90057E853 /* _012_AddJobPriority.swift in Sources */, FD481A942CAE0AE000ECC4CF /* MockAppContext.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index eea5572f6d..2d776443aa 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -70,7 +70,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD self.loadingViewController = LoadingViewController() AppSetup.setupEnvironment( - additionalMigrationTargets: [DeprecatedUIKitMigrationTarget.self], appSpecificBlock: { [dependencies] in Log.setup(with: Logger(primaryPrefix: "Session", using: dependencies)) Log.info(.cat, "Setting up environment.") @@ -221,7 +220,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // Dispatch async so things can continue to be progressed if a migration does need to run DispatchQueue.global(qos: .userInitiated).async { [weak self, dependencies] in AppSetup.runPostSetupMigrations( - additionalMigrationTargets: [DeprecatedUIKitMigrationTarget.self], migrationProgressChanged: { progress, minEstimatedTotalTime in self?.loadingViewController?.updateProgress( progress: progress, @@ -599,7 +597,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // The re-run the migration (should succeed since there is no data) AppSetup.runPostSetupMigrations( - additionalMigrationTargets: [DeprecatedUIKitMigrationTarget.self], migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in self?.loadingViewController?.updateProgress( progress: progress, diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index 2f861719a4..5260915ddc 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -2,58 +2,53 @@ import Foundation import GRDB import SessionUtilitiesKit -public enum SNMessagingKit: MigratableTarget { // Just to make the external API nice - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: .messagingKit, - migrations: [ - [ - _001_InitialSetupMigration.self, - _002_SetupStandardJobs.self - ], // Initial DB Creation - [ - _003_YDBToGRDBMigration.self - ], // YDB to GRDB Migration - [ - _004_RemoveLegacyYDB.self - ], // Legacy DB removal - [ - _005_FixDeletedMessageReadState.self, - _006_FixHiddenModAdminSupport.self, - _007_HomeQueryOptimisationIndexes.self - ], // Add job priorities - [ - _008_EmojiReacts.self, - _009_OpenGroupPermission.self, - _010_AddThreadIdToFTS.self - ], // Fix thread FTS - [ - _011_AddPendingReadReceipts.self, - _012_AddFTSIfNeeded.self, - _013_SessionUtilChanges.self, - _014_GenerateInitialUserConfigDumps.self, - _015_BlockCommunityMessageRequests.self, - _016_MakeBrokenProfileTimestampsNullable.self, - _017_RebuildFTSIfNeeded_2_4_5.self, - _018_DisappearingMessagesConfiguration.self, - _019_ScheduleAppUpdateCheckJob.self, - _020_AddMissingWhisperFlag.self, - _021_ReworkRecipientState.self, - _022_GroupsRebuildChanges.self, - _023_GroupsExpiredFlag.self, - _024_FixBustedInteractionVariant.self, - _025_DropLegacyClosedGroupKeyPairTable.self, - _026_MessageDeduplicationTable.self - ], - [], // Renamed `Setting` to `KeyValueStore` - [ - _027_MoveSettingsToLibSession.self, - _028_RenameAttachments.self, - _029_AddProMessageFlag.self - ] - ] - ) - } +public enum SNMessagingKit { // Just to make the external API nice + public static let migrations: [Migration.Type] = [ + _001_SUK_InitialSetupMigration.self, + _002_SUK_SetupStandardJobs.self, + _003_SUK_YDBToGRDBMigration.self, + _004_SNK_InitialSetupMigration.self, + _005_SNK_SetupStandardJobs.self, + _006_SMK_InitialSetupMigration.self, + _007_SMK_SetupStandardJobs.self, + _008_SNK_YDBToGRDBMigration.self, + _009_SMK_YDBToGRDBMigration.self, + _010_FlagMessageHashAsDeletedOrInvalid.self, + _011_RemoveLegacyYDB.self, + _012_AddJobPriority.self, + _013_FixDeletedMessageReadState.self, + _014_FixHiddenModAdminSupport.self, + _015_HomeQueryOptimisationIndexes.self, + _016_ThemePreferences.self, + _017_EmojiReacts.self, + _018_OpenGroupPermission.self, + _019_AddThreadIdToFTS.self, + _020_AddJobUniqueHash.self, + _021_AddSnodeReveivedMessageInfoPrimaryKey.self, + _022_DropSnodeCache.self, + _023_SplitSnodeReceivedMessageInfo.self, + _024_ResetUserConfigLastHashes.self, + _025_AddPendingReadReceipts.self, + _026_AddFTSIfNeeded.self, + _027_SessionUtilChanges.self, + _028_GenerateInitialUserConfigDumps.self, + _029_BlockCommunityMessageRequests.self, + _030_MakeBrokenProfileTimestampsNullable.self, + _031_RebuildFTSIfNeeded_2_4_5.self, + _032_DisappearingMessagesConfiguration.self, + _033_ScheduleAppUpdateCheckJob.self, + _034_AddMissingWhisperFlag.self, + _035_ReworkRecipientState.self, + _036_GroupsRebuildChanges.self, + _037_GroupsExpiredFlag.self, + _038_FixBustedInteractionVariant.self, + _039_DropLegacyClosedGroupKeyPairTable.self, + _040_MessageDeduplicationTable.self, + _041_RenameTableSettingToKeyValueStore.self, + _042_MoveSettingsToLibSession.self, + _043_RenameAttachments.self, + _044_AddProMessageFlag.self + ] public static func configure(using dependencies: Dependencies) { // Configure the job executors diff --git a/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift b/SessionMessagingKit/Database/Migrations/_001_SUK_InitialSetupMigration.swift similarity index 94% rename from SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift rename to SessionMessagingKit/Database/Migrations/_001_SUK_InitialSetupMigration.swift index 038c6de166..d91ffaca7d 100644 --- a/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_001_SUK_InitialSetupMigration.swift @@ -4,10 +4,10 @@ import Foundation import GRDB +import SessionUtilitiesKit -enum _001_InitialSetupMigration: Migration { - static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "initialSetup" +enum _001_SUK_InitialSetupMigration: Migration { + static let identifier: String = "utilitiesKit.initialSetup" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [ Identity.self, Job.self, JobDependencies.self diff --git a/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionMessagingKit/Database/Migrations/_002_SUK_SetupStandardJobs.swift similarity index 89% rename from SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift rename to SessionMessagingKit/Database/Migrations/_002_SUK_SetupStandardJobs.swift index ca787c2c2c..8f27b313d5 100644 --- a/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift +++ b/SessionMessagingKit/Database/Migrations/_002_SUK_SetupStandardJobs.swift @@ -2,12 +2,12 @@ import Foundation import GRDB +import SessionUtilitiesKit /// This migration sets up the standard jobs, since we want these jobs to run before any "once-off" jobs we do this migration /// before running the `YDBToGRDBMigration` -enum _002_SetupStandardJobs: Migration { - static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "SetupStandardJobs" +enum _002_SUK_SetupStandardJobs: Migration { + static let identifier: String = "utilitiesKit.SetupStandardJobs" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionNetworkingKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_003_SUK_YDBToGRDBMigration.swift similarity index 70% rename from SessionNetworkingKit/Database/Migrations/_003_YDBToGRDBMigration.swift rename to SessionMessagingKit/Database/Migrations/_003_SUK_YDBToGRDBMigration.swift index 382874faf3..5753532d9a 100644 --- a/SessionNetworkingKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_003_SUK_YDBToGRDBMigration.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _003_YDBToGRDBMigration: Migration { - static let target: TargetMigrations.Identifier = .networkingKit - static let identifier: String = "YDBToGRDBMigration" +enum _003_SUK_YDBToGRDBMigration: Migration { + static let identifier: String = "utilitiesKit.YDBToGRDBMigration" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionNetworkingKit/Database/Migrations/_001_InitialSetupMigration.swift b/SessionMessagingKit/Database/Migrations/_004_SNK_InitialSetupMigration.swift similarity index 90% rename from SessionNetworkingKit/Database/Migrations/_001_InitialSetupMigration.swift rename to SessionMessagingKit/Database/Migrations/_004_SNK_InitialSetupMigration.swift index 9473c323f8..9852a0a11f 100644 --- a/SessionNetworkingKit/Database/Migrations/_001_InitialSetupMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_004_SNK_InitialSetupMigration.swift @@ -6,9 +6,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _001_InitialSetupMigration: Migration { - static let target: TargetMigrations.Identifier = .networkingKit - static let identifier: String = "initialSetup" +enum _004_SNK_InitialSetupMigration: Migration { + static let identifier: String = "snodeKit.initialSetup" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionNetworkingKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionMessagingKit/Database/Migrations/_005_SNK_SetupStandardJobs.swift similarity index 91% rename from SessionNetworkingKit/Database/Migrations/_002_SetupStandardJobs.swift rename to SessionMessagingKit/Database/Migrations/_005_SNK_SetupStandardJobs.swift index 845a42fe16..2241868cc8 100644 --- a/SessionNetworkingKit/Database/Migrations/_002_SetupStandardJobs.swift +++ b/SessionMessagingKit/Database/Migrations/_005_SNK_SetupStandardJobs.swift @@ -6,9 +6,8 @@ import SessionUtilitiesKit /// This migration sets up the standard jobs, since we want these jobs to run before any "once-off" jobs we do this migration /// before running the `YDBToGRDBMigration` -enum _002_SetupStandardJobs: Migration { - static let target: TargetMigrations.Identifier = .networkingKit - static let identifier: String = "SetupStandardJobs" +enum _005_SNK_SetupStandardJobs: Migration { + static let identifier: String = "snodeKit.SetupStandardJobs" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift b/SessionMessagingKit/Database/Migrations/_006_SMK_InitialSetupMigration.swift similarity index 97% rename from SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift rename to SessionMessagingKit/Database/Migrations/_006_SMK_InitialSetupMigration.swift index 2823930730..6c9e861ca9 100644 --- a/SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_006_SMK_InitialSetupMigration.swift @@ -6,9 +6,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _001_InitialSetupMigration: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "initialSetup" +enum _006_SMK_InitialSetupMigration: Migration { + static let identifier: String = "messagingKit.initialSetup" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [ Contact.self, Profile.self, SessionThread.self, DisappearingMessagesConfiguration.self, @@ -59,7 +58,7 @@ enum _001_InitialSetupMigration: Migration { /// Create a full-text search table synchronized with the Profile table try db.create(virtualTable: "profile_fts", using: FTS5()) { t in t.synchronize(withTable: "profile") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("nickname") t.column("name") @@ -106,7 +105,7 @@ enum _001_InitialSetupMigration: Migration { /// Create a full-text search table synchronized with the ClosedGroup table try db.create(virtualTable: "closedGroup_fts", using: FTS5()) { t in t.synchronize(withTable: "closedGroup") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("name") } @@ -157,7 +156,7 @@ enum _001_InitialSetupMigration: Migration { /// Create a full-text search table synchronized with the OpenGroup table try db.create(virtualTable: "openGroup_fts", using: FTS5()) { t in t.synchronize(withTable: "openGroup") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("name") } @@ -268,7 +267,7 @@ enum _001_InitialSetupMigration: Migration { /// Create a full-text search table synchronized with the Interaction table try db.create(virtualTable: "interaction_fts", using: FTS5()) { t in t.synchronize(withTable: "interaction") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("body") } diff --git a/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionMessagingKit/Database/Migrations/_007_SMK_SetupStandardJobs.swift similarity index 93% rename from SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift rename to SessionMessagingKit/Database/Migrations/_007_SMK_SetupStandardJobs.swift index b9b3e31588..f7057035e0 100644 --- a/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift +++ b/SessionMessagingKit/Database/Migrations/_007_SMK_SetupStandardJobs.swift @@ -7,9 +7,8 @@ import SessionNetworkingKit /// This migration sets up the standard jobs, since we want these jobs to run before any "once-off" jobs we do this migration /// before running the `YDBToGRDBMigration` -enum _002_SetupStandardJobs: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "SetupStandardJobs" +enum _007_SMK_SetupStandardJobs: Migration { + static let identifier: String = "messagingKit.SetupStandardJobs" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_008_SNK_YDBToGRDBMigration.swift similarity index 69% rename from SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift rename to SessionMessagingKit/Database/Migrations/_008_SNK_YDBToGRDBMigration.swift index 565fd3e3f2..ac66dd7c0b 100644 --- a/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_008_SNK_YDBToGRDBMigration.swift @@ -2,10 +2,10 @@ import Foundation import GRDB +import SessionUtilitiesKit -enum _003_YDBToGRDBMigration: Migration { - static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "YDBToGRDBMigration" +enum _008_SNK_YDBToGRDBMigration: Migration { + static let identifier: String = "snodeKit.YDBToGRDBMigration" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_009_SMK_YDBToGRDBMigration.swift similarity index 79% rename from SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift rename to SessionMessagingKit/Database/Migrations/_009_SMK_YDBToGRDBMigration.swift index 13ecc5df76..7851f48d74 100644 --- a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_009_SMK_YDBToGRDBMigration.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _003_YDBToGRDBMigration: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "YDBToGRDBMigration" +enum _009_SMK_YDBToGRDBMigration: Migration { + static let identifier: String = "messagingKit.YDBToGRDBMigration" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionNetworkingKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift b/SessionMessagingKit/Database/Migrations/_010_FlagMessageHashAsDeletedOrInvalid.swift similarity index 81% rename from SessionNetworkingKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift rename to SessionMessagingKit/Database/Migrations/_010_FlagMessageHashAsDeletedOrInvalid.swift index 989df981c8..ff71d5ffbe 100644 --- a/SessionNetworkingKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift +++ b/SessionMessagingKit/Database/Migrations/_010_FlagMessageHashAsDeletedOrInvalid.swift @@ -7,9 +7,8 @@ import SessionUtilitiesKit /// This migration adds a flag to the `SnodeReceivedMessageInfo` so that when deleting interactions we can /// ignore their hashes when subsequently trying to fetch new messages (which results in the storage server returning /// messages from the beginning of time) -enum _004_FlagMessageHashAsDeletedOrInvalid: Migration { - static let target: TargetMigrations.Identifier = .networkingKit - static let identifier: String = "FlagMessageHashAsDeletedOrInvalid" +enum _010_FlagMessageHashAsDeletedOrInvalid: Migration { + static let identifier: String = "snodeKit.FlagMessageHashAsDeletedOrInvalid" static let minExpectedRunDuration: TimeInterval = 0.2 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift b/SessionMessagingKit/Database/Migrations/_011_RemoveLegacyYDB.swift similarity index 78% rename from SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift rename to SessionMessagingKit/Database/Migrations/_011_RemoveLegacyYDB.swift index c5c4c4a4d8..ef8588451f 100644 --- a/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift +++ b/SessionMessagingKit/Database/Migrations/_011_RemoveLegacyYDB.swift @@ -6,9 +6,8 @@ import SessionUtilitiesKit import SessionNetworkingKit /// This migration used to remove the legacy YapDatabase files (the old logic has been removed and is no longer supported so it now does nothing) -enum _004_RemoveLegacyYDB: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "RemoveLegacyYDB" +enum _011_RemoveLegacyYDB: Migration { + static let identifier: String = "messagingKit.RemoveLegacyYDB" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift b/SessionMessagingKit/Database/Migrations/_012_AddJobPriority.swift similarity index 90% rename from SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift rename to SessionMessagingKit/Database/Migrations/_012_AddJobPriority.swift index 852033b582..93a2c68752 100644 --- a/SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift +++ b/SessionMessagingKit/Database/Migrations/_012_AddJobPriority.swift @@ -2,10 +2,10 @@ import Foundation import GRDB +import SessionUtilitiesKit -enum _004_AddJobPriority: Migration { - static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "AddJobPriority" +enum _012_AddJobPriority: Migration { + static let identifier: String = "utilitiesKit.AddJobPriority" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_005_FixDeletedMessageReadState.swift b/SessionMessagingKit/Database/Migrations/_013_FixDeletedMessageReadState.swift similarity index 83% rename from SessionMessagingKit/Database/Migrations/_005_FixDeletedMessageReadState.swift rename to SessionMessagingKit/Database/Migrations/_013_FixDeletedMessageReadState.swift index efe33c321d..a49d63d0ca 100644 --- a/SessionMessagingKit/Database/Migrations/_005_FixDeletedMessageReadState.swift +++ b/SessionMessagingKit/Database/Migrations/_013_FixDeletedMessageReadState.swift @@ -5,9 +5,8 @@ import GRDB import SessionUtilitiesKit /// This migration fixes a bug where certain message variants could incorrectly be counted as unread messages -enum _005_FixDeletedMessageReadState: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "FixDeletedMessageReadState" +enum _013_FixDeletedMessageReadState: Migration { + static let identifier: String = "messagingKit.FixDeletedMessageReadState" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_006_FixHiddenModAdminSupport.swift b/SessionMessagingKit/Database/Migrations/_014_FixHiddenModAdminSupport.swift similarity index 85% rename from SessionMessagingKit/Database/Migrations/_006_FixHiddenModAdminSupport.swift rename to SessionMessagingKit/Database/Migrations/_014_FixHiddenModAdminSupport.swift index 006b04c283..5247ae2d79 100644 --- a/SessionMessagingKit/Database/Migrations/_006_FixHiddenModAdminSupport.swift +++ b/SessionMessagingKit/Database/Migrations/_014_FixHiddenModAdminSupport.swift @@ -6,9 +6,8 @@ import SessionUtilitiesKit /// This migration fixes an issue where hidden mods/admins weren't getting recognised as mods/admins, it reset's the `info_updates` /// for open groups so they will fully re-fetch their mod/admin lists -enum _006_FixHiddenModAdminSupport: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "FixHiddenModAdminSupport" +enum _014_FixHiddenModAdminSupport: Migration { + static let identifier: String = "messagingKit.FixHiddenModAdminSupport" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_007_HomeQueryOptimisationIndexes.swift b/SessionMessagingKit/Database/Migrations/_015_HomeQueryOptimisationIndexes.swift similarity index 81% rename from SessionMessagingKit/Database/Migrations/_007_HomeQueryOptimisationIndexes.swift rename to SessionMessagingKit/Database/Migrations/_015_HomeQueryOptimisationIndexes.swift index bf8ded493e..3a0a617928 100644 --- a/SessionMessagingKit/Database/Migrations/_007_HomeQueryOptimisationIndexes.swift +++ b/SessionMessagingKit/Database/Migrations/_015_HomeQueryOptimisationIndexes.swift @@ -7,9 +7,8 @@ import GRDB import SessionUtilitiesKit /// This migration adds an index to the interaction table in order to improve the performance of retrieving the number of unread interactions -enum _007_HomeQueryOptimisationIndexes: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "HomeQueryOptimisationIndexes" +enum _015_HomeQueryOptimisationIndexes: Migration { + static let identifier: String = "messagingKit.HomeQueryOptimisationIndexes" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/Session/Database/Migrations/_001_ThemePreferences.swift b/SessionMessagingKit/Database/Migrations/_016_ThemePreferences.swift similarity index 78% rename from Session/Database/Migrations/_001_ThemePreferences.swift rename to SessionMessagingKit/Database/Migrations/_016_ThemePreferences.swift index 8bb9987f95..f19020e3ed 100644 --- a/Session/Database/Migrations/_001_ThemePreferences.swift +++ b/SessionMessagingKit/Database/Migrations/_016_ThemePreferences.swift @@ -13,9 +13,8 @@ import SessionUtilitiesKit /// **Note:** This migration used to live within `SessionUIKit` but we wanted to isolate it and remove dependencies from it so we /// needed to extract this migration into the `Session` and `SessionShareExtension` targets (since both need theming they both /// need to provide this migration as an option during setup) -enum _001_ThemePreferences: Migration { - static let target: TargetMigrations.Identifier = ._deprecatedUIKit - static let identifier: String = "ThemePreferences" +enum _016_ThemePreferences: Migration { + static let identifier: String = "uiKit.ThemePreferences" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] @@ -86,25 +85,3 @@ private extension Theme.PrimaryColor { } } } - -enum DeprecatedUIKitMigrationTarget: MigratableTarget { - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: ._deprecatedUIKit, - migrations: [ - // Want to ensure the initial DB stuff has been completed before doing any - // SNUIKit migrations - [], // Initial DB Creation - [], // YDB to GRDB Migration - [], // Legacy DB removal - [ - _001_ThemePreferences.self - ], // Add job priorities - [], // Fix thread FTS - [], - [], // Renamed `Setting` to `KeyValueStore` - [] - ] - ) - } -} diff --git a/SessionMessagingKit/Database/Migrations/_008_EmojiReacts.swift b/SessionMessagingKit/Database/Migrations/_017_EmojiReacts.swift similarity index 91% rename from SessionMessagingKit/Database/Migrations/_008_EmojiReacts.swift rename to SessionMessagingKit/Database/Migrations/_017_EmojiReacts.swift index bcd9c2f84b..c102846bad 100644 --- a/SessionMessagingKit/Database/Migrations/_008_EmojiReacts.swift +++ b/SessionMessagingKit/Database/Migrations/_017_EmojiReacts.swift @@ -5,9 +5,8 @@ import GRDB import SessionUtilitiesKit /// This migration adds the new types needed for Emoji Reacts -enum _008_EmojiReacts: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "EmojiReacts" +enum _017_EmojiReacts: Migration { + static let identifier: String = "messagingKit.EmojiReacts" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [Reaction.self] diff --git a/SessionMessagingKit/Database/Migrations/_009_OpenGroupPermission.swift b/SessionMessagingKit/Database/Migrations/_018_OpenGroupPermission.swift similarity index 83% rename from SessionMessagingKit/Database/Migrations/_009_OpenGroupPermission.swift rename to SessionMessagingKit/Database/Migrations/_018_OpenGroupPermission.swift index b8e7c47efb..bf51074058 100644 --- a/SessionMessagingKit/Database/Migrations/_009_OpenGroupPermission.swift +++ b/SessionMessagingKit/Database/Migrations/_018_OpenGroupPermission.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _009_OpenGroupPermission: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "OpenGroupPermission" +enum _018_OpenGroupPermission: Migration { + static let identifier: String = "messagingKit.OpenGroupPermission" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_010_AddThreadIdToFTS.swift b/SessionMessagingKit/Database/Migrations/_019_AddThreadIdToFTS.swift similarity index 82% rename from SessionMessagingKit/Database/Migrations/_010_AddThreadIdToFTS.swift rename to SessionMessagingKit/Database/Migrations/_019_AddThreadIdToFTS.swift index 9c2aea1207..92dfecc4fd 100644 --- a/SessionMessagingKit/Database/Migrations/_010_AddThreadIdToFTS.swift +++ b/SessionMessagingKit/Database/Migrations/_019_AddThreadIdToFTS.swift @@ -6,9 +6,8 @@ import SessionUtilitiesKit /// This migration recreates the interaction FTS table and adds the threadId so we can do a performant in-conversation /// searh (currently it's much slower than the global search) -enum _010_AddThreadIdToFTS: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "AddThreadIdToFTS" +enum _019_AddThreadIdToFTS: Migration { + static let identifier: String = "messagingKit.AddThreadIdToFTS" static let minExpectedRunDuration: TimeInterval = 3 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] @@ -22,7 +21,7 @@ enum _010_AddThreadIdToFTS: Migration { try db.create(virtualTable: "interaction_fts", using: FTS5()) { t in t.synchronize(withTable: "interaction") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("body") t.column("threadId") diff --git a/SessionUtilitiesKit/Database/Migrations/_005_AddJobUniqueHash.swift b/SessionMessagingKit/Database/Migrations/_020_AddJobUniqueHash.swift similarity index 76% rename from SessionUtilitiesKit/Database/Migrations/_005_AddJobUniqueHash.swift rename to SessionMessagingKit/Database/Migrations/_020_AddJobUniqueHash.swift index e4a36701f5..b9bebf7e81 100644 --- a/SessionUtilitiesKit/Database/Migrations/_005_AddJobUniqueHash.swift +++ b/SessionMessagingKit/Database/Migrations/_020_AddJobUniqueHash.swift @@ -2,10 +2,10 @@ import Foundation import GRDB +import SessionUtilitiesKit -enum _005_AddJobUniqueHash: Migration { - static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "AddJobUniqueHash" +enum _020_AddJobUniqueHash: Migration { + static let identifier: String = "utilitiesKit.AddJobUniqueHash" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionNetworkingKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift b/SessionMessagingKit/Database/Migrations/_021_AddSnodeReveivedMessageInfoPrimaryKey.swift similarity index 91% rename from SessionNetworkingKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift rename to SessionMessagingKit/Database/Migrations/_021_AddSnodeReveivedMessageInfoPrimaryKey.swift index 6565fc40d1..55e215871e 100644 --- a/SessionNetworkingKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift +++ b/SessionMessagingKit/Database/Migrations/_021_AddSnodeReveivedMessageInfoPrimaryKey.swift @@ -2,12 +2,12 @@ import Foundation import GRDB +import SessionNetworkingKit import SessionUtilitiesKit /// This migration adds a primary key to `SnodeReceivedMessageInfo` based on the key and hash to speed up lookup -enum _005_AddSnodeReveivedMessageInfoPrimaryKey: Migration { - static let target: TargetMigrations.Identifier = .networkingKit - static let identifier: String = "AddSnodeReveivedMessageInfoPrimaryKey" +enum _021_AddSnodeReveivedMessageInfoPrimaryKey: Migration { + static let identifier: String = "snodeKit.AddSnodeReveivedMessageInfoPrimaryKey" static let minExpectedRunDuration: TimeInterval = 0.2 static let createdTables: [(TableRecord & FetchableRecord).Type] = [SnodeReceivedMessageInfo.self] diff --git a/SessionNetworkingKit/Database/Migrations/_006_DropSnodeCache.swift b/SessionMessagingKit/Database/Migrations/_022_DropSnodeCache.swift similarity index 86% rename from SessionNetworkingKit/Database/Migrations/_006_DropSnodeCache.swift rename to SessionMessagingKit/Database/Migrations/_022_DropSnodeCache.swift index 6cc16ea4ef..af5ceaaa5d 100644 --- a/SessionNetworkingKit/Database/Migrations/_006_DropSnodeCache.swift +++ b/SessionMessagingKit/Database/Migrations/_022_DropSnodeCache.swift @@ -5,9 +5,8 @@ import GRDB import SessionUtilitiesKit /// This migration drops the current `SnodePool` and `SnodeSet` and their associated jobs as they are handled by `libSession` now -enum _006_DropSnodeCache: Migration { - static let target: TargetMigrations.Identifier = .networkingKit - static let identifier: String = "DropSnodeCache" +enum _022_DropSnodeCache: Migration { + static let identifier: String = "snodeKit.DropSnodeCache" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionNetworkingKit/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift b/SessionMessagingKit/Database/Migrations/_023_SplitSnodeReceivedMessageInfo.swift similarity index 96% rename from SessionNetworkingKit/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift rename to SessionMessagingKit/Database/Migrations/_023_SplitSnodeReceivedMessageInfo.swift index 779eecee5e..e77ead364d 100644 --- a/SessionNetworkingKit/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift +++ b/SessionMessagingKit/Database/Migrations/_023_SplitSnodeReceivedMessageInfo.swift @@ -2,12 +2,12 @@ import Foundation import GRDB +import SessionNetworkingKit import SessionUtilitiesKit /// This migration splits the old `key` structure used for `SnodeReceivedMessageInfo` into separate columns for more efficient querying -enum _007_SplitSnodeReceivedMessageInfo: Migration { - static let target: TargetMigrations.Identifier = .networkingKit - static let identifier: String = "SplitSnodeReceivedMessageInfo" +enum _023_SplitSnodeReceivedMessageInfo: Migration { + static let identifier: String = "snodeKit.SplitSnodeReceivedMessageInfo" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [SnodeReceivedMessageInfo.self] diff --git a/SessionNetworkingKit/Database/Migrations/_008_ResetUserConfigLastHashes.swift b/SessionMessagingKit/Database/Migrations/_024_ResetUserConfigLastHashes.swift similarity index 84% rename from SessionNetworkingKit/Database/Migrations/_008_ResetUserConfigLastHashes.swift rename to SessionMessagingKit/Database/Migrations/_024_ResetUserConfigLastHashes.swift index 1eb3e6d265..2fad3edb4e 100644 --- a/SessionNetworkingKit/Database/Migrations/_008_ResetUserConfigLastHashes.swift +++ b/SessionMessagingKit/Database/Migrations/_024_ResetUserConfigLastHashes.swift @@ -2,13 +2,13 @@ import Foundation import GRDB +import SessionNetworkingKit import SessionUtilitiesKit /// This migration resets the `lastHash` value for all user config namespaces to force the app to fetch the latest config /// messages in case there are multi-part config message we had previously seen and failed to merge -enum _008_ResetUserConfigLastHashes: Migration { - static let target: TargetMigrations.Identifier = .networkingKit - static let identifier: String = "ResetUserConfigLastHashes" +enum _024_ResetUserConfigLastHashes: Migration { + static let identifier: String = "snodeKit.ResetUserConfigLastHashes" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_011_AddPendingReadReceipts.swift b/SessionMessagingKit/Database/Migrations/_025_AddPendingReadReceipts.swift similarity index 89% rename from SessionMessagingKit/Database/Migrations/_011_AddPendingReadReceipts.swift rename to SessionMessagingKit/Database/Migrations/_025_AddPendingReadReceipts.swift index 5f51432095..0b0d5fec63 100644 --- a/SessionMessagingKit/Database/Migrations/_011_AddPendingReadReceipts.swift +++ b/SessionMessagingKit/Database/Migrations/_025_AddPendingReadReceipts.swift @@ -6,9 +6,8 @@ import SessionUtilitiesKit /// This migration adds a table to track pending read receipts (it's possible to receive a read receipt message before getting the original /// message due to how one-to-one conversations work, by storing pending read receipts we should be able to prevent this case) -enum _011_AddPendingReadReceipts: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "AddPendingReadReceipts" +enum _025_AddPendingReadReceipts: Migration { + static let identifier: String = "messagingKit.AddPendingReadReceipts" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [PendingReadReceipt.self] diff --git a/SessionMessagingKit/Database/Migrations/_012_AddFTSIfNeeded.swift b/SessionMessagingKit/Database/Migrations/_026_AddFTSIfNeeded.swift similarity index 80% rename from SessionMessagingKit/Database/Migrations/_012_AddFTSIfNeeded.swift rename to SessionMessagingKit/Database/Migrations/_026_AddFTSIfNeeded.swift index a030deed3f..b655432c2c 100644 --- a/SessionMessagingKit/Database/Migrations/_012_AddFTSIfNeeded.swift +++ b/SessionMessagingKit/Database/Migrations/_026_AddFTSIfNeeded.swift @@ -5,9 +5,8 @@ import GRDB import SessionUtilitiesKit /// This migration adds the FTS table back for internal test users whose FTS table was removed unintentionally -enum _012_AddFTSIfNeeded: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "AddFTSIfNeeded" +enum _026_AddFTSIfNeeded: Migration { + static let identifier: String = "messagingKit.AddFTSIfNeeded" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] @@ -17,7 +16,7 @@ enum _012_AddFTSIfNeeded: Migration { if try db.tableExists("interaction_fts") == false { try db.create(virtualTable: "interaction_fts", using: FTS5()) { t in t.synchronize(withTable: "interaction") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("body") t.column("threadId") diff --git a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_027_SessionUtilChanges.swift similarity index 98% rename from SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift rename to SessionMessagingKit/Database/Migrations/_027_SessionUtilChanges.swift index cb67ad5bf5..57e76b93a9 100644 --- a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_027_SessionUtilChanges.swift @@ -9,9 +9,8 @@ import SessionUtil import SessionUtilitiesKit /// This migration makes the neccessary changes to support the updated user config syncing system -enum _013_SessionUtilChanges: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "SessionUtilChanges" +enum _027_SessionUtilChanges: Migration { + static let identifier: String = "messagingKit.SessionUtilChanges" static let minExpectedRunDuration: TimeInterval = 0.4 static let createdTables: [(TableRecord & FetchableRecord).Type] = [ConfigDump.self] @@ -229,7 +228,7 @@ enum _013_SessionUtilChanges: Migration { } } -private extension _013_SessionUtilChanges { +private extension _027_SessionUtilChanges { static func generateLegacyClosedGroupKeyPairHash(threadId: String, publicKey: Data, secretKey: Data) -> String { return Data(Insecure.MD5 .hash(data: threadId.bytes + publicKey.bytes + secretKey.bytes) diff --git a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift b/SessionMessagingKit/Database/Migrations/_028_GenerateInitialUserConfigDumps.swift similarity index 98% rename from SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift rename to SessionMessagingKit/Database/Migrations/_028_GenerateInitialUserConfigDumps.swift index c81a813e12..a53031dd46 100644 --- a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift +++ b/SessionMessagingKit/Database/Migrations/_028_GenerateInitialUserConfigDumps.swift @@ -6,9 +6,8 @@ import SessionUtil import SessionUtilitiesKit /// This migration goes through the current state of the database and generates config dumps for the user config types -enum _014_GenerateInitialUserConfigDumps: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "GenerateInitialUserConfigDumps" +enum _028_GenerateInitialUserConfigDumps: Migration { + static let identifier: String = "messagingKit.GenerateInitialUserConfigDumps" static let minExpectedRunDuration: TimeInterval = 4.0 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift b/SessionMessagingKit/Database/Migrations/_029_BlockCommunityMessageRequests.swift similarity index 94% rename from SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift rename to SessionMessagingKit/Database/Migrations/_029_BlockCommunityMessageRequests.swift index dd58e13355..22cb579ef3 100644 --- a/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift +++ b/SessionMessagingKit/Database/Migrations/_029_BlockCommunityMessageRequests.swift @@ -5,9 +5,8 @@ import GRDB import SessionUtilitiesKit /// This migration adds a flag indicating whether a profile has indicated it is blocking community message requests -enum _015_BlockCommunityMessageRequests: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "BlockCommunityMessageRequests" +enum _029_BlockCommunityMessageRequests: Migration { + static let identifier: String = "messagingKit.BlockCommunityMessageRequests" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_016_MakeBrokenProfileTimestampsNullable.swift b/SessionMessagingKit/Database/Migrations/_030_MakeBrokenProfileTimestampsNullable.swift similarity index 89% rename from SessionMessagingKit/Database/Migrations/_016_MakeBrokenProfileTimestampsNullable.swift rename to SessionMessagingKit/Database/Migrations/_030_MakeBrokenProfileTimestampsNullable.swift index 82816602ca..dbbeb35044 100644 --- a/SessionMessagingKit/Database/Migrations/_016_MakeBrokenProfileTimestampsNullable.swift +++ b/SessionMessagingKit/Database/Migrations/_030_MakeBrokenProfileTimestampsNullable.swift @@ -6,9 +6,8 @@ import SessionUtilitiesKit /// This migration updates the tiemstamps added to the `Profile` in earlier migrations to be nullable (having it not null /// results in migration issues when a user jumps between multiple versions) -enum _016_MakeBrokenProfileTimestampsNullable: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "MakeBrokenProfileTimestampsNullable" +enum _030_MakeBrokenProfileTimestampsNullable: Migration { + static let identifier: String = "messagingKit.MakeBrokenProfileTimestampsNullable" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_017_RebuildFTSIfNeeded_2_4_5.swift b/SessionMessagingKit/Database/Migrations/_031_RebuildFTSIfNeeded_2_4_5.swift similarity index 85% rename from SessionMessagingKit/Database/Migrations/_017_RebuildFTSIfNeeded_2_4_5.swift rename to SessionMessagingKit/Database/Migrations/_031_RebuildFTSIfNeeded_2_4_5.swift index c9c9240fde..9660270f21 100644 --- a/SessionMessagingKit/Database/Migrations/_017_RebuildFTSIfNeeded_2_4_5.swift +++ b/SessionMessagingKit/Database/Migrations/_031_RebuildFTSIfNeeded_2_4_5.swift @@ -7,9 +7,8 @@ import GRDB import SessionUtilitiesKit /// This migration adds the FTS table back if either the tables or any of the triggers no longer exist -enum _017_RebuildFTSIfNeeded_2_4_5: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "RebuildFTSIfNeeded_2_4_5" +enum _031_RebuildFTSIfNeeded_2_4_5: Migration { + static let identifier: String = "messagingKit.RebuildFTSIfNeeded_2_4_5" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] @@ -30,7 +29,7 @@ enum _017_RebuildFTSIfNeeded_2_4_5: Migration { try db.create(virtualTable: "interaction_fts", using: FTS5()) { t in t.synchronize(withTable: "interaction") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("body") t.column("threadId") @@ -44,7 +43,7 @@ enum _017_RebuildFTSIfNeeded_2_4_5: Migration { try db.create(virtualTable: "profile_fts", using: FTS5()) { t in t.synchronize(withTable: "profile") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("nickname") t.column("name") @@ -58,7 +57,7 @@ enum _017_RebuildFTSIfNeeded_2_4_5: Migration { try db.create(virtualTable: "closedGroup_fts", using: FTS5()) { t in t.synchronize(withTable: "closedGroup") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("name") } @@ -71,7 +70,7 @@ enum _017_RebuildFTSIfNeeded_2_4_5: Migration { try db.create(virtualTable: "openGroup_fts", using: FTS5()) { t in t.synchronize(withTable: "openGroup") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("name") } diff --git a/SessionMessagingKit/Database/Migrations/_018_DisappearingMessagesConfiguration.swift b/SessionMessagingKit/Database/Migrations/_032_DisappearingMessagesConfiguration.swift similarity index 97% rename from SessionMessagingKit/Database/Migrations/_018_DisappearingMessagesConfiguration.swift rename to SessionMessagingKit/Database/Migrations/_032_DisappearingMessagesConfiguration.swift index 809c426e56..4bf07b018f 100644 --- a/SessionMessagingKit/Database/Migrations/_018_DisappearingMessagesConfiguration.swift +++ b/SessionMessagingKit/Database/Migrations/_032_DisappearingMessagesConfiguration.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _018_DisappearingMessagesConfiguration: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "DisappearingMessagesWithTypes" +enum _032_DisappearingMessagesConfiguration: Migration { + static let identifier: String = "messagingKit.DisappearingMessagesWithTypes" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_019_ScheduleAppUpdateCheckJob.swift b/SessionMessagingKit/Database/Migrations/_033_ScheduleAppUpdateCheckJob.swift similarity index 84% rename from SessionMessagingKit/Database/Migrations/_019_ScheduleAppUpdateCheckJob.swift rename to SessionMessagingKit/Database/Migrations/_033_ScheduleAppUpdateCheckJob.swift index 9f5bd4c724..f0df877278 100644 --- a/SessionMessagingKit/Database/Migrations/_019_ScheduleAppUpdateCheckJob.swift +++ b/SessionMessagingKit/Database/Migrations/_033_ScheduleAppUpdateCheckJob.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _019_ScheduleAppUpdateCheckJob: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "ScheduleAppUpdateCheckJob" +enum _033_ScheduleAppUpdateCheckJob: Migration { + static let identifier: String = "messagingKit.ScheduleAppUpdateCheckJob" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_020_AddMissingWhisperFlag.swift b/SessionMessagingKit/Database/Migrations/_034_AddMissingWhisperFlag.swift similarity index 81% rename from SessionMessagingKit/Database/Migrations/_020_AddMissingWhisperFlag.swift rename to SessionMessagingKit/Database/Migrations/_034_AddMissingWhisperFlag.swift index 90dbfc4fbd..ecd158b8b1 100644 --- a/SessionMessagingKit/Database/Migrations/_020_AddMissingWhisperFlag.swift +++ b/SessionMessagingKit/Database/Migrations/_034_AddMissingWhisperFlag.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _020_AddMissingWhisperFlag: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "AddMissingWhisperFlag" +enum _034_AddMissingWhisperFlag: Migration { + static let identifier: String = "messagingKit.AddMissingWhisperFlag" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_021_ReworkRecipientState.swift b/SessionMessagingKit/Database/Migrations/_035_ReworkRecipientState.swift similarity index 97% rename from SessionMessagingKit/Database/Migrations/_021_ReworkRecipientState.swift rename to SessionMessagingKit/Database/Migrations/_035_ReworkRecipientState.swift index a47d202666..f0ebe35f20 100644 --- a/SessionMessagingKit/Database/Migrations/_021_ReworkRecipientState.swift +++ b/SessionMessagingKit/Database/Migrations/_035_ReworkRecipientState.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _021_ReworkRecipientState: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "ReworkRecipientState" +enum _035_ReworkRecipientState: Migration { + static let identifier: String = "messagingKit.ReworkRecipientState" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] @@ -180,7 +179,7 @@ enum _021_ReworkRecipientState: Migration { } } -private extension _021_ReworkRecipientState { +private extension _035_ReworkRecipientState { enum LegacyState: Int { case sending case failed diff --git a/SessionMessagingKit/Database/Migrations/_022_GroupsRebuildChanges.swift b/SessionMessagingKit/Database/Migrations/_036_GroupsRebuildChanges.swift similarity index 97% rename from SessionMessagingKit/Database/Migrations/_022_GroupsRebuildChanges.swift rename to SessionMessagingKit/Database/Migrations/_036_GroupsRebuildChanges.swift index d3ad1f25be..a7444ce0f2 100644 --- a/SessionMessagingKit/Database/Migrations/_022_GroupsRebuildChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_036_GroupsRebuildChanges.swift @@ -8,9 +8,8 @@ import GRDB import SessionNetworkingKit import SessionUtilitiesKit -enum _022_GroupsRebuildChanges: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "GroupsRebuildChanges" +enum _036_GroupsRebuildChanges: Migration { + static let identifier: String = "messagingKit.GroupsRebuildChanges" static let minExpectedRunDuration: TimeInterval = 0.1 static var createdTables: [(FetchableRecord & TableRecord).Type] = [] @@ -209,7 +208,7 @@ enum _022_GroupsRebuildChanges: Migration { } } -private extension _022_GroupsRebuildChanges { +private extension _036_GroupsRebuildChanges { static func generateFilename(format: ImageFormat = .jpeg, using dependencies: Dependencies) -> String { return dependencies[singleton: .crypto] .generate(.uuid()) diff --git a/SessionMessagingKit/Database/Migrations/_023_GroupsExpiredFlag.swift b/SessionMessagingKit/Database/Migrations/_037_GroupsExpiredFlag.swift similarity index 76% rename from SessionMessagingKit/Database/Migrations/_023_GroupsExpiredFlag.swift rename to SessionMessagingKit/Database/Migrations/_037_GroupsExpiredFlag.swift index 2bffc37639..294efd4846 100644 --- a/SessionMessagingKit/Database/Migrations/_023_GroupsExpiredFlag.swift +++ b/SessionMessagingKit/Database/Migrations/_037_GroupsExpiredFlag.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _023_GroupsExpiredFlag: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "GroupsExpiredFlag" +enum _037_GroupsExpiredFlag: Migration { + static let identifier: String = "messagingKit.GroupsExpiredFlag" static let minExpectedRunDuration: TimeInterval = 0.1 static var createdTables: [(FetchableRecord & TableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_024_FixBustedInteractionVariant.swift b/SessionMessagingKit/Database/Migrations/_038_FixBustedInteractionVariant.swift similarity index 83% rename from SessionMessagingKit/Database/Migrations/_024_FixBustedInteractionVariant.swift rename to SessionMessagingKit/Database/Migrations/_038_FixBustedInteractionVariant.swift index 9b65965362..5929ad6c87 100644 --- a/SessionMessagingKit/Database/Migrations/_024_FixBustedInteractionVariant.swift +++ b/SessionMessagingKit/Database/Migrations/_038_FixBustedInteractionVariant.swift @@ -7,9 +7,8 @@ import SessionUtilitiesKit /// There was a bug with internal releases of the Groups Rebuild feature where we incorrectly assigned an `Interaction.Variant` /// value of `3` to deleted message artifacts when it should have been `2`, this migration updates any interactions with a value of `2` /// to be `3` -enum _024_FixBustedInteractionVariant: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "FixBustedInteractionVariant" +enum _038_FixBustedInteractionVariant: Migration { + static let identifier: String = "messagingKit.FixBustedInteractionVariant" static let minExpectedRunDuration: TimeInterval = 0.1 static var createdTables: [(FetchableRecord & TableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_025_DropLegacyClosedGroupKeyPairTable.swift b/SessionMessagingKit/Database/Migrations/_039_DropLegacyClosedGroupKeyPairTable.swift similarity index 74% rename from SessionMessagingKit/Database/Migrations/_025_DropLegacyClosedGroupKeyPairTable.swift rename to SessionMessagingKit/Database/Migrations/_039_DropLegacyClosedGroupKeyPairTable.swift index afc0dd376d..20111dade4 100644 --- a/SessionMessagingKit/Database/Migrations/_025_DropLegacyClosedGroupKeyPairTable.swift +++ b/SessionMessagingKit/Database/Migrations/_039_DropLegacyClosedGroupKeyPairTable.swift @@ -6,9 +6,8 @@ import SessionUtilitiesKit /// Legacy closed groups are no longer supported so we can drop the `closedGroupKeyPair` table from /// the database -enum _025_DropLegacyClosedGroupKeyPairTable: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "DropLegacyClosedGroupKeyPairTable" +enum _039_DropLegacyClosedGroupKeyPairTable: Migration { + static let identifier: String = "messagingKit.DropLegacyClosedGroupKeyPairTable" static let minExpectedRunDuration: TimeInterval = 0.1 static var createdTables: [(FetchableRecord & TableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_026_MessageDeduplicationTable.swift b/SessionMessagingKit/Database/Migrations/_040_MessageDeduplicationTable.swift similarity index 98% rename from SessionMessagingKit/Database/Migrations/_026_MessageDeduplicationTable.swift rename to SessionMessagingKit/Database/Migrations/_040_MessageDeduplicationTable.swift index 5addd66869..5431858f19 100644 --- a/SessionMessagingKit/Database/Migrations/_026_MessageDeduplicationTable.swift +++ b/SessionMessagingKit/Database/Migrations/_040_MessageDeduplicationTable.swift @@ -8,9 +8,8 @@ import SessionNetworkingKit /// The different platforms use different approaches for message deduplication but in the future we want to shift the database logic into /// `libSession` so it makes sense to try to define a longer-term deduplication approach we we can use in `libSession`, additonally /// the PN extension will need to replicate this deduplication data so having a single source-of-truth for the data will make things easier -enum _026_MessageDeduplicationTable: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "MessageDeduplicationTable" +enum _040_MessageDeduplicationTable: Migration { + static let identifier: String = "messagingKit.MessageDeduplicationTable" static let minExpectedRunDuration: TimeInterval = 5 static var createdTables: [(FetchableRecord & TableRecord).Type] = [ MessageDeduplication.self @@ -343,7 +342,7 @@ enum _026_MessageDeduplicationTable: Migration { } } -internal extension _026_MessageDeduplicationTable { +internal extension _040_MessageDeduplicationTable { static func legacyDedupeIdentifier( variant: Interaction.Variant, timestampMs: Int64 @@ -372,7 +371,7 @@ internal extension _026_MessageDeduplicationTable { } } -internal extension _026_MessageDeduplicationTable { +internal extension _040_MessageDeduplicationTable { enum ControlMessageProcessRecordVariant: Int { case readReceipt = 1 case typingIndicator = 2 diff --git a/SessionUtilitiesKit/Database/Migrations/_006_RenameTableSettingToKeyValueStore.swift b/SessionMessagingKit/Database/Migrations/_041_RenameTableSettingToKeyValueStore.swift similarity index 68% rename from SessionUtilitiesKit/Database/Migrations/_006_RenameTableSettingToKeyValueStore.swift rename to SessionMessagingKit/Database/Migrations/_041_RenameTableSettingToKeyValueStore.swift index ffad28e128..48a366aecf 100644 --- a/SessionUtilitiesKit/Database/Migrations/_006_RenameTableSettingToKeyValueStore.swift +++ b/SessionMessagingKit/Database/Migrations/_041_RenameTableSettingToKeyValueStore.swift @@ -2,10 +2,10 @@ import Foundation import GRDB +import SessionUtilitiesKit -enum _006_RenameTableSettingToKeyValueStore: Migration { - static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "RenameTableSettingToKeyValueStore" // stringlint:disable +enum _041_RenameTableSettingToKeyValueStore: Migration { + static let identifier: String = "utilitiesKit.RenameTableSettingToKeyValueStore" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [ KeyValueStore.self ] diff --git a/SessionMessagingKit/Database/Migrations/_027_MoveSettingsToLibSession.swift b/SessionMessagingKit/Database/Migrations/_042_MoveSettingsToLibSession.swift similarity index 97% rename from SessionMessagingKit/Database/Migrations/_027_MoveSettingsToLibSession.swift rename to SessionMessagingKit/Database/Migrations/_042_MoveSettingsToLibSession.swift index 3f6353e72c..5e63bd1bff 100644 --- a/SessionMessagingKit/Database/Migrations/_027_MoveSettingsToLibSession.swift +++ b/SessionMessagingKit/Database/Migrations/_042_MoveSettingsToLibSession.swift @@ -6,9 +6,8 @@ import SessionUIKit import SessionUtilitiesKit /// This migration extracts an old settings from the database and saves them into libSession -enum _027_MoveSettingsToLibSession: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "MoveSettingsToLibSession" +enum _042_MoveSettingsToLibSession: Migration { + static let identifier: String = "messagingKit.MoveSettingsToLibSession" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_028_RenameAttachments.swift b/SessionMessagingKit/Database/Migrations/_043_RenameAttachments.swift similarity index 99% rename from SessionMessagingKit/Database/Migrations/_028_RenameAttachments.swift rename to SessionMessagingKit/Database/Migrations/_043_RenameAttachments.swift index a4e8969695..ff94b962c2 100644 --- a/SessionMessagingKit/Database/Migrations/_028_RenameAttachments.swift +++ b/SessionMessagingKit/Database/Migrations/_043_RenameAttachments.swift @@ -8,9 +8,8 @@ import SessionUtilitiesKit /// This migration renames all attachments to use a hash of the download url for the filename instead of a random UUID (means we can /// generate the filename just from the URL and don't need to store the filename) -enum _028_RenameAttachments: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "RenameAttachments" +enum _043_RenameAttachments: Migration { + static let identifier: String = "messagingKit.RenameAttachments" static let minExpectedRunDuration: TimeInterval = 3 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_029_AddProMessageFlag.swift b/SessionMessagingKit/Database/Migrations/_044_AddProMessageFlag.swift similarity index 76% rename from SessionMessagingKit/Database/Migrations/_029_AddProMessageFlag.swift rename to SessionMessagingKit/Database/Migrations/_044_AddProMessageFlag.swift index 0d2751199e..7f51d1ffc2 100644 --- a/SessionMessagingKit/Database/Migrations/_029_AddProMessageFlag.swift +++ b/SessionMessagingKit/Database/Migrations/_044_AddProMessageFlag.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _029_AddProMessageFlag: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "AddProMessageFlag" +enum _044_AddProMessageFlag: Migration { + static let identifier: String = "messagingKit.AddProMessageFlag" static let minExpectedRunDuration: TimeInterval = 0.1 static var createdTables: [(FetchableRecord & TableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Models/MessageDeduplication.swift b/SessionMessagingKit/Database/Models/MessageDeduplication.swift index 6a73d52936..e8b5a8f531 100644 --- a/SessionMessagingKit/Database/Models/MessageDeduplication.swift +++ b/SessionMessagingKit/Database/Models/MessageDeduplication.swift @@ -209,7 +209,7 @@ public extension MessageDeduplication { _ processedMessage: ProcessedMessage, using dependencies: Dependencies ) throws { - typealias Variant = _026_MessageDeduplicationTable.ControlMessageProcessRecordVariant + typealias Variant = _040_MessageDeduplicationTable.ControlMessageProcessRecordVariant try ensureMessageIsNotADuplicate( threadId: processedMessage.threadId, uniqueIdentifier: processedMessage.uniqueIdentifier, @@ -402,12 +402,12 @@ private extension MessageDeduplication { _ db: ObservingDatabase, threadId: String, legacyIdentifier: String?, - legacyVariant: _026_MessageDeduplicationTable.ControlMessageProcessRecordVariant?, + legacyVariant: _040_MessageDeduplicationTable.ControlMessageProcessRecordVariant?, timestampMs: Int64?, serverExpirationTimestamp: TimeInterval?, using dependencies: Dependencies ) throws { - typealias Variant = _026_MessageDeduplicationTable.ControlMessageProcessRecordVariant + typealias Variant = _040_MessageDeduplicationTable.ControlMessageProcessRecordVariant guard let legacyIdentifier: String = legacyIdentifier, let legacyVariant: Variant = legacyVariant, @@ -463,7 +463,7 @@ private extension MessageDeduplication { } @available(*, deprecated, message: "⚠️ Remove this code once once enough time has passed since it's release (at least 1 month)") - static func getLegacyVariant(for variant: Message.Variant?) -> _026_MessageDeduplicationTable.ControlMessageProcessRecordVariant? { + static func getLegacyVariant(for variant: Message.Variant?) -> _040_MessageDeduplicationTable.ControlMessageProcessRecordVariant? { guard let variant: Message.Variant = variant else { return nil } switch variant { @@ -494,7 +494,7 @@ private extension MessageDeduplication { case .standard(_, _, _, let messageInfo, _): guard let timestampMs: UInt64 = messageInfo.message.sentTimestampMs, - let variant: _026_MessageDeduplicationTable.ControlMessageProcessRecordVariant = getLegacyVariant(for: Message.Variant(from: messageInfo.message)) + let variant: _040_MessageDeduplicationTable.ControlMessageProcessRecordVariant = getLegacyVariant(for: Message.Variant(from: messageInfo.message)) else { return nil } return "LegacyRecord-\(variant.rawValue)-\(timestampMs)" // stringlint:ignore diff --git a/SessionMessagingKitTests/Database/Models/MessageDeduplicationSpec.swift b/SessionMessagingKitTests/Database/Models/MessageDeduplicationSpec.swift index bd36080603..22fb802b43 100644 --- a/SessionMessagingKitTests/Database/Models/MessageDeduplicationSpec.swift +++ b/SessionMessagingKitTests/Database/Models/MessageDeduplicationSpec.swift @@ -18,9 +18,7 @@ class MessageDeduplicationSpec: AsyncSpec { @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies ) @TestState(singleton: .extensionHelper, in: dependencies) var mockExtensionHelper: MockExtensionHelper! = MockExtensionHelper( diff --git a/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift b/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift index 85d0809b20..130facfd6c 100644 --- a/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift +++ b/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift @@ -27,10 +27,7 @@ class DisplayPictureDownloadJobSpec: QuickSpec { ) @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) diff --git a/SessionMessagingKitTests/Jobs/MessageSendJobSpec.swift b/SessionMessagingKitTests/Jobs/MessageSendJobSpec.swift index 15012b5318..e549e2c221 100644 --- a/SessionMessagingKitTests/Jobs/MessageSendJobSpec.swift +++ b/SessionMessagingKitTests/Jobs/MessageSendJobSpec.swift @@ -35,10 +35,7 @@ class MessageSendJobSpec: QuickSpec { ) @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try SessionThread.upsert( diff --git a/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift b/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift index a226aab1df..79692802c7 100644 --- a/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift +++ b/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift @@ -20,10 +20,7 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) diff --git a/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift b/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift index 07bda1e05f..309aa452d8 100644 --- a/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift +++ b/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift @@ -28,11 +28,7 @@ class LibSessionGroupInfoSpec: QuickSpec { ) @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self, - SNNetworkingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) diff --git a/SessionMessagingKitTests/LibSession/LibSessionGroupMembersSpec.swift b/SessionMessagingKitTests/LibSession/LibSessionGroupMembersSpec.swift index 8001ede737..43f6c045de 100644 --- a/SessionMessagingKitTests/LibSession/LibSessionGroupMembersSpec.swift +++ b/SessionMessagingKitTests/LibSession/LibSessionGroupMembersSpec.swift @@ -27,10 +27,7 @@ class LibSessionGroupMembersSpec: QuickSpec { ) @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) diff --git a/SessionMessagingKitTests/LibSession/LibSessionSpec.swift b/SessionMessagingKitTests/LibSession/LibSessionSpec.swift index f23341efdf..cb8ff17dd8 100644 --- a/SessionMessagingKitTests/LibSession/LibSessionSpec.swift +++ b/SessionMessagingKitTests/LibSession/LibSessionSpec.swift @@ -27,10 +27,7 @@ class LibSessionSpec: QuickSpec { ) @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index 24635b39af..da31a7c154 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -110,10 +110,7 @@ class OpenGroupManagerSpec: QuickSpec { }() @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift index be86b9ed76..f731e18ecf 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift @@ -29,11 +29,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNNetworkingKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift index 2f55e63806..69d1a6d505 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift @@ -29,10 +29,7 @@ class MessageSenderGroupsSpec: QuickSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageSenderSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageSenderSpec.swift index 6ddb79b9bf..ee73e9fbc0 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageSenderSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageSenderSpec.swift @@ -17,10 +17,7 @@ class MessageSenderSpec: QuickSpec { @TestState var dependencies: TestDependencies! = TestDependencies() @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db) diff --git a/SessionMessagingKitTests/Sending & Receiving/Pollers/CommunityPollerSpec.swift b/SessionMessagingKitTests/Sending & Receiving/Pollers/CommunityPollerSpec.swift index 6f966ebf9c..79f98b853e 100644 --- a/SessionMessagingKitTests/Sending & Receiving/Pollers/CommunityPollerSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/Pollers/CommunityPollerSpec.swift @@ -21,10 +21,7 @@ class CommunityPollerSpec: AsyncSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) diff --git a/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift b/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift index 5cd9216cab..13dd801e31 100644 --- a/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift +++ b/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift @@ -24,10 +24,7 @@ class ExtensionHelperSpec: AsyncSpec { @TestState(singleton: .extensionHelper, in: dependencies) var extensionHelper: ExtensionHelper! = ExtensionHelper(using: dependencies) @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies ) @TestState(singleton: .crypto, in: dependencies) var mockCrypto: MockCrypto! = MockCrypto( diff --git a/SessionNetworkingKit/Configuration.swift b/SessionNetworkingKit/Configuration.swift deleted file mode 100644 index 18a73d2615..0000000000 --- a/SessionNetworkingKit/Configuration.swift +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import GRDB -import SessionUtilitiesKit - -public enum SNNetworkingKit: MigratableTarget { // Just to make the external API nice - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: .networkingKit, - migrations: [ - [ - _001_InitialSetupMigration.self, - _002_SetupStandardJobs.self - ], // Initial DB Creation - [ - _003_YDBToGRDBMigration.self - ], // YDB to GRDB Migration - [ - _004_FlagMessageHashAsDeletedOrInvalid.self - ], // Legacy DB removal - [], // Add job priorities - [], // Fix thread FTS - [ - _005_AddSnodeReveivedMessageInfoPrimaryKey.self, - _006_DropSnodeCache.self, - _007_SplitSnodeReceivedMessageInfo.self, - _008_ResetUserConfigLastHashes.self - ], - [], // Renamed `Setting` to `KeyValueStore` - [] - ] - ) - } -} diff --git a/SessionShareExtension/ShareNavController.swift b/SessionShareExtension/ShareNavController.swift index e977151317..39797ee76b 100644 --- a/SessionShareExtension/ShareNavController.swift +++ b/SessionShareExtension/ShareNavController.swift @@ -45,7 +45,6 @@ final class ShareNavController: UINavigationController { dependencies.warmCache(cache: .appVersion) AppSetup.setupEnvironment( - additionalMigrationTargets: [DeprecatedUIKitMigrationTarget.self], appSpecificBlock: { [dependencies] in // stringlint:ignore_start if !Log.loggerExists(withPrefix: "SessionShareExtension") { diff --git a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift index 7028b12544..beaaa7ed26 100644 --- a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift @@ -21,12 +21,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: AsyncSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNNetworkingKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try SessionThread( diff --git a/SessionTests/Conversations/Settings/ThreadNotificationSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadNotificationSettingsViewModelSpec.swift index 227c06bf86..8d2532b0b0 100644 --- a/SessionTests/Conversations/Settings/ThreadNotificationSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadNotificationSettingsViewModelSpec.swift @@ -21,12 +21,7 @@ class ThreadNotificationSettingsViewModelSpec: AsyncSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNNetworkingKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try SessionThread( diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index ec9d79c0a2..77514dedf8 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -29,12 +29,7 @@ class ThreadSettingsViewModelSpec: AsyncSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNNetworkingKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity( diff --git a/SessionTests/Database/DatabaseSpec.swift b/SessionTests/Database/DatabaseSpec.swift index 5c63cd7fae..c9b824b196 100644 --- a/SessionTests/Database/DatabaseSpec.swift +++ b/SessionTests/Database/DatabaseSpec.swift @@ -38,14 +38,7 @@ class DatabaseSpec: QuickSpec { @TestState var initialResult: Result! = nil @TestState var finalResult: Result! = nil - let allMigrations: [Storage.KeyedMigration] = SynchronousStorage.sortedMigrationInfo( - migrationTargets: [ - SNUtilitiesKit.self, - SNNetworkingKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ] - ) + let allMigrations: [Migration.Type] = SNMessagingKit.migrations let dynamicTests: [MigrationTest] = MigrationTest.extractTests(allMigrations) let allTableTypes: [(TableRecord & FetchableRecord).Type] = MigrationTest.extractDatabaseTypes(allMigrations) MigrationTest.explicitValues = [ @@ -75,12 +68,7 @@ class DatabaseSpec: QuickSpec { // MARK: -- can be created from an empty state it("can be created from an empty state") { mockStorage.perform( - migrationTargets: [ - SNUtilitiesKit.self, - SNNetworkingKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ], + migrations: allMigrations, async: false, onProgressUpdate: nil, onComplete: { result in initialResult = result } @@ -92,7 +80,7 @@ class DatabaseSpec: QuickSpec { // MARK: -- can still parse the database table types it("can still parse the database table types") { mockStorage.perform( - sortedMigrations: allMigrations, + migrations: allMigrations, async: false, onProgressUpdate: nil, onComplete: { result in initialResult = result } @@ -115,7 +103,7 @@ class DatabaseSpec: QuickSpec { // MARK: -- can still parse the database types setting null where possible it("can still parse the database types setting null where possible") { mockStorage.perform( - sortedMigrations: allMigrations, + migrations: allMigrations, async: false, onProgressUpdate: nil, onComplete: { result in initialResult = result } @@ -137,9 +125,9 @@ class DatabaseSpec: QuickSpec { // MARK: -- can migrate from X to Y dynamicTests.forEach { test in - it("can migrate from \(test.initialMigrationKey) to \(test.finalMigrationKey)") { + it("can migrate from \(test.initialMigrationIdentifier) to \(test.finalMigrationIdentifier)") { let initialStateResult: Result = { - if let cachedResult: Result = snapshotCache[test.initialMigrationKey] { + if let cachedResult: Result = snapshotCache[test.initialMigrationIdentifier] { return cachedResult } @@ -153,7 +141,7 @@ class DatabaseSpec: QuickSpec { // Generate dummy data (otherwise structural issues or invalid foreign keys won't error) var initialResult: Result! storage.perform( - sortedMigrations: test.initialMigrations, + migrations: test.initialMigrations, async: false, onProgressUpdate: nil, onComplete: { result in initialResult = result } @@ -163,10 +151,10 @@ class DatabaseSpec: QuickSpec { // Generate dummy data (otherwise structural issues or invalid foreign keys won't error) try MigrationTest.generateDummyData(storage, nullsWherePossible: false) - snapshotCache[test.initialMigrationKey] = .success(dbQueue) + snapshotCache[test.initialMigrationIdentifier] = .success(dbQueue) return .success(dbQueue) } catch { - snapshotCache[test.initialMigrationKey] = .failure(error) + snapshotCache[test.initialMigrationIdentifier] = .failure(error) return .failure(error) } }() @@ -175,7 +163,7 @@ class DatabaseSpec: QuickSpec { switch initialStateResult { case .success(let db): sourceDb = db case .failure(let error): - fail("Failed to prepare the initial state for '\(test.initialMigrationKey)'. Error: \(error)") + fail("Failed to prepare the initial state for '\(test.initialMigrationIdentifier)'. Error: \(error)") return } @@ -186,7 +174,7 @@ class DatabaseSpec: QuickSpec { // Peform the target migrations to ensure the migrations themselves worked correctly mockStorage.perform( - sortedMigrations: test.migrationsToTest, + migrations: test.migrationsToTest, async: false, onProgressUpdate: nil, onComplete: { result in finalResult = result } @@ -195,12 +183,68 @@ class DatabaseSpec: QuickSpec { switch finalResult { case .success: break case .failure(let error): - fail("Failed to migrate from '\(test.initialMigrationKey)' to '\(test.finalMigrationKey)'. Error: \(error)") + fail("Failed to migrate from '\(test.initialMigrationIdentifier)' to '\(test.finalMigrationIdentifier)'. Error: \(error)") case .none: - fail("Failed to migrate from '\(test.initialMigrationKey)' to '\(test.finalMigrationKey)'. Error: No result") + fail("Failed to migrate from '\(test.initialMigrationIdentifier)' to '\(test.finalMigrationIdentifier)'. Error: No result") } } } + + // MARK: -- migration order hasn't changed + it("migration order hasn't changed") { + expect(SNMessagingKit.migrations.map { $0.identifier }).to(equal([ + "utilitiesKit.initialSetup", + "utilitiesKit.SetupStandardJobs", + "utilitiesKit.YDBToGRDBMigration", + "snodeKit.initialSetup", + "snodeKit.SetupStandardJobs", + "messagingKit.initialSetup", + "messagingKit.SetupStandardJobs", + "snodeKit.YDBToGRDBMigration", + "messagingKit.YDBToGRDBMigration", + "snodeKit.FlagMessageHashAsDeletedOrInvalid", + "messagingKit.RemoveLegacyYDB", + "utilitiesKit.AddJobPriority", + "messagingKit.FixDeletedMessageReadState", + "messagingKit.FixHiddenModAdminSupport", + "messagingKit.HomeQueryOptimisationIndexes", + "uiKit.ThemePreferences", + "messagingKit.EmojiReacts", + "messagingKit.OpenGroupPermission", + "messagingKit.AddThreadIdToFTS", + "utilitiesKit.AddJobUniqueHash", + "snodeKit.AddSnodeReveivedMessageInfoPrimaryKey", + "snodeKit.DropSnodeCache", + "snodeKit.SplitSnodeReceivedMessageInfo", + "snodeKit.ResetUserConfigLastHashes", + "messagingKit.AddPendingReadReceipts", + "messagingKit.AddFTSIfNeeded", + "messagingKit.SessionUtilChanges", + "messagingKit.GenerateInitialUserConfigDumps", + "messagingKit.BlockCommunityMessageRequests", + "messagingKit.MakeBrokenProfileTimestampsNullable", + "messagingKit.RebuildFTSIfNeeded_2_4_5", + "messagingKit.DisappearingMessagesWithTypes", + "messagingKit.ScheduleAppUpdateCheckJob", + "messagingKit.AddMissingWhisperFlag", + "messagingKit.ReworkRecipientState", + "messagingKit.GroupsRebuildChanges", + "messagingKit.GroupsExpiredFlag", + "messagingKit.FixBustedInteractionVariant", + "messagingKit.DropLegacyClosedGroupKeyPairTable", + "messagingKit.MessageDeduplicationTable", + "utilitiesKit.RenameTableSettingToKeyValueStore", + "messagingKit.MoveSettingsToLibSession", + "messagingKit.RenameAttachments", + "messagingKit.AddProMessageFlag" + ])) + } + + // MARK: -- there are no duplicate migration names + it("there are no duplicate migration names") { + expect(Set(SNMessagingKit.migrations.map { $0.identifier }).sorted()) + .to(equal(SNMessagingKit.migrations.map { $0.identifier }.sorted())) + } } } } @@ -236,15 +280,15 @@ private struct TableColumn: Hashable { private class MigrationTest { static var explicitValues: [TableColumn: (any DatabaseValueConvertible)] = [:] - let initialMigrations: [Storage.KeyedMigration] - let migrationsToTest: [Storage.KeyedMigration] + let initialMigrations: [Migration.Type] + let migrationsToTest: [Migration.Type] - var initialMigrationKey: String { return (initialMigrations.last?.key ?? "an empty database") } - var finalMigrationKey: String { return (migrationsToTest.last?.key ?? "invalid") } + var initialMigrationIdentifier: String { return (initialMigrations.last?.identifier ?? "an empty database") } + var finalMigrationIdentifier: String { return (migrationsToTest.last?.identifier ?? "invalid") } private init( - initialMigrations: [Storage.KeyedMigration], - migrationsToTest: [Storage.KeyedMigration] + initialMigrations: [Migration.Type], + migrationsToTest: [Migration.Type] ) { self.initialMigrations = initialMigrations self.migrationsToTest = migrationsToTest @@ -252,7 +296,7 @@ private class MigrationTest { // MARK: - Test Data - static func extractTests(_ allMigrations: [Storage.KeyedMigration]) -> [MigrationTest] { + static func extractTests(_ allMigrations: [Migration.Type]) -> [MigrationTest] { return (0..<(allMigrations.count - 1)) .flatMap { index -> [MigrationTest] in ((index + 1).. MigrationTest in @@ -264,10 +308,10 @@ private class MigrationTest { } } - static func extractDatabaseTypes(_ allMigrations: [Storage.KeyedMigration]) -> [(TableRecord & FetchableRecord).Type] { + static func extractDatabaseTypes(_ allMigrations: [Migration.Type]) -> [(TableRecord & FetchableRecord).Type] { return Array(allMigrations .reduce(into: [:]) { result, next in - next.migration.createdTables.forEach { table in + next.createdTables.forEach { table in result[ObjectIdentifier(table).hashValue] = table } } @@ -392,69 +436,3 @@ private class MigrationTest { } } } - -enum TestAllMigrationRequirementsReversedMigratableTarget: MigratableTarget { // Just to make the external API nice - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: .session, - migrations: [ - [ - TestRequiresAllMigrationRequirementsReversedMigration.self - ] - ] - ) - } -} - -enum TestRequiresLibSessionStateMigratableTarget: MigratableTarget { // Just to make the external API nice - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: .session, - migrations: [ - [ - TestRequiresLibSessionStateMigration.self - ] - ] - ) - } -} - -enum TestRequiresSessionIdCachedMigratableTarget: MigratableTarget { // Just to make the external API nice - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: .session, - migrations: [ - [ - TestRequiresSessionIdCachedMigration.self - ] - ] - ) - } -} - -enum TestRequiresAllMigrationRequirementsReversedMigration: Migration { - static let target: TargetMigrations.Identifier = .session - static let identifier: String = "test" // stringlint:ignore - static let minExpectedRunDuration: TimeInterval = 0.1 - static let createdTables: [(TableRecord & FetchableRecord).Type] = [] - - static func migrate(_ db: ObservingDatabase, using dependencies: Dependencies) throws {} -} - -enum TestRequiresLibSessionStateMigration: Migration { - static let target: TargetMigrations.Identifier = .session - static let identifier: String = "test" // stringlint:ignore - static let minExpectedRunDuration: TimeInterval = 0.1 - static let createdTables: [(TableRecord & FetchableRecord).Type] = [] - - static func migrate(_ db: ObservingDatabase, using dependencies: Dependencies) throws {} -} - -enum TestRequiresSessionIdCachedMigration: Migration { - static let target: TargetMigrations.Identifier = .session - static let identifier: String = "test" // stringlint:ignore - static let minExpectedRunDuration: TimeInterval = 0.1 - static let createdTables: [(TableRecord & FetchableRecord).Type] = [] - - static func migrate(_ db: ObservingDatabase, using dependencies: Dependencies) throws {} -} diff --git a/SessionTests/Onboarding/OnboardingSpec.swift b/SessionTests/Onboarding/OnboardingSpec.swift index e351b5a8b5..f39b6b65a3 100644 --- a/SessionTests/Onboarding/OnboardingSpec.swift +++ b/SessionTests/Onboarding/OnboardingSpec.swift @@ -24,12 +24,7 @@ class OnboardingSpec: AsyncSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNNetworkingKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ], + migrations: SNMessagingKit.migrations, using: dependencies ) @TestState(singleton: .crypto, in: dependencies) var mockCrypto: MockCrypto! = MockCrypto( diff --git a/SessionTests/Settings/NotificationContentViewModelSpec.swift b/SessionTests/Settings/NotificationContentViewModelSpec.swift index 65c099d860..2a55c6ffae 100644 --- a/SessionTests/Settings/NotificationContentViewModelSpec.swift +++ b/SessionTests/Settings/NotificationContentViewModelSpec.swift @@ -21,12 +21,7 @@ class NotificationContentViewModelSpec: AsyncSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNNetworkingKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ], + migrations: SNMessagingKit.migrations, using: dependencies ) @TestState var secretKey: [UInt8]! = Array(Data(hex: TestConstants.edSecretKey)) diff --git a/SessionUtilitiesKit/Configuration.swift b/SessionUtilitiesKit/Configuration.swift index ce7ef571ee..c9a0e7503f 100644 --- a/SessionUtilitiesKit/Configuration.swift +++ b/SessionUtilitiesKit/Configuration.swift @@ -4,41 +4,12 @@ import Foundation import UIKit.UIFont import GRDB -public enum SNUtilitiesKit: MigratableTarget { // Just to make the external API nice +public enum SNUtilitiesKit { public static var maxFileSize: UInt = 0 public static var isRunningTests: Bool { ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil // stringlint:ignore } - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: .utilitiesKit, - migrations: [ - [ - // Intentionally including the '_003_YDBToGRDBMigration' in the first migration - // set to ensure the 'Identity' data is migrated before any other migrations are - // run (some need access to the users publicKey) - _001_InitialSetupMigration.self, - _002_SetupStandardJobs.self, - _003_YDBToGRDBMigration.self - ], // Initial DB Creation - [], // YDB to GRDB Migration - [], // Legacy DB removal - [ - _004_AddJobPriority.self - ], // Add job priorities - [], // Fix thread FTS - [ - _005_AddJobUniqueHash.self - ], - [ - _006_RenameTableSettingToKeyValueStore.self - ], // Renamed `Setting` to `KeyValueStore` - [] - ] - ) - } - public static func configure( networkMaxFileSize: UInt, using dependencies: Dependencies diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index b162e27c99..e9b4cfbc4f 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -246,8 +246,6 @@ open class Storage { // MARK: - Migrations - public typealias KeyedMigration = (key: String, identifier: TargetMigrations.Identifier, migration: Migration.Type) - public static func appliedMigrationIdentifiers(_ db: ObservingDatabase) -> Set { let migrator: DatabaseMigrator = DatabaseMigrator() @@ -255,47 +253,11 @@ open class Storage { .defaulting(to: []) } - public static func sortedMigrationInfo(migrationTargets: [MigratableTarget.Type]) -> [KeyedMigration] { - typealias MigrationInfo = (identifier: TargetMigrations.Identifier, migrations: TargetMigrations.MigrationSet) - - return migrationTargets - .map { target -> TargetMigrations in target.migrations() } - .sorted() - .reduce(into: [[MigrationInfo]]()) { result, next in - next.migrations.enumerated().forEach { index, migrationSet in - if result.count <= index { - result.append([]) - } - - result[index] = (result[index] + [(next.identifier, migrationSet)]) - } - } - .reduce(into: []) { result, next in - next.forEach { identifier, migrations in - result.append(contentsOf: migrations.map { (identifier.key(with: $0), identifier, $0) }) - } - } - } - public func perform( - migrationTargets: [MigratableTarget.Type], + migrations: [Migration.Type], async: Bool = true, onProgressUpdate: ((CGFloat, TimeInterval) -> ())?, onComplete: @escaping (Result) -> () - ) { - perform( - sortedMigrations: Storage.sortedMigrationInfo(migrationTargets: migrationTargets), - async: async, - onProgressUpdate: onProgressUpdate, - onComplete: onComplete - ) - } - - internal func perform( - sortedMigrations: [KeyedMigration], - async: Bool, - onProgressUpdate: ((CGFloat, TimeInterval) -> ())?, - onComplete: @escaping (Result) -> () ) { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { let error: Error = (startupError ?? StorageError.startupFailed) @@ -306,36 +268,33 @@ open class Storage { // Setup and run any required migrations var migrator: DatabaseMigrator = DatabaseMigrator() - sortedMigrations.forEach { _, identifier, migration in - migrator.registerMigration( - self, - targetIdentifier: identifier, - migration: migration, - using: dependencies - ) + migrations.forEach { migration in + migrator.registerMigration(migration.identifier) { [dependencies] db in + let migration = migration.loggedMigrate(using: dependencies) + try migration(ObservingDatabase.create(db, using: dependencies)) + } } // Determine which migrations need to be performed and gather the relevant settings needed to // inform the app of progress/states let completedMigrations: [String] = (try? dbWriter.read { db in try migrator.completedMigrations(db) }) .defaulting(to: []) - let unperformedMigrations: [KeyedMigration] = sortedMigrations + let unperformedMigrations: [Migration.Type] = migrations .reduce(into: []) { result, next in - guard !completedMigrations.contains(next.key) else { return } + guard !completedMigrations.contains(next.identifier) else { return } result.append(next) } let migrationToDurationMap: [String: TimeInterval] = unperformedMigrations .reduce(into: [:]) { result, next in - result[next.key] = next.migration.minExpectedRunDuration + result[next.identifier] = next.minExpectedRunDuration } - let unperformedMigrationDurations: [TimeInterval] = unperformedMigrations - .map { _, _, migration in migration.minExpectedRunDuration } + let unperformedMigrationDurations: [TimeInterval] = unperformedMigrations.map { $0.minExpectedRunDuration } let totalMinExpectedDuration: TimeInterval = migrationToDurationMap.values.reduce(0, +) // Store the logic to handle migration progress and completion let progressUpdater: (String, CGFloat) -> Void = { (targetKey: String, progress: CGFloat) in - guard let migrationIndex: Int = unperformedMigrations.firstIndex(where: { key, _, _ in key == targetKey }) else { + guard let migrationIndex: Int = unperformedMigrations.firstIndex(where: { $0.identifier == targetKey }) else { return } @@ -352,8 +311,8 @@ open class Storage { let migrationCompleted: (Result) -> () = { [weak self, migrator, dbWriter, dependencies] result in // Make sure to transition the progress updater to 100% for the final migration (just // in case the migration itself didn't update to 100% itself) - if let lastMigrationKey: String = unperformedMigrations.last?.key { - MigrationExecution.current?.progressUpdater(lastMigrationKey, 1) + if let lastMigrationIdentifier: String = unperformedMigrations.last?.identifier { + MigrationExecution.current?.progressUpdater(lastMigrationIdentifier, 1) } self?.hasCompletedMigrations = true @@ -401,8 +360,8 @@ open class Storage { let migrationContext: MigrationExecution.Context = MigrationExecution.Context(progressUpdater: progressUpdater) // If we have an unperformed migration then trigger the progress updater immediately - if let firstMigrationKey: String = unperformedMigrations.first?.key { - migrationContext.progressUpdater(firstMigrationKey, 0) + if let firstMigrationIdentifier: String = unperformedMigrations.first?.identifier { + migrationContext.progressUpdater(firstMigrationIdentifier, 0) } MigrationExecution.$current.withValue(migrationContext) { diff --git a/SessionUtilitiesKit/Database/Types/Migration.swift b/SessionUtilitiesKit/Database/Types/Migration.swift index 36a9026a8f..4609edfbdd 100644 --- a/SessionUtilitiesKit/Database/Types/Migration.swift +++ b/SessionUtilitiesKit/Database/Types/Migration.swift @@ -12,7 +12,6 @@ public extension Log.Category { // MARK: - Migration public protocol Migration { - static var target: TargetMigrations.Identifier { get } static var identifier: String { get } static var minExpectedRunDuration: TimeInterval { get } static var createdTables: [(TableRecord & FetchableRecord).Type] { get } @@ -21,17 +20,12 @@ public protocol Migration { } public extension Migration { - static func loggedMigrate( - _ storage: Storage?, - targetIdentifier: TargetMigrations.Identifier, - using dependencies: Dependencies - ) -> ((_ db: ObservingDatabase) throws -> ()) { + static func loggedMigrate(using dependencies: Dependencies) -> ((_ db: ObservingDatabase) throws -> ()) { return { (db: ObservingDatabase) in - Log.info(.migration, "Starting \(targetIdentifier.key(with: self))") + Log.info(.migration, "Starting \(identifier)") /// Store the `currentlyRunningMigration` in case it's useful MigrationExecution.current?.currentlyRunningMigration = MigrationExecution.CurrentlyRunningMigration( - identifier: targetIdentifier, migration: self ) defer { MigrationExecution.current?.currentlyRunningMigration = nil } @@ -44,7 +38,7 @@ public extension Migration { MigrationExecution.current?.observedEvents.append(contentsOf: db.events) MigrationExecution.current?.postCommitActions.merge(db.postCommitActions) { old, _ in old } - Log.info(.migration, "Completed \(targetIdentifier.key(with: self))") + Log.info(.migration, "Completed \(identifier)") } } } @@ -53,10 +47,9 @@ public extension Migration { public enum MigrationExecution { public struct CurrentlyRunningMigration: ThreadSafeType { - public let identifier: TargetMigrations.Identifier public let migration: Migration.Type - public var key: String { identifier.key(with: migration) } + public var key: String { migration.identifier } } public final class Context { @@ -83,6 +76,7 @@ public enum MigrationExecution { @TaskLocal public static var current: Context? + // stringlint:ignore_contents public static func updateProgress(_ progress: CGFloat) { // In test builds ignore any migration progress updates (we run in a custom database writer anyway) guard !SNUtilitiesKit.isRunningTests else { return } diff --git a/SessionUtilitiesKit/Database/Types/TargetMigrations.swift b/SessionUtilitiesKit/Database/Types/TargetMigrations.swift deleted file mode 100644 index 860647c618..0000000000 --- a/SessionUtilitiesKit/Database/Types/TargetMigrations.swift +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import GRDB - -public protocol MigratableTarget { - static func migrations() -> TargetMigrations -} - -public struct TargetMigrations: Comparable { - /// This identifier is used to determine the order each set of migrations should run in. - /// - /// All migrations within a specific set will run first, followed by all migrations for the same set index in - /// the next `Identifier` before moving on to the next `MigrationSet`. So given the migrations: - /// - /// `{a: [1], [2, 3]}, {b: [4, 5], [6]}` - /// - /// the migrations will run in the following order: - /// - /// `a1, b4, b5, a2, a3, b6` - public enum Identifier: String, CaseIterable, Comparable { - // WARNING: The string version of these cases are used as migration identifiers so - // changing them will result in the migrations running again - case session - case utilitiesKit - case networkingKit = "snodeKit" - case messagingKit - case _deprecatedUIKit = "uiKit" - case test - - public static func < (lhs: Self, rhs: Self) -> Bool { - let lhsIndex: Int = (Identifier.allCases.firstIndex(of: lhs) ?? Identifier.allCases.count) - let rhsIndex: Int = (Identifier.allCases.firstIndex(of: rhs) ?? Identifier.allCases.count) - - return (lhsIndex < rhsIndex) - } - - public func key(with migration: Migration.Type) -> String { - return "\(self.rawValue).\(migration.identifier)" - } - } - - public typealias MigrationSet = [Migration.Type] - - let identifier: Identifier - let migrations: [MigrationSet] - - // MARK: - Initialization - - public init( - identifier: Identifier, - migrations: [MigrationSet] - ) { - guard !migrations.contains(where: { migration in migration.contains(where: { $0.target != identifier }) }) else { - preconditionFailure("Attempted to register a migration with the wrong target") - } - - self.identifier = identifier - self.migrations = migrations - } - - // MARK: - Equatable - - public static func == (lhs: TargetMigrations, rhs: TargetMigrations) -> Bool { - return ( - lhs.identifier == rhs.identifier && - lhs.migrations.count == rhs.migrations.count - ) - } - - // MARK: - Comparable - - public static func < (lhs: Self, rhs: Self) -> Bool { - return (lhs.identifier < rhs.identifier) - } -} diff --git a/SessionUtilitiesKit/Database/Utilities/DatabaseMigrator+Utilities.swift b/SessionUtilitiesKit/Database/Utilities/DatabaseMigrator+Utilities.swift deleted file mode 100644 index 748175ca8d..0000000000 --- a/SessionUtilitiesKit/Database/Utilities/DatabaseMigrator+Utilities.swift +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import GRDB - -public extension DatabaseMigrator { - mutating func registerMigration( - _ storage: Storage?, - targetIdentifier: TargetMigrations.Identifier, - migration: Migration.Type, - foreignKeyChecks: ForeignKeyChecks = .deferred, - using dependencies: Dependencies - ) { - self.registerMigration( - targetIdentifier.key(with: migration), - migrate: { db in - let migration = migration.loggedMigrate(storage, targetIdentifier: targetIdentifier, using: dependencies) - try migration(ObservingDatabase.create(db, using: dependencies)) - } - ) - } -} diff --git a/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift b/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift index a6c60c58b8..5101300fc7 100644 --- a/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift +++ b/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift @@ -15,9 +15,7 @@ class IdentitySpec: QuickSpec { @TestState var dependencies: TestDependencies! = TestDependencies() @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self - ], + migrations: [_001_SUK_InitialSetupMigration.self], using: dependencies ) diff --git a/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift b/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift index 82b6fe25e2..bfd9d50009 100644 --- a/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift +++ b/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift @@ -47,14 +47,12 @@ class JobRunnerSpec: QuickSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self + migrations: [ + _001_SUK_InitialSetupMigration.self, + _012_AddJobPriority.self, + _020_AddJobUniqueHash.self ], - using: dependencies, - initialData: { db in - // Migrations add jobs which we don't want so delete them - try Job.deleteAll(db) - } + using: dependencies ) @TestState(singleton: .jobRunner, in: dependencies) var jobRunner: JobRunnerType! = JobRunner( isTestingJobRunner: true, diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index f6e76580bf..1a3f2f7558 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -10,7 +10,6 @@ import SessionUtilitiesKit public enum AppSetup { public static func setupEnvironment( requestId: String? = nil, - additionalMigrationTargets: [MigratableTarget.Type] = [], appSpecificBlock: (() -> ())? = nil, migrationProgressChanged: ((CGFloat, TimeInterval) -> ())? = nil, migrationsCompletion: @escaping (Result) -> (), @@ -43,7 +42,6 @@ public enum AppSetup { runPostSetupMigrations( requestId: requestId, backgroundTask: backgroundTask, - additionalMigrationTargets: additionalMigrationTargets, migrationProgressChanged: migrationProgressChanged, migrationsCompletion: migrationsCompletion, using: dependencies @@ -57,7 +55,6 @@ public enum AppSetup { public static func runPostSetupMigrations( requestId: String? = nil, backgroundTask: SessionBackgroundTask? = nil, - additionalMigrationTargets: [MigratableTarget.Type] = [], migrationProgressChanged: ((CGFloat, TimeInterval) -> ())? = nil, migrationsCompletion: @escaping (Result) -> (), using dependencies: Dependencies @@ -65,12 +62,7 @@ public enum AppSetup { var backgroundTask: SessionBackgroundTask? = (backgroundTask ?? SessionBackgroundTask(label: #function, using: dependencies)) dependencies[singleton: .storage].perform( - migrationTargets: additionalMigrationTargets - .appending(contentsOf: [ - SNUtilitiesKit.self, - SNNetworkingKit.self, - SNMessagingKit.self - ]), + migrations: SNMessagingKit.migrations, onProgressUpdate: migrationProgressChanged, onComplete: { originalResult in // Now that the migrations are complete there are a few more states which need diff --git a/_SharedTestUtilities/SynchronousStorage.swift b/_SharedTestUtilities/SynchronousStorage.swift index fef7fd8aae..59510cf436 100644 --- a/_SharedTestUtilities/SynchronousStorage.swift +++ b/_SharedTestUtilities/SynchronousStorage.swift @@ -11,8 +11,7 @@ class SynchronousStorage: Storage, DependenciesSettable, InitialSetupable { public init( customWriter: DatabaseWriter? = nil, - migrationTargets: [MigratableTarget.Type]? = nil, - migrations: [Storage.KeyedMigration]? = nil, + migrations: [Migration.Type]? = nil, using dependencies: Dependencies, initialData: ((ObservingDatabase) throws -> ())? = nil ) { @@ -21,20 +20,9 @@ class SynchronousStorage: Storage, DependenciesSettable, InitialSetupable { super.init(customWriter: customWriter, using: dependencies) - // Process any migration targets first - if let migrationTargets: [MigratableTarget.Type] = migrationTargets { + if let migrations: [Migration.Type] = migrations { perform( - migrationTargets: migrationTargets, - async: false, - onProgressUpdate: nil, - onComplete: { _ in } - ) - } - - // Then process any provided migration info - if let migrations: [Storage.KeyedMigration] = migrations { - perform( - sortedMigrations: migrations, + migrations: migrations, async: false, onProgressUpdate: nil, onComplete: { _ in } From 0e1fb9a3f2666f843440dd13f20031e1475dedfb Mon Sep 17 00:00:00 2001 From: mikoldin Date: Wed, 27 Aug 2025 11:07:51 +0800 Subject: [PATCH 05/66] Fix issue on some device long delete message is not wrapping --- .../Message Cells/Content Views/DeletedMessageView.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift b/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift index 566ddceca1..c235b80e21 100644 --- a/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift +++ b/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift @@ -33,7 +33,7 @@ final class DeletedMessageView: UIView { let imageContainerView: UIView = UIView() imageContainerView.set(.width, to: DeletedMessageView.iconImageViewSize) imageContainerView.set(.height, to: DeletedMessageView.iconImageViewSize) - + let imageView = UIImageView(image: Lucide.image(icon: .trash2, size: DeletedMessageView.iconSize)?.withRenderingMode(.alwaysTemplate)) imageView.themeTintColor = textColor imageView.contentMode = .scaleAspectFit @@ -45,7 +45,6 @@ final class DeletedMessageView: UIView { // Body label let titleLabel = UILabel() titleLabel.setContentHuggingPriority(.required, for: .vertical) - titleLabel.preferredMaxLayoutWidth = maxWidth - 6 // `6` for the `stackView.layoutMargins` titleLabel.font = .systemFont(ofSize: Values.smallFontSize) titleLabel.text = { switch variant { @@ -69,6 +68,6 @@ final class DeletedMessageView: UIView { let calculatedSize: CGSize = stackView.systemLayoutSizeFitting(CGSize(width: maxWidth, height: 999)) stackView.pin(to: self, withInset: Values.smallSpacing) - stackView.set(.height, to: calculatedSize.height) + stackView.set(.height, greaterThanOrEqualTo: calculatedSize.height) } } From 06015d8cda38e648518ed926632e1d966e314001 Mon Sep 17 00:00:00 2001 From: mikoldin Date: Wed, 27 Aug 2025 14:12:58 +0800 Subject: [PATCH 06/66] Fix input field not hiding when showing link preview modal --- .../ConversationVC+Interaction.swift | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 00c27d060f..24ce7ec4f0 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -860,6 +860,9 @@ extension ConversationVC: } func showLinkPreviewSuggestionModal() { + // Hides accessory view while link preview confirmation is presented + hideInputAccessoryView() + let linkPreviewModal: ConfirmationModal = ConfirmationModal( info: ConfirmationModal.Info( title: "linkPreviewsEnable".localized(), @@ -870,12 +873,17 @@ extension ConversationVC: ), confirmTitle: "enable".localized(), confirmStyle: .danger, - cancelStyle: .alert_text - ) { [weak self, dependencies = viewModel.dependencies] _ in - dependencies.setAsync(.areLinkPreviewsEnabled, true) { - self?.snInputView.autoGenerateLinkPreview() + cancelStyle: .alert_text, + onConfirm: { [weak self, dependencies = viewModel.dependencies] _ in + dependencies.setAsync(.areLinkPreviewsEnabled, true) { + self?.snInputView.autoGenerateLinkPreview() + } + }, + afterClosed: { [weak self] in + // Bring back accessory view after confirmation action + self?.showInputAccessoryView() } - } + ) ) present(linkPreviewModal, animated: true, completion: nil) From b8023b67978d3b1b29ceb5e211a23cd4db9b3ad5 Mon Sep 17 00:00:00 2001 From: mikoldin Date: Tue, 2 Sep 2025 15:17:51 +0800 Subject: [PATCH 07/66] Update message request delete action --- Session/Utilities/UIContextualAction+Utilities.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Session/Utilities/UIContextualAction+Utilities.swift b/Session/Utilities/UIContextualAction+Utilities.swift index 5b5ce426a4..7e30326c7e 100644 --- a/Session/Utilities/UIContextualAction+Utilities.swift +++ b/Session/Utilities/UIContextualAction+Utilities.swift @@ -649,7 +649,7 @@ public extension UIContextualAction { guard !isMessageRequest else { switch threadViewModel.threadVariant { case .group: return ThemedAttributedString(string: "groupInviteDelete".localized()) - default: return ThemedAttributedString(string: "messageRequestsDelete".localized()) + default: return ThemedAttributedString(string: "messageRequestsContactDelete".localized()) } } @@ -692,7 +692,7 @@ public extension UIContextualAction { return .deleteGroupAndContent case (.group, _, _): return .leaveGroupAsync - case (.contact, _, _): return .deleteContactConversationAndMarkHidden + case (.contact, _, _): return .deleteContactConversationAndContact } }() From a953e20b46f74b26b153a460f4eca2a574383d1e Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 3 Sep 2025 14:35:33 +1000 Subject: [PATCH 08/66] Removed "Delete for Everyone" option when deleting pending messages --- .../MessageViewModel+DeletionActions.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift b/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift index 9cbad6bf0b..8f19598ecb 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift @@ -124,7 +124,7 @@ public extension MessageViewModel.DeletionBehaviours { enum SelectedMessageState { case outgoingOnly case containsIncoming - case containsDeletedOrControlMessages + case containsLocalOnlyMessages /// Control, pending or deleted messages } /// If it's a legacy group and they have been deprecated then the user shouldn't be able to delete messages @@ -134,8 +134,9 @@ public extension MessageViewModel.DeletionBehaviours { let state: SelectedMessageState = { guard !cellViewModels.contains(where: { $0.variant.isDeletedMessage }) && - !cellViewModels.contains(where: { $0.variant.isInfoMessage }) - else { return .containsDeletedOrControlMessages } + !cellViewModels.contains(where: { $0.variant.isInfoMessage }) && + !cellViewModels.contains(where: { $0.state == .sending }) + else { return .containsLocalOnlyMessages } return (cellViewModels.contains(where: { $0.variant == .standardIncoming }) ? .containsIncoming : @@ -171,8 +172,8 @@ public extension MessageViewModel.DeletionBehaviours { }() switch (state, isAdmin) { - /// User selects messages including a control message or “deleted” message - case (.containsDeletedOrControlMessages, _): + /// User selects messages including a control, pending or “deleted” message + case (.containsLocalOnlyMessages, _): return MessageViewModel.DeletionBehaviours( title: "deleteMessage" .putNumber(cellViewModels.count) From 3f3d8e84fb30247d1eb878d2dd8164cfea5086e6 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 3 Sep 2025 15:29:32 +1000 Subject: [PATCH 09/66] Added failed state as well --- .../Shared Models/MessageViewModel+DeletionActions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift b/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift index 8f19598ecb..d26763bbfa 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift @@ -135,7 +135,7 @@ public extension MessageViewModel.DeletionBehaviours { guard !cellViewModels.contains(where: { $0.variant.isDeletedMessage }) && !cellViewModels.contains(where: { $0.variant.isInfoMessage }) && - !cellViewModels.contains(where: { $0.state == .sending }) + !cellViewModels.contains(where: { $0.state == .sending || $0.state == .failed }) else { return .containsLocalOnlyMessages } return (cellViewModels.contains(where: { $0.variant == .standardIncoming }) ? From 6a5bb2273c77051f8999169d7885e408c65536f4 Mon Sep 17 00:00:00 2001 From: mikoldin Date: Wed, 3 Sep 2025 14:23:54 +0800 Subject: [PATCH 10/66] Fix overly large file placeholder preview --- .../MediaMessageView.swift | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift index 1e732e5f1b..4feb892afb 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift @@ -406,12 +406,7 @@ public class MediaMessageView: UIView { ) : stackView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor) ), - - imageView.widthAnchor.constraint( - equalTo: imageView.heightAnchor, - multiplier: clampedRatio - ), - + (maybeImageSize != nil ? imageView.widthAnchor.constraint(equalToConstant: imageSize) : imageView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor) @@ -426,11 +421,9 @@ public class MediaMessageView: UIView { equalTo: imageView.centerYAnchor, constant: ceil(imageSize * 0.15) ), - fileTypeImageView.widthAnchor.constraint( - equalTo: fileTypeImageView.heightAnchor, - multiplier: ((fileTypeImageView.image?.size.width ?? 1) / (fileTypeImageView.image?.size.height ?? 1)) - ), - fileTypeImageView.widthAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 0.5), + + fileTypeImageView.widthAnchor.constraint(equalToConstant: imageSize * 0.5), + fileTypeImageView.heightAnchor.constraint(equalToConstant: imageSize * 0.5), loadingView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor), loadingView.centerYAnchor.constraint(equalTo: imageView.centerYAnchor), @@ -438,6 +431,16 @@ public class MediaMessageView: UIView { loadingView.heightAnchor.constraint(equalToConstant: ceil(imageSize / 3)) ]) + if imageView.image?.size == nil { + // Handle `clampedRatio` ratio when image is from data + NSLayoutConstraint.activate([ + imageView.widthAnchor.constraint( + equalTo: imageView.heightAnchor, + multiplier: clampedRatio + ) + ]) + } + // No inset for the text for URLs but there is for all other layouts if !attachment.isUrl { NSLayoutConstraint.activate([ From 684a30bf7c07e1cca3e7738d011b68eed249bd5b Mon Sep 17 00:00:00 2001 From: mikoldin Date: Thu, 4 Sep 2025 10:33:54 +0800 Subject: [PATCH 11/66] Updated block contacts settings button design to be consistent --- .../Settings/ConversationSettingsViewModel.swift | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Session/Settings/ConversationSettingsViewModel.swift b/Session/Settings/ConversationSettingsViewModel.swift index 0419aa80fc..1bfaa1e129 100644 --- a/Session/Settings/ConversationSettingsViewModel.swift +++ b/Session/Settings/ConversationSettingsViewModel.swift @@ -39,16 +39,11 @@ class ConversationSettingsViewModel: SessionTableViewModel, NavigatableStateHold switch self { case .messageTrimming: return "conversationsMessageTrimming".localized() case .audioMessages: return "conversationsAudioMessages".localized() - case .blockedContacts: return nil + case .blockedContacts: return "conversationsBlockedContacts".localized() } } - var style: SessionTableSectionStyle { - switch self { - case .blockedContacts: return .padding - default: return .titleRoundedContent - } - } + var style: SessionTableSectionStyle { .titleRoundedContent } } // MARK: - Content @@ -192,10 +187,8 @@ class ConversationSettingsViewModel: SessionTableViewModel, NavigatableStateHold SessionCell.Info( id: .blockedContacts, title: "conversationsBlockedContacts".localized(), - styling: SessionCell.StyleInfo( - tintColor: .danger, - backgroundStyle: .noBackground - ), + subtitle: "blockedContactsManageDescription".localized(), + trailingAccessory: .icon(.chevronRight), onTap: { [weak viewModel, dependencies = viewModel.dependencies] in viewModel?.transitionToScreen( SessionTableViewController(viewModel: BlockedContactsViewModel(using: dependencies)) From 0f5f091d9fd3ac35b96813dc645d71115f42a336 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 4 Sep 2025 12:46:03 +1000 Subject: [PATCH 12/66] Fix primary colour reset, main actor specifications --- .../Content Views/MediaView.swift | 2 +- Session/Settings/AppearanceViewModel.swift | 2 ++ .../Utilities/ImageLoading+Convenience.swift | 14 ++++++++------ .../Components/SessionImageView.swift | 2 +- SessionUIKit/Style Guide/ThemeManager.swift | 19 ++++++++++--------- SessionUIKit/Types/ImageDataManager.swift | 11 +++++++---- 6 files changed, 29 insertions(+), 21 deletions(-) diff --git a/Session/Conversations/Message Cells/Content Views/MediaView.swift b/Session/Conversations/Message Cells/Content Views/MediaView.swift index 01a44703a9..09e9b1e7b0 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaView.swift @@ -198,7 +198,7 @@ public class MediaView: UIView { guard processedData == nil else { return } Log.error("[MediaView] Could not load thumbnail") - Task { @MainActor [weak self] in self?.configure(forError: .invalid) } + self?.configure(forError: .invalid) } } diff --git a/Session/Settings/AppearanceViewModel.swift b/Session/Settings/AppearanceViewModel.swift index 6f93c3f507..20c5762267 100644 --- a/Session/Settings/AppearanceViewModel.swift +++ b/Session/Settings/AppearanceViewModel.swift @@ -223,6 +223,8 @@ class AppearanceViewModel: SessionTableViewModel, NavigatableStateHolder, Observ ), onTap: { ThemeManager.updateThemeState( + theme: state.theme, /// Keep the current value + primaryColor: state.primaryColor, /// Keep the current value matchSystemNightModeSetting: !state.autoDarkModeEnabled ) } diff --git a/Session/Utilities/ImageLoading+Convenience.swift b/Session/Utilities/ImageLoading+Convenience.swift index 54a69d2524..1b1ef57fd3 100644 --- a/Session/Utilities/ImageLoading+Convenience.swift +++ b/Session/Utilities/ImageLoading+Convenience.swift @@ -69,10 +69,11 @@ public extension ImageDataManager.DataSource { // MARK: - ImageDataManagerType Convenience public extension ImageDataManagerType { + @MainActor func loadImage( attachment: Attachment, using dependencies: Dependencies, - onComplete: @escaping (ImageDataManager.ProcessedImageData?) -> Void = { _ in } + onComplete: @MainActor @escaping (ImageDataManager.ProcessedImageData?) -> Void = { _ in } ) { guard let source: ImageDataManager.DataSource = ImageDataManager.DataSource.from( attachment: attachment, @@ -82,11 +83,12 @@ public extension ImageDataManagerType { load(source, onComplete: onComplete) } + @MainActor func loadThumbnail( size: ImageDataManager.ThumbnailSize, attachment: Attachment, using dependencies: Dependencies, - onComplete: @escaping (ImageDataManager.ProcessedImageData?) -> Void = { _ in } + onComplete: @MainActor @escaping (ImageDataManager.ProcessedImageData?) -> Void = { _ in } ) { guard let source: ImageDataManager.DataSource = ImageDataManager.DataSource.thumbnailFrom( attachment: attachment, @@ -102,7 +104,7 @@ public extension ImageDataManagerType { public extension SessionImageView { @MainActor - func loadImage(from path: String, onComplete: ((ImageDataManager.ProcessedImageData?) -> Void)? = nil) { + func loadImage(from path: String, onComplete: (@MainActor (ImageDataManager.ProcessedImageData?) -> Void)? = nil) { loadImage(.url(URL(fileURLWithPath: path)), onComplete: onComplete) } @@ -110,7 +112,7 @@ public extension SessionImageView { func loadImage( attachment: Attachment, using dependencies: Dependencies, - onComplete: ((ImageDataManager.ProcessedImageData?) -> Void)? = nil + onComplete: (@MainActor (ImageDataManager.ProcessedImageData?) -> Void)? = nil ) { guard let source: ImageDataManager.DataSource = ImageDataManager.DataSource.from( attachment: attachment, @@ -128,7 +130,7 @@ public extension SessionImageView { size: ImageDataManager.ThumbnailSize, attachment: Attachment, using dependencies: Dependencies, - onComplete: ((ImageDataManager.ProcessedImageData?) -> Void)? = nil + onComplete: (@MainActor (ImageDataManager.ProcessedImageData?) -> Void)? = nil ) { guard let source: ImageDataManager.DataSource = ImageDataManager.DataSource.thumbnailFrom( attachment: attachment, @@ -143,7 +145,7 @@ public extension SessionImageView { } @MainActor - func loadPlaceholder(seed: String, text: String, size: CGFloat, onComplete: ((ImageDataManager.ProcessedImageData?) -> Void)? = nil) { + func loadPlaceholder(seed: String, text: String, size: CGFloat, onComplete: (@MainActor (ImageDataManager.ProcessedImageData?) -> Void)? = nil) { loadImage(.placeholderIcon(seed: seed, text: text, size: size), onComplete: onComplete) } } diff --git a/SessionUIKit/Components/SessionImageView.swift b/SessionUIKit/Components/SessionImageView.swift index 0eec4aab2b..a768725213 100644 --- a/SessionUIKit/Components/SessionImageView.swift +++ b/SessionUIKit/Components/SessionImageView.swift @@ -138,7 +138,7 @@ public class SessionImageView: UIImageView { } @MainActor - public func loadImage(_ source: ImageDataManager.DataSource, onComplete: ((ImageDataManager.ProcessedImageData?) -> Void)? = nil) { + public func loadImage(_ source: ImageDataManager.DataSource, onComplete: (@MainActor (ImageDataManager.ProcessedImageData?) -> Void)? = nil) { /// If we are trying to load the image that is already displayed then no need to do anything if currentLoadIdentifier == source.identifier && (self.image == nil || isAnimating()) { /// If it was an animation that got paused then resume it diff --git a/SessionUIKit/Style Guide/ThemeManager.swift b/SessionUIKit/Style Guide/ThemeManager.swift index 1ceae3a4dc..7065d19d0f 100644 --- a/SessionUIKit/Style Guide/ThemeManager.swift +++ b/SessionUIKit/Style Guide/ThemeManager.swift @@ -53,10 +53,6 @@ public enum ThemeManager { _primaryColor = targetPrimaryColor _hasLoadedTheme = true - if !hasSetInitialSystemTrait || themeChanged { - updateAllUI() - } - if matchSystemChanged { _matchSystemNightModeSetting = targetMatchSystemNightModeSetting @@ -65,9 +61,14 @@ public enum ThemeManager { SNUIKit.mainWindow?.overrideUserInterfaceStyle = .unspecified } - // If the theme was changed then trigger the callback for the theme settings change (so it gets persisted) + // If the theme was changed then trigger a UI update and the callback for the theme settings + // change (so it gets persisted) guard themeChanged || matchSystemChanged else { return } + if !hasSetInitialSystemTrait || themeChanged { + updateAllUI() + } + SNUIKit.themeSettingsChanged(targetTheme, targetPrimaryColor, targetMatchSystemNightModeSetting) } @@ -82,10 +83,10 @@ public enum ThemeManager { // Swap to the appropriate light/dark mode switch (currentUserInterfaceStyle, ThemeManager.currentTheme) { - case (.light, .classicDark): updateThemeState(theme: .classicLight) - case (.light, .oceanDark): updateThemeState(theme: .oceanLight) - case (.dark, .classicLight): updateThemeState(theme: .classicDark) - case (.dark, .oceanLight): updateThemeState(theme: .oceanDark) + case (.light, .classicDark): updateThemeState(theme: .classicLight, primaryColor: _primaryColor) + case (.light, .oceanDark): updateThemeState(theme: .oceanLight, primaryColor: _primaryColor) + case (.dark, .classicLight): updateThemeState(theme: .classicDark, primaryColor: _primaryColor) + case (.dark, .oceanLight): updateThemeState(theme: .oceanDark, primaryColor: _primaryColor) default: break } } diff --git a/SessionUIKit/Types/ImageDataManager.swift b/SessionUIKit/Types/ImageDataManager.swift index 4cfaa8083f..9e0dd29674 100644 --- a/SessionUIKit/Types/ImageDataManager.swift +++ b/SessionUIKit/Types/ImageDataManager.swift @@ -59,9 +59,10 @@ public actor ImageDataManager: ImageDataManagerType { return processedData } - nonisolated public func load( + @MainActor + public func load( _ source: ImageDataManager.DataSource, - onComplete: @escaping (ImageDataManager.ProcessedImageData?) -> Void + onComplete: @MainActor @escaping (ImageDataManager.ProcessedImageData?) -> Void ) { Task { [weak self] in let result: ImageDataManager.ProcessedImageData? = await self?.load(source) @@ -863,9 +864,11 @@ public extension ImageDataManager { public protocol ImageDataManagerType { @discardableResult func load(_ source: ImageDataManager.DataSource) async -> ImageDataManager.ProcessedImageData? - nonisolated func load( + + @MainActor + func load( _ source: ImageDataManager.DataSource, - onComplete: @escaping (ImageDataManager.ProcessedImageData?) -> Void + onComplete: @MainActor @escaping (ImageDataManager.ProcessedImageData?) -> Void ) func cachedImage(identifier: String) async -> ImageDataManager.ProcessedImageData? From 5e57af93573c91ea49233c0c719ff54345eb3ace Mon Sep 17 00:00:00 2001 From: mikoldin Date: Thu, 4 Sep 2025 11:24:54 +0800 Subject: [PATCH 13/66] Fix trailing accessory views large padding --- Session/Settings/AppearanceViewModel.swift | 6 +++++- .../Settings/ConversationSettingsViewModel.swift | 6 +++++- Session/Settings/HelpViewModel.swift | 16 ++++++++++++---- Session/Shared/Types/SessionCell+Accessory.swift | 14 ++++++++++++-- .../Shared/Views/SessionCell+AccessoryView.swift | 7 +++++++ 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/Session/Settings/AppearanceViewModel.swift b/Session/Settings/AppearanceViewModel.swift index 6f93c3f507..c88b392681 100644 --- a/Session/Settings/AppearanceViewModel.swift +++ b/Session/Settings/AppearanceViewModel.swift @@ -238,7 +238,11 @@ class AppearanceViewModel: SessionTableViewModel, NavigatableStateHolder, Observ "appIconSelect".localized(), font: .titleRegular ), - trailingAccessory: .icon(.chevronRight), + trailingAccessory: .icon( + .chevronRight, + shouldFill: true , + shouldFollowIconSize: true + ), onTap: { [weak viewModel, dependencies = viewModel.dependencies] in viewModel?.transitionToScreen( SessionTableViewController( diff --git a/Session/Settings/ConversationSettingsViewModel.swift b/Session/Settings/ConversationSettingsViewModel.swift index 1bfaa1e129..68214b2380 100644 --- a/Session/Settings/ConversationSettingsViewModel.swift +++ b/Session/Settings/ConversationSettingsViewModel.swift @@ -188,7 +188,11 @@ class ConversationSettingsViewModel: SessionTableViewModel, NavigatableStateHold id: .blockedContacts, title: "conversationsBlockedContacts".localized(), subtitle: "blockedContactsManageDescription".localized(), - trailingAccessory: .icon(.chevronRight), + trailingAccessory: .icon( + .chevronRight, + shouldFill: true , + shouldFollowIconSize: true + ), onTap: { [weak viewModel, dependencies = viewModel.dependencies] in viewModel?.transitionToScreen( SessionTableViewController(viewModel: BlockedContactsViewModel(using: dependencies)) diff --git a/Session/Settings/HelpViewModel.swift b/Session/Settings/HelpViewModel.swift index ba9d899f8c..26e38683da 100644 --- a/Session/Settings/HelpViewModel.swift +++ b/Session/Settings/HelpViewModel.swift @@ -76,7 +76,9 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa trailingAccessory: .icon( UIImage(systemName: "arrow.up.forward.app")? .withRenderingMode(.alwaysTemplate), - size: .small + size: .small, + shouldFill: true, + shouldFollowIconSize: true ), onTap: { guard let url: URL = URL(string: "https://getsession.org/translate") else { @@ -97,7 +99,9 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa trailingAccessory: .icon( UIImage(systemName: "arrow.up.forward.app")? .withRenderingMode(.alwaysTemplate), - size: .small + size: .small, + shouldFill: true, + shouldFollowIconSize: true ), onTap: { guard let url: URL = URL(string: "https://getsession.org/survey") else { @@ -118,7 +122,9 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa trailingAccessory: .icon( UIImage(systemName: "arrow.up.forward.app")? .withRenderingMode(.alwaysTemplate), - size: .small + size: .small, + shouldFill: true, + shouldFollowIconSize: true ), onTap: { guard let url: URL = URL(string: "https://getsession.org/faq") else { @@ -139,7 +145,9 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa trailingAccessory: .icon( UIImage(systemName: "arrow.up.forward.app")? .withRenderingMode(.alwaysTemplate), - size: .small + size: .small, + shouldFill: true, + shouldFollowIconSize: true ), onTap: { guard let url: URL = URL(string: "https://sessionapp.zendesk.com/hc/en-us") else { diff --git a/Session/Shared/Types/SessionCell+Accessory.swift b/Session/Shared/Types/SessionCell+Accessory.swift index ed8d6b0a97..4ebb6e17e3 100644 --- a/Session/Shared/Types/SessionCell+Accessory.swift +++ b/Session/Shared/Types/SessionCell+Accessory.swift @@ -40,7 +40,8 @@ public extension SessionCell.Accessory { size: IconSize = .medium, customTint: ThemeValue? = nil, shouldFill: Bool = false, - accessibility: Accessibility? = nil + shouldFollowIconSize: Bool = false, + accessibility: Accessibility? = nil, ) -> SessionCell.Accessory { return SessionCell.AccessoryConfig.Icon( icon: icon, @@ -48,6 +49,7 @@ public extension SessionCell.Accessory { iconSize: size, customTint: customTint, shouldFill: shouldFill, + shouldFollowIconSize: shouldFollowIconSize, accessibility: accessibility ) } @@ -57,7 +59,8 @@ public extension SessionCell.Accessory { size: IconSize = .medium, customTint: ThemeValue? = nil, shouldFill: Bool = false, - accessibility: Accessibility? = nil + shouldFollowIconSize: Bool = false, + accessibility: Accessibility? = nil, ) -> SessionCell.Accessory { return SessionCell.AccessoryConfig.Icon( icon: nil, @@ -65,6 +68,7 @@ public extension SessionCell.Accessory { iconSize: size, customTint: customTint, shouldFill: shouldFill, + shouldFollowIconSize: shouldFollowIconSize, accessibility: accessibility ) } @@ -226,6 +230,7 @@ public extension SessionCell.AccessoryConfig { public let iconSize: IconSize public let customTint: ThemeValue? public let shouldFill: Bool + public let shouldFollowIconSize: Bool fileprivate init( icon: Lucide.Icon?, @@ -233,6 +238,7 @@ public extension SessionCell.AccessoryConfig { iconSize: IconSize, customTint: ThemeValue?, shouldFill: Bool, + shouldFollowIconSize: Bool = false, accessibility: Accessibility? ) { self.icon = icon @@ -240,6 +246,7 @@ public extension SessionCell.AccessoryConfig { self.iconSize = iconSize self.customTint = customTint self.shouldFill = shouldFill + self.shouldFollowIconSize = shouldFollowIconSize super.init(accessibility: accessibility) } @@ -252,6 +259,7 @@ public extension SessionCell.AccessoryConfig { iconSize.hash(into: &hasher) customTint.hash(into: &hasher) shouldFill.hash(into: &hasher) + shouldFollowIconSize.hash(into: &hasher) accessibility.hash(into: &hasher) } @@ -264,7 +272,9 @@ public extension SessionCell.AccessoryConfig { iconSize == rhs.iconSize && customTint == rhs.customTint && shouldFill == rhs.shouldFill && + shouldFollowIconSize == rhs.shouldFollowIconSize && accessibility == rhs.accessibility + ) } } diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index 8d7c9ee551..a93a8c41f3 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -307,6 +307,13 @@ extension SessionCell { imageView.themeTintColor = (accessory.customTint ?? tintColor) imageView.contentMode = (accessory.shouldFill ? .scaleAspectFill : .scaleAspectFit) + // Use icon size when displaying accessory view. + // 50 width causes large padding not aligning accessory to right + if accessory.shouldFollowIconSize { + fixedWidthConstraint.constant = accessory.iconSize.size + fixedWidthConstraint.isActive = true + } + switch (accessory.icon, accessory.image) { case (.some(let icon), _): imageView.image = Lucide From 9a6904f1062cfcea58d2b97eccdfdb48caacb8d0 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 4 Sep 2025 13:57:44 +1000 Subject: [PATCH 14/66] Tweaks to try to improve CI output --- .drone.jsonnet | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index d24f88633f..44833d72a4 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -77,10 +77,23 @@ local clean_up_old_test_sims_on_commit_trigger = { { name: 'Build and Run Tests', commands: [ + 'echo "--- Running Build and Tests ---"', 'echo "Explicitly running unit tests on \'App_Store_Release\' configuration to ensure optimisation behaviour is consistent"', 'echo "If tests fail inconsistently from local builds this is likely the difference"', 'echo ""', - 'NSUnbufferedIO=YES xcodebuild test -project Session.xcodeproj -scheme Session -derivedDataPath ./build/derivedData -resultBundlePath ./build/artifacts/testResults.xcresult -parallelizeTargets -configuration "App_Store_Release" -destination "platform=iOS Simulator,id=$(<./build/artifacts/sim_uuid)" -parallel-testing-enabled NO -test-timeouts-enabled YES -maximum-test-execution-time-allowance 10 -collect-test-diagnostics never ENABLE_TESTABILITY=YES 2>&1 | xcbeautify --is-ci', + 'xcodebuild_output=$(mktemp)', + 'xcodebuild_exit_code=0', + 'NSUnbufferedIO=YES xcodebuild test -project Session.xcodeproj -scheme Session -derivedDataPath ./build/derivedData -resultBundlePath ./build/artifacts/testResults.xcresult -parallelizeTargets -configuration "App_Store_Release" -destination "platform=iOS Simulator,id=$(<./build/artifacts/sim_uuid)" -parallel-testing-enabled NO -test-timeouts-enabled YES -maximum-test-execution-time-allowance 10 -collect-test-diagnostics never ENABLE_TESTABILITY=YES 2>&1 | tee "$xcodebuild_output" | xcbeautify --is-ci || xcodebuild_exit_code=${PIPESTATUS[0]}', + 'echo ""', + 'echo "--- xcodebuild finished with exit code: $xcodebuild_exit_code ---"', + 'echo ""', + 'if [ $xcodebuild_exit_code -ne 0 ]; then', + ' echo "🔴 Build failed. See log above for compile errors."', + ' exit $xcodebuild_exit_code', + 'fi', + 'echo ""', + 'echo "✅ Build Succeeded. Verifying test results..."', + 'xcresultparser --output-format cli --exit-with-error-on-failure ./build/artifacts/testResults.xcresult', ], depends_on: [ 'Reset SPM Cache if Needed', @@ -99,31 +112,26 @@ local clean_up_old_test_sims_on_commit_trigger = { status: ['success', 'failure'], }, }, - { - name: 'Check for Build/Test Failures', - commands: [ - 'echo "Checking for build errors or test failures in xcresult bundle..."', - 'xcresultparser --output-format cli --failed-tests-only ./build/artifacts/testResults.xcresult' - ], - depends_on: ['Build and Run Tests'] - }, { name: 'Log Failed Test Summary', commands: [ 'echo "--- FAILED TESTS ---"', - 'xcresultparser --output-format cli --failed-tests-only ./build/artifacts/testResults.xcresult' + 'xcresultparser --output-format cli --failed-tests-only ./build/artifacts/testResults.xcresult', ], - depends_on: ['Check for Build/Test Failures'], + depends_on: ['Build and Run Tests'], when: { status: ['failure'], // Only run this on failure }, }, { - name: 'Convert xcresult to xml', + name: 'Generate Code Coverage Report', commands: [ 'xcresultparser --output-format cobertura ./build/artifacts/testResults.xcresult > ./build/artifacts/coverage.xml', ], depends_on: ['Build and Run Tests'], + when: { + status: ['success'], + }, }, ], }, From 27d691863f81415f41b432d13fbb783f2684a618 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 4 Sep 2025 14:03:11 +1000 Subject: [PATCH 15/66] Another CI config tweak attempt --- .drone.jsonnet | 53 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 44833d72a4..da03b07860 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -77,23 +77,42 @@ local clean_up_old_test_sims_on_commit_trigger = { { name: 'Build and Run Tests', commands: [ - 'echo "--- Running Build and Tests ---"', - 'echo "Explicitly running unit tests on \'App_Store_Release\' configuration to ensure optimisation behaviour is consistent"', - 'echo "If tests fail inconsistently from local builds this is likely the difference"', - 'echo ""', - 'xcodebuild_output=$(mktemp)', - 'xcodebuild_exit_code=0', - 'NSUnbufferedIO=YES xcodebuild test -project Session.xcodeproj -scheme Session -derivedDataPath ./build/derivedData -resultBundlePath ./build/artifacts/testResults.xcresult -parallelizeTargets -configuration "App_Store_Release" -destination "platform=iOS Simulator,id=$(<./build/artifacts/sim_uuid)" -parallel-testing-enabled NO -test-timeouts-enabled YES -maximum-test-execution-time-allowance 10 -collect-test-diagnostics never ENABLE_TESTABILITY=YES 2>&1 | tee "$xcodebuild_output" | xcbeautify --is-ci || xcodebuild_exit_code=${PIPESTATUS[0]}', - 'echo ""', - 'echo "--- xcodebuild finished with exit code: $xcodebuild_exit_code ---"', - 'echo ""', - 'if [ $xcodebuild_exit_code -ne 0 ]; then', - ' echo "🔴 Build failed. See log above for compile errors."', - ' exit $xcodebuild_exit_code', - 'fi', - 'echo ""', - 'echo "✅ Build Succeeded. Verifying test results..."', - 'xcresultparser --output-format cli --exit-with-error-on-failure ./build/artifacts/testResults.xcresult', + ''' + bash -c ' + # set -e makes the script exit immediately if a command fails. + # set -o pipefail ensures that a pipeline fails if any command in it fails. + set -eo pipefail + + echo "--- Running Build and Tests ---" + echo "Explicitly running unit tests on \\'App_Store_Release\\' configuration..." + echo "" + + xcodebuild_output=$(mktemp) + xcodebuild_exit_code=0 + + # Run the command and capture the true exit code of xcodebuild, even if xcbeautify succeeds. + # We add a `|| true` at the end because `set -e` would otherwise exit the script here + # if the pipeline fails, and we want to handle the exit code manually. + (NSUnbufferedIO=YES xcodebuild test -project Session.xcodeproj -scheme Session -derivedDataPath ./build/derivedData -resultBundlePath ./build/artifacts/testResults.xcresult -parallelizeTargets -configuration "App_Store_Release" -destination "platform=iOS Simulator,id=$(<./build/artifacts/sim_uuid)" -parallel-testing-enabled NO -test-timeouts-enabled YES -maximum-test-execution-time-allowance 10 -collect-test-diagnostics never ENABLE_TESTABILITY=YES 2>&1 | tee "$xcodebuild_output" | xcbeautify --is-ci) || xcodebuild_exit_code=${PIPESTATUS[0]} + + echo "" + echo "--- xcodebuild finished with exit code: $xcodebuild_exit_code ---" + echo "" + + # Check for a build failure (e.g., compile error) + if [ "$xcodebuild_exit_code" -ne 0 ]; then + echo "🔴 Build failed. See log above for compile errors." + exit "$xcodebuild_exit_code" + fi + + echo "" + echo "✅ Build Succeeded. Verifying test results..." + + # If the build succeeded, check the xcresult for test failures. + # The exit code of this command will determine the final status of this step. + xcresultparser --output-format cli --exit-with-error-on-failure ./build/artifacts/testResults.xcresult + ' + ''' ], depends_on: [ 'Reset SPM Cache if Needed', From bace35a5321d7df06958e7040ee30e074405d41c Mon Sep 17 00:00:00 2001 From: mikoldin Date: Thu, 4 Sep 2025 12:07:20 +0800 Subject: [PATCH 16/66] Align dark mode toggle setting design with other platforms --- Session/Settings/AppearanceViewModel.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Session/Settings/AppearanceViewModel.swift b/Session/Settings/AppearanceViewModel.swift index 6f93c3f507..775a97abc0 100644 --- a/Session/Settings/AppearanceViewModel.swift +++ b/Session/Settings/AppearanceViewModel.swift @@ -40,7 +40,7 @@ class AppearanceViewModel: SessionTableViewModel, NavigatableStateHolder, Observ case .themes: return "appearanceThemes".localized() case .primaryColor: return "appearancePrimaryColor".localized() case .primaryColorSelection: return nil - case .autoDarkMode: return "appearanceAutoDarkMode".localized() + case .autoDarkMode: return "darkMode".localized() case .appIcon: return "appIcon".localized() } } @@ -213,10 +213,8 @@ class AppearanceViewModel: SessionTableViewModel, NavigatableStateHolder, Observ elements: [ SessionCell.Info( id: .darkModeMatchSystemSettings, - title: SessionCell.TextInfo( - "followSystemSettings".localized(), - font: .titleRegular - ), + title: "appearanceAutoDarkMode".localized(), + subtitle: "followSystemSettings".localized(), trailingAccessory: .toggle( state.autoDarkModeEnabled, oldValue: previousState.autoDarkModeEnabled From e1af97d3a9f28b37da11d8d52174cf60be670ef8 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 4 Sep 2025 14:11:02 +1000 Subject: [PATCH 17/66] Further tweaks --- .drone.jsonnet | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index da03b07860..d38005a180 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -79,21 +79,16 @@ local clean_up_old_test_sims_on_commit_trigger = { commands: [ ''' bash -c ' - # set -e makes the script exit immediately if a command fails. - # set -o pipefail ensures that a pipeline fails if any command in it fails. - set -eo pipefail - + set -o pipefail + echo "--- Running Build and Tests ---" echo "Explicitly running unit tests on \\'App_Store_Release\\' configuration..." - echo "" - - xcodebuild_output=$(mktemp) + + # This variable will hold the true exit code of xcodebuild xcodebuild_exit_code=0 - # Run the command and capture the true exit code of xcodebuild, even if xcbeautify succeeds. - # We add a `|| true` at the end because `set -e` would otherwise exit the script here - # if the pipeline fails, and we want to handle the exit code manually. - (NSUnbufferedIO=YES xcodebuild test -project Session.xcodeproj -scheme Session -derivedDataPath ./build/derivedData -resultBundlePath ./build/artifacts/testResults.xcresult -parallelizeTargets -configuration "App_Store_Release" -destination "platform=iOS Simulator,id=$(<./build/artifacts/sim_uuid)" -parallel-testing-enabled NO -test-timeouts-enabled YES -maximum-test-execution-time-allowance 10 -collect-test-diagnostics never ENABLE_TESTABILITY=YES 2>&1 | tee "$xcodebuild_output" | xcbeautify --is-ci) || xcodebuild_exit_code=${PIPESTATUS[0]} + # Build and test + (NSUnbufferedIO=YES xcodebuild test -project Session.xcodeproj -scheme Session -derivedDataPath ./build/derivedData -resultBundlePath ./build/artifacts/testResults.xcresult -parallelizeTargets -configuration "App_Store_Release" -destination "platform=iOS Simulator,id=$(<./build/artifacts/sim_uuid)" -parallel-testing-enabled NO -test-timeouts-enabled YES -maximum-test-execution-time-allowance 10 -collect-test-diagnostics never ENABLE_TESTABILITY=YES 2>&1 | tee /tmp/xcodebuild_raw.log | xcbeautify --is-ci) || xcodebuild_exit_code=${PIPESTATUS[0]} echo "" echo "--- xcodebuild finished with exit code: $xcodebuild_exit_code ---" From f09e8a07173683a5a24b46d98d6728d82bd7b05c Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 4 Sep 2025 14:28:20 +1000 Subject: [PATCH 18/66] More tweaks --- .drone.jsonnet | 35 ++---------------------- Scripts/build_ci.sh | 66 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 33 deletions(-) create mode 100755 Scripts/build_ci.sh diff --git a/.drone.jsonnet b/.drone.jsonnet index d38005a180..e921c8c5ee 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -77,37 +77,7 @@ local clean_up_old_test_sims_on_commit_trigger = { { name: 'Build and Run Tests', commands: [ - ''' - bash -c ' - set -o pipefail - - echo "--- Running Build and Tests ---" - echo "Explicitly running unit tests on \\'App_Store_Release\\' configuration..." - - # This variable will hold the true exit code of xcodebuild - xcodebuild_exit_code=0 - - # Build and test - (NSUnbufferedIO=YES xcodebuild test -project Session.xcodeproj -scheme Session -derivedDataPath ./build/derivedData -resultBundlePath ./build/artifacts/testResults.xcresult -parallelizeTargets -configuration "App_Store_Release" -destination "platform=iOS Simulator,id=$(<./build/artifacts/sim_uuid)" -parallel-testing-enabled NO -test-timeouts-enabled YES -maximum-test-execution-time-allowance 10 -collect-test-diagnostics never ENABLE_TESTABILITY=YES 2>&1 | tee /tmp/xcodebuild_raw.log | xcbeautify --is-ci) || xcodebuild_exit_code=${PIPESTATUS[0]} - - echo "" - echo "--- xcodebuild finished with exit code: $xcodebuild_exit_code ---" - echo "" - - # Check for a build failure (e.g., compile error) - if [ "$xcodebuild_exit_code" -ne 0 ]; then - echo "🔴 Build failed. See log above for compile errors." - exit "$xcodebuild_exit_code" - fi - - echo "" - echo "✅ Build Succeeded. Verifying test results..." - - # If the build succeeded, check the xcresult for test failures. - # The exit code of this command will determine the final status of this step. - xcresultparser --output-format cli --exit-with-error-on-failure ./build/artifacts/testResults.xcresult - ' - ''' + './Scripts/run_xcode_ci.sh test -resultBundlePath ./build/artifacts/testResults.xcresult -destination "platform=iOS Simulator,id=$(<./build/artifacts/sim_uuid)" -parallel-testing-enabled NO -test-timeouts-enabled YES -maximum-test-execution-time-allowance 10 -collect-test-diagnostics never ENABLE_TESTABILITY=YES', ], depends_on: [ 'Reset SPM Cache if Needed', @@ -179,8 +149,7 @@ local clean_up_old_test_sims_on_commit_trigger = { { name: 'Build', commands: [ - 'mkdir build', - 'NSUnbufferedIO=YES && xcodebuild archive -project Session.xcodeproj -scheme Session -derivedDataPath ./build/derivedData -parallelizeTargets -configuration "App_Store_Release" -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | xcbeautify --is-ci', + './Scripts/run_xcode_ci.sh archive -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator"', ], depends_on: [ 'Reset SPM Cache if Needed', diff --git a/Scripts/build_ci.sh b/Scripts/build_ci.sh new file mode 100755 index 0000000000..dd9a410b7a --- /dev/null +++ b/Scripts/build_ci.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +set -euo pipefail + +if [ $# -lt 1 ]; then + echo "Error: Missing mode. Usage: $0 [test|archive] [unique_xcodebuild_args...]" + exit 1 +fi + +MODE="$1" +shift + +COMMON_ARGS=( + -project Session.xcodeproj + -scheme Session + -derivedDataPath ./build/derivedData + -parallelizeTargets + -configuration "App_Store_Release" +) + +UNIQUE_ARGS=("$@") + +if [[ "$MODE" == "test" ]]; then + + echo "--- Running Build and Unit Tests (App_Store_Release) ---" + + mkdir build + xcodebuild_exit_code=0 + + # We wrap the pipeline in parentheses to capture the exit code of xcodebuild + # which is at PIPESTATUS[0]. We do not use tee to a file here, as the complexity + # of reading back the UUID is not necessary if we pass it via args. + ( + NSUnbufferedIO=YES xcodebuild test \ + "${COMMON_ARGS[@]}" \ + "${UNIQUE_ARGS[@]}" 2>&1 | xcbeautify --is-ci + ) || xcodebuild_exit_code=${PIPESTATUS[0]} + + echo "" + echo "--- xcodebuild finished with exit code: $xcodebuild_exit_code ---" + + # Check for a build failure (e.g., compile error, linker issue, or simulator connection error) + if [ "$xcodebuild_exit_code" -ne 0 ]; then + echo "🔴 Build failed. See log above for compile errors." + exit "$xcodebuild_exit_code" + fi + + echo "" + echo "✅ Build Succeeded. Verifying test results from xcresult bundle..." + + # If the build passed, xcresultparser becomes the final gatekeeper for test results. + xcresultparser --output-format cli --exit-with-error-on-failure ./build/artifacts/testResults.xcresult + +elif [[ "$MODE" == "archive" ]]; then + + echo "--- Running Simulator Archive Build (App_Store_Release) ---" + + mkdir build + NSUnbufferedIO=YES xcodebuild archive \ + "${COMMON_ARGS[@]}" \ + "${UNIQUE_ARGS[@]}" 2>&1 | xcbeautify --is-ci + +else + echo "Error: Invalid mode '$MODE' specified. Use 'test' or 'archive'." + exit 1 +fi From 1d2e00bed251ad869c3b414599141967d03cbda3 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 4 Sep 2025 15:00:04 +1000 Subject: [PATCH 19/66] Script name error --- .drone.jsonnet | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index e921c8c5ee..2befffe493 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -77,7 +77,7 @@ local clean_up_old_test_sims_on_commit_trigger = { { name: 'Build and Run Tests', commands: [ - './Scripts/run_xcode_ci.sh test -resultBundlePath ./build/artifacts/testResults.xcresult -destination "platform=iOS Simulator,id=$(<./build/artifacts/sim_uuid)" -parallel-testing-enabled NO -test-timeouts-enabled YES -maximum-test-execution-time-allowance 10 -collect-test-diagnostics never ENABLE_TESTABILITY=YES', + './Scripts/build_ci.sh test -resultBundlePath ./build/artifacts/testResults.xcresult -destination "platform=iOS Simulator,id=$(<./build/artifacts/sim_uuid)" -parallel-testing-enabled NO -test-timeouts-enabled YES -maximum-test-execution-time-allowance 10 -collect-test-diagnostics never ENABLE_TESTABILITY=YES', ], depends_on: [ 'Reset SPM Cache if Needed', @@ -149,7 +149,7 @@ local clean_up_old_test_sims_on_commit_trigger = { { name: 'Build', commands: [ - './Scripts/run_xcode_ci.sh archive -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator"', + './Scripts/build_ci.sh archive -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator"', ], depends_on: [ 'Reset SPM Cache if Needed', From 9369d228f219ce11bef1c51dd9a4843c4f5baa1d Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 4 Sep 2025 15:14:27 +1000 Subject: [PATCH 20/66] Another CI tweak --- Scripts/build_ci.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/Scripts/build_ci.sh b/Scripts/build_ci.sh index dd9a410b7a..5be06c83d7 100755 --- a/Scripts/build_ci.sh +++ b/Scripts/build_ci.sh @@ -24,7 +24,6 @@ if [[ "$MODE" == "test" ]]; then echo "--- Running Build and Unit Tests (App_Store_Release) ---" - mkdir build xcodebuild_exit_code=0 # We wrap the pipeline in parentheses to capture the exit code of xcodebuild @@ -55,7 +54,6 @@ elif [[ "$MODE" == "archive" ]]; then echo "--- Running Simulator Archive Build (App_Store_Release) ---" - mkdir build NSUnbufferedIO=YES xcodebuild archive \ "${COMMON_ARGS[@]}" \ "${UNIQUE_ARGS[@]}" 2>&1 | xcbeautify --is-ci From c7d253fb4962540b610d862e0311c12a9cf6f40c Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 4 Sep 2025 15:36:33 +1000 Subject: [PATCH 21/66] Try to output error summary to avoid having to read log --- Scripts/build_ci.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Scripts/build_ci.sh b/Scripts/build_ci.sh index 5be06c83d7..bec8b93877 100755 --- a/Scripts/build_ci.sh +++ b/Scripts/build_ci.sh @@ -40,7 +40,11 @@ if [[ "$MODE" == "test" ]]; then # Check for a build failure (e.g., compile error, linker issue, or simulator connection error) if [ "$xcodebuild_exit_code" -ne 0 ]; then - echo "🔴 Build failed. See log above for compile errors." + echo "🔴 Build failed. See log above for full context." + echo "" + echo "--- Summary of Errors ---" + grep -i --color=always "error:" /tmp/xcodebuild_raw.log || true + echo "-------------------------" exit "$xcodebuild_exit_code" fi From ab527466650250464589ddc189aca5e18b8db574 Mon Sep 17 00:00:00 2001 From: mikoldin Date: Thu, 4 Sep 2025 14:01:16 +0800 Subject: [PATCH 22/66] Aligned deleted message bubble's font and text color --- .../Message Cells/Content Views/DeletedMessageView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift b/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift index 566ddceca1..522dffc243 100644 --- a/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift +++ b/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift @@ -36,6 +36,7 @@ final class DeletedMessageView: UIView { let imageView = UIImageView(image: Lucide.image(icon: .trash2, size: DeletedMessageView.iconSize)?.withRenderingMode(.alwaysTemplate)) imageView.themeTintColor = textColor + imageView.alpha = Values.mediumOpacity imageView.contentMode = .scaleAspectFit imageView.set(.width, to: DeletedMessageView.iconSize) imageView.set(.height, to: DeletedMessageView.iconSize) @@ -46,7 +47,7 @@ final class DeletedMessageView: UIView { let titleLabel = UILabel() titleLabel.setContentHuggingPriority(.required, for: .vertical) titleLabel.preferredMaxLayoutWidth = maxWidth - 6 // `6` for the `stackView.layoutMargins` - titleLabel.font = .systemFont(ofSize: Values.smallFontSize) + titleLabel.font = .italicSystemFont(ofSize: Values.smallFontSize) titleLabel.text = { switch variant { case .standardIncomingDeletedLocally, .standardOutgoingDeletedLocally: @@ -56,6 +57,7 @@ final class DeletedMessageView: UIView { } }() titleLabel.themeTextColor = textColor + titleLabel.alpha = Values.mediumOpacity titleLabel.lineBreakMode = .byTruncatingTail titleLabel.numberOfLines = 2 From 547595a1c0468fb7da6cafff868010716f28dead Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 4 Sep 2025 16:07:24 +1000 Subject: [PATCH 23/66] Log output tweaks --- Scripts/build_ci.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Scripts/build_ci.sh b/Scripts/build_ci.sh index bec8b93877..4e4dd14ee8 100755 --- a/Scripts/build_ci.sh +++ b/Scripts/build_ci.sh @@ -19,6 +19,9 @@ COMMON_ARGS=( ) UNIQUE_ARGS=("$@") +XCODEBUILD_RAW_LOG=$(mktemp) + +trap 'rm -f "$XCODEBUILD_RAW_LOG"' EXIT if [[ "$MODE" == "test" ]]; then @@ -32,7 +35,7 @@ if [[ "$MODE" == "test" ]]; then ( NSUnbufferedIO=YES xcodebuild test \ "${COMMON_ARGS[@]}" \ - "${UNIQUE_ARGS[@]}" 2>&1 | xcbeautify --is-ci + "${UNIQUE_ARGS[@]}" 2>&1 | tee "$XCODEBUILD_RAW_LOG" | xcbeautify --is-ci ) || xcodebuild_exit_code=${PIPESTATUS[0]} echo "" @@ -43,7 +46,7 @@ if [[ "$MODE" == "test" ]]; then echo "🔴 Build failed. See log above for full context." echo "" echo "--- Summary of Errors ---" - grep -i --color=always "error:" /tmp/xcodebuild_raw.log || true + grep -i --color=always "error:" "$XCODEBUILD_RAW_LOG" || true echo "-------------------------" exit "$xcodebuild_exit_code" fi From 6777740f9c573193917d09fedc103474bf1311c9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 4 Sep 2025 17:03:54 +1000 Subject: [PATCH 24/66] Fix build error --- .../_TestUtilities/MockImageDataManager.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SessionMessagingKitTests/_TestUtilities/MockImageDataManager.swift b/SessionMessagingKitTests/_TestUtilities/MockImageDataManager.swift index d409bede10..78693da700 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockImageDataManager.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockImageDataManager.swift @@ -12,9 +12,10 @@ class MockImageDataManager: Mock, ImageDataManagerType { return mock(args: [source]) } + @MainActor func load( _ source: ImageDataManager.DataSource, - onComplete: @escaping (ImageDataManager.ProcessedImageData?) -> Void + onComplete: @MainActor @escaping (ImageDataManager.ProcessedImageData?) -> Void ) { mockNoReturn(args: [source], untrackedArgs: [onComplete]) } From addad4cfd20e14e68d6548cf491cc56c3fd30fb7 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 4 Sep 2025 17:32:14 +1000 Subject: [PATCH 25/66] More tweaks --- Scripts/build_ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/build_ci.sh b/Scripts/build_ci.sh index 4e4dd14ee8..2b06471851 100755 --- a/Scripts/build_ci.sh +++ b/Scripts/build_ci.sh @@ -46,7 +46,7 @@ if [[ "$MODE" == "test" ]]; then echo "🔴 Build failed. See log above for full context." echo "" echo "--- Summary of Errors ---" - grep -i --color=always "error:" "$XCODEBUILD_RAW_LOG" || true + grep -E --color=always '(:[0-9]+:[0-9]+: error:)|(ld: error:)|(Command PhaseScriptExecution failed)' "$XCODEBUILD_RAW_LOG" || true echo "-------------------------" exit "$xcodebuild_exit_code" fi From bc13d9d5214eab2a065a8c6b974f0bc2c07dfbdd Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 5 Sep 2025 08:29:38 +1000 Subject: [PATCH 26/66] Verify test results --- Scripts/build_ci.sh | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Scripts/build_ci.sh b/Scripts/build_ci.sh index 2b06471851..4b9864f660 100755 --- a/Scripts/build_ci.sh +++ b/Scripts/build_ci.sh @@ -1,5 +1,7 @@ #!/bin/bash +IFS=$' \t\n' + set -euo pipefail if [ $# -lt 1 ]; then @@ -54,8 +56,20 @@ if [[ "$MODE" == "test" ]]; then echo "" echo "✅ Build Succeeded. Verifying test results from xcresult bundle..." - # If the build passed, xcresultparser becomes the final gatekeeper for test results. - xcresultparser --output-format cli --exit-with-error-on-failure ./build/artifacts/testResults.xcresult + # If the build passed, xcresultparser becomes the final gatekeeper for test results + parser_output=$(xcresultparser --output-format cli ./build/artifacts/testResults.xcresult) + echo "$parser_output" + + build_errors_count=$(echo "$parser_output" | grep "Number of errors" | awk '{print $NF}') + failed_tests_count=$(echo "$parser_output" | grep "Number of failed tests" | awk '{print $NF}') + + if [ "${build_errors_count:-0}" -gt 0 ] || [ "${failed_tests_count:-0}" -gt 0 ]; then + echo "" + echo "🔴 Verification failed: Found $build_errors_count build error(s) and $failed_tests_count failed test(s) in the xcresult bundle." + exit 1 + else + echo "✅ Verification successful: No build errors or test failures found." + fi elif [[ "$MODE" == "archive" ]]; then From b08d759f93d14b059d8f9b1aa4b5851edd45f1f8 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 5 Sep 2025 08:49:08 +1000 Subject: [PATCH 27/66] Further tweaks --- Scripts/build_ci.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/build_ci.sh b/Scripts/build_ci.sh index 4b9864f660..fcc8c2c1b2 100755 --- a/Scripts/build_ci.sh +++ b/Scripts/build_ci.sh @@ -57,8 +57,8 @@ if [[ "$MODE" == "test" ]]; then echo "✅ Build Succeeded. Verifying test results from xcresult bundle..." # If the build passed, xcresultparser becomes the final gatekeeper for test results - parser_output=$(xcresultparser --output-format cli ./build/artifacts/testResults.xcresult) - echo "$parser_output" + xcresultparser --output-format cli --no-test-result --coverage ./build/artifacts/testResults.xcresult + parser_output=$(xcresultparser --output-format cli --no-test-result ./build/artifacts/testResults.xcresult) build_errors_count=$(echo "$parser_output" | grep "Number of errors" | awk '{print $NF}') failed_tests_count=$(echo "$parser_output" | grep "Number of failed tests" | awk '{print $NF}') From 2fb0173db24bfd088a1a81bd81bf193a90723523 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 5 Sep 2025 09:37:04 +1000 Subject: [PATCH 28/66] Another tweak --- Scripts/build_ci.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Scripts/build_ci.sh b/Scripts/build_ci.sh index fcc8c2c1b2..5c5cf69b07 100755 --- a/Scripts/build_ci.sh +++ b/Scripts/build_ci.sh @@ -47,9 +47,15 @@ if [[ "$MODE" == "test" ]]; then if [ "$xcodebuild_exit_code" -ne 0 ]; then echo "🔴 Build failed. See log above for full context." echo "" - echo "--- Summary of Errors ---" + echo "--- Summary of Potential Build Errors ---" grep -E --color=always '(:[0-9]+:[0-9]+: error:)|(ld: error:)|(Command PhaseScriptExecution failed)' "$XCODEBUILD_RAW_LOG" || true - echo "-------------------------" + echo "" + echo "--- End of Raw Log (for context on unknown errors) ---" + + # If the grep above was empty, the error is likely in the last few lines + tail -n 50 "$XCODEBUILD_RAW_LOG" + + echo "----------------------------------------------------" exit "$xcodebuild_exit_code" fi From ab0ce7a5116231ee41b08d7e998f3d6f7e287a0d Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 5 Sep 2025 09:57:51 +1000 Subject: [PATCH 29/66] A few more tweaks to better indicate failures --- .drone.jsonnet | 1 + Scripts/build_ci.sh | 42 ++++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 2befffe493..64e175cee2 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -101,6 +101,7 @@ local clean_up_old_test_sims_on_commit_trigger = { commands: [ 'echo "--- FAILED TESTS ---"', 'xcresultparser --output-format cli --failed-tests-only ./build/artifacts/testResults.xcresult', + 'exit 1' // Always fail if this runs to make it more obvious in the UI ], depends_on: ['Build and Run Tests'], when: { diff --git a/Scripts/build_ci.sh b/Scripts/build_ci.sh index 5c5cf69b07..9ff7932d3d 100755 --- a/Scripts/build_ci.sh +++ b/Scripts/build_ci.sh @@ -43,26 +43,16 @@ if [[ "$MODE" == "test" ]]; then echo "" echo "--- xcodebuild finished with exit code: $xcodebuild_exit_code ---" - # Check for a build failure (e.g., compile error, linker issue, or simulator connection error) - if [ "$xcodebuild_exit_code" -ne 0 ]; then - echo "🔴 Build failed. See log above for full context." - echo "" - echo "--- Summary of Potential Build Errors ---" - grep -E --color=always '(:[0-9]+:[0-9]+: error:)|(ld: error:)|(Command PhaseScriptExecution failed)' "$XCODEBUILD_RAW_LOG" || true - echo "" - echo "--- End of Raw Log (for context on unknown errors) ---" - - # If the grep above was empty, the error is likely in the last few lines - tail -n 50 "$XCODEBUILD_RAW_LOG" - - echo "----------------------------------------------------" - exit "$xcodebuild_exit_code" + if [ "$xcodebuild_exit_code" -eq 0 ]; then + echo "✅ All tests passed and build succeeded!" + exit 0 fi - + echo "" - echo "✅ Build Succeeded. Verifying test results from xcresult bundle..." - - # If the build passed, xcresultparser becomes the final gatekeeper for test results + echo "🔴 Build failed" + echo "----------------------------------------------------" + echo "Checking for test failures in xcresult bundle..." + xcresultparser --output-format cli --no-test-result --coverage ./build/artifacts/testResults.xcresult parser_output=$(xcresultparser --output-format cli --no-test-result ./build/artifacts/testResults.xcresult) @@ -71,11 +61,23 @@ if [[ "$MODE" == "test" ]]; then if [ "${build_errors_count:-0}" -gt 0 ] || [ "${failed_tests_count:-0}" -gt 0 ]; then echo "" - echo "🔴 Verification failed: Found $build_errors_count build error(s) and $failed_tests_count failed test(s) in the xcresult bundle." + echo "🔴 Found $build_errors_count build error(s) and $failed_tests_count failed test(s) in the xcresult bundle." exit 1 else - echo "✅ Verification successful: No build errors or test failures found." + echo "No test failures found in results. Failure was likely a build error." + echo "" + + echo "--- Summary of Potential Build Errors ---" + grep -E --color=always '(:[0-9]+:[0-9]+: error:)|(ld: error:)|(error: linker command failed)|(PhaseScriptExecution)|(rsync error:)' "$XCODEBUILD_RAW_LOG" || true + echo "" + echo "--- End of Raw Log ---" + tail -n 20 "$XCODEBUILD_RAW_LOG" + echo "-------------------------" + exit "$xcodebuild_exit_code" fi + + echo "----------------------------------------------------" + exit "$xcodebuild_exit_code" elif [[ "$MODE" == "archive" ]]; then From 5deb0be9b7a82d02367659186354d2d9cd7e6349 Mon Sep 17 00:00:00 2001 From: mikoldin Date: Fri, 5 Sep 2025 08:52:49 +0800 Subject: [PATCH 30/66] Updated paddings and font sizes --- .../Message Cells/CallMessageCell.swift | 2 +- .../Content Views/DeletedMessageView.swift | 15 +++++++++++---- SessionUIKit/Style Guide/Values.swift | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Session/Conversations/Message Cells/CallMessageCell.swift b/Session/Conversations/Message Cells/CallMessageCell.swift index 305ea3a29c..13566d5769 100644 --- a/Session/Conversations/Message Cells/CallMessageCell.swift +++ b/Session/Conversations/Message Cells/CallMessageCell.swift @@ -48,7 +48,7 @@ final class CallMessageCell: MessageCell { private lazy var label: UILabel = { let result: UILabel = UILabel() - result.font = .boldSystemFont(ofSize: Values.smallFontSize) + result.font = .boldSystemFont(ofSize: Values.mediumFontSize) result.themeTextColor = .textPrimary result.textAlignment = .center result.lineBreakMode = .byWordWrapping diff --git a/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift b/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift index 522dffc243..72f5e5c4b1 100644 --- a/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift +++ b/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift @@ -10,6 +10,8 @@ import SessionUtilitiesKit final class DeletedMessageView: UIView { private static let iconSize: CGFloat = 18 private static let iconImageViewSize: CGFloat = 30 + private static let horizontalInset = Values.mediumSmallSpacing + private static let verticalInset = Values.smallSpacing // MARK: - Lifecycle @@ -36,7 +38,7 @@ final class DeletedMessageView: UIView { let imageView = UIImageView(image: Lucide.image(icon: .trash2, size: DeletedMessageView.iconSize)?.withRenderingMode(.alwaysTemplate)) imageView.themeTintColor = textColor - imageView.alpha = Values.mediumOpacity + imageView.alpha = Values.highOpacity imageView.contentMode = .scaleAspectFit imageView.set(.width, to: DeletedMessageView.iconSize) imageView.set(.height, to: DeletedMessageView.iconSize) @@ -47,7 +49,7 @@ final class DeletedMessageView: UIView { let titleLabel = UILabel() titleLabel.setContentHuggingPriority(.required, for: .vertical) titleLabel.preferredMaxLayoutWidth = maxWidth - 6 // `6` for the `stackView.layoutMargins` - titleLabel.font = .italicSystemFont(ofSize: Values.smallFontSize) + titleLabel.font = .italicSystemFont(ofSize: Values.mediumFontSize) titleLabel.text = { switch variant { case .standardIncomingDeletedLocally, .standardOutgoingDeletedLocally: @@ -57,7 +59,7 @@ final class DeletedMessageView: UIView { } }() titleLabel.themeTextColor = textColor - titleLabel.alpha = Values.mediumOpacity + titleLabel.alpha = Values.highOpacity titleLabel.lineBreakMode = .byTruncatingTail titleLabel.numberOfLines = 2 @@ -70,7 +72,12 @@ final class DeletedMessageView: UIView { addSubview(stackView) let calculatedSize: CGSize = stackView.systemLayoutSizeFitting(CGSize(width: maxWidth, height: 999)) - stackView.pin(to: self, withInset: Values.smallSpacing) + + stackView.pin(.top, to: .top, of: self, withInset: Self.verticalInset) + stackView.pin(.leading, to: .leading, of: self, withInset: Self.horizontalInset) + stackView.pin(.trailing, to: .trailing, of: self, withInset: -Self.horizontalInset) + stackView.pin(.bottom, to: .bottom, of: self, withInset: -Self.verticalInset) + stackView.set(.height, to: calculatedSize.height) } } diff --git a/SessionUIKit/Style Guide/Values.swift b/SessionUIKit/Style Guide/Values.swift index ffd825c2af..e1b8d4eb8c 100644 --- a/SessionUIKit/Style Guide/Values.swift +++ b/SessionUIKit/Style Guide/Values.swift @@ -8,7 +8,7 @@ public enum Values { public static let veryLowOpacity = CGFloat(0.12) public static let lowOpacity = CGFloat(0.4) public static let mediumOpacity = CGFloat(0.6) - public static let highOpacity = CGFloat(0.75) + public static let highOpacity = CGFloat(0.7) // MARK: - Font Sizes public static let miniFontSize = isIPhone5OrSmaller ? CGFloat(8) : CGFloat(10) From e6bda959abf8c36816356046beeffdf1bd0ea899 Mon Sep 17 00:00:00 2001 From: mikoldin Date: Fri, 5 Sep 2025 11:40:15 +0800 Subject: [PATCH 31/66] Refactor accessory view alignment handling --- Session/Settings/AppearanceViewModel.swift | 3 +- .../ConversationSettingsViewModel.swift | 3 +- Session/Settings/HelpViewModel.swift | 12 ++--- .../Shared/Types/SessionCell+Accessory.swift | 25 +++++---- .../Views/SessionCell+AccessoryView.swift | 54 ++++++++++++++----- 5 files changed, 63 insertions(+), 34 deletions(-) diff --git a/Session/Settings/AppearanceViewModel.swift b/Session/Settings/AppearanceViewModel.swift index c88b392681..8ae3cba518 100644 --- a/Session/Settings/AppearanceViewModel.swift +++ b/Session/Settings/AppearanceViewModel.swift @@ -240,8 +240,7 @@ class AppearanceViewModel: SessionTableViewModel, NavigatableStateHolder, Observ ), trailingAccessory: .icon( .chevronRight, - shouldFill: true , - shouldFollowIconSize: true + pinEdges: [.right] ), onTap: { [weak viewModel, dependencies = viewModel.dependencies] in viewModel?.transitionToScreen( diff --git a/Session/Settings/ConversationSettingsViewModel.swift b/Session/Settings/ConversationSettingsViewModel.swift index 68214b2380..d26cff429d 100644 --- a/Session/Settings/ConversationSettingsViewModel.swift +++ b/Session/Settings/ConversationSettingsViewModel.swift @@ -190,8 +190,7 @@ class ConversationSettingsViewModel: SessionTableViewModel, NavigatableStateHold subtitle: "blockedContactsManageDescription".localized(), trailingAccessory: .icon( .chevronRight, - shouldFill: true , - shouldFollowIconSize: true + pinEdges: [.right] ), onTap: { [weak viewModel, dependencies = viewModel.dependencies] in viewModel?.transitionToScreen( diff --git a/Session/Settings/HelpViewModel.swift b/Session/Settings/HelpViewModel.swift index 26e38683da..4ab69ce0d1 100644 --- a/Session/Settings/HelpViewModel.swift +++ b/Session/Settings/HelpViewModel.swift @@ -77,8 +77,7 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa UIImage(systemName: "arrow.up.forward.app")? .withRenderingMode(.alwaysTemplate), size: .small, - shouldFill: true, - shouldFollowIconSize: true + pinEdges: [.right] ), onTap: { guard let url: URL = URL(string: "https://getsession.org/translate") else { @@ -100,8 +99,7 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa UIImage(systemName: "arrow.up.forward.app")? .withRenderingMode(.alwaysTemplate), size: .small, - shouldFill: true, - shouldFollowIconSize: true + pinEdges: [.right] ), onTap: { guard let url: URL = URL(string: "https://getsession.org/survey") else { @@ -123,8 +121,7 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa UIImage(systemName: "arrow.up.forward.app")? .withRenderingMode(.alwaysTemplate), size: .small, - shouldFill: true, - shouldFollowIconSize: true + pinEdges: [.right] ), onTap: { guard let url: URL = URL(string: "https://getsession.org/faq") else { @@ -146,8 +143,7 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa UIImage(systemName: "arrow.up.forward.app")? .withRenderingMode(.alwaysTemplate), size: .small, - shouldFill: true, - shouldFollowIconSize: true + pinEdges: [.right] ), onTap: { guard let url: URL = URL(string: "https://sessionapp.zendesk.com/hc/en-us") else { diff --git a/Session/Shared/Types/SessionCell+Accessory.swift b/Session/Shared/Types/SessionCell+Accessory.swift index 4ebb6e17e3..f27e6e19db 100644 --- a/Session/Shared/Types/SessionCell+Accessory.swift +++ b/Session/Shared/Types/SessionCell+Accessory.swift @@ -40,7 +40,7 @@ public extension SessionCell.Accessory { size: IconSize = .medium, customTint: ThemeValue? = nil, shouldFill: Bool = false, - shouldFollowIconSize: Bool = false, + pinEdges: [UIView.HorizontalEdge] = [.leading, .trailing], accessibility: Accessibility? = nil, ) -> SessionCell.Accessory { return SessionCell.AccessoryConfig.Icon( @@ -49,7 +49,7 @@ public extension SessionCell.Accessory { iconSize: size, customTint: customTint, shouldFill: shouldFill, - shouldFollowIconSize: shouldFollowIconSize, + pinEdges: pinEdges, accessibility: accessibility ) } @@ -59,7 +59,7 @@ public extension SessionCell.Accessory { size: IconSize = .medium, customTint: ThemeValue? = nil, shouldFill: Bool = false, - shouldFollowIconSize: Bool = false, + pinEdges: [UIView.HorizontalEdge] = [.leading, .trailing], accessibility: Accessibility? = nil, ) -> SessionCell.Accessory { return SessionCell.AccessoryConfig.Icon( @@ -68,7 +68,7 @@ public extension SessionCell.Accessory { iconSize: size, customTint: customTint, shouldFill: shouldFill, - shouldFollowIconSize: shouldFollowIconSize, + pinEdges: pinEdges, accessibility: accessibility ) } @@ -78,6 +78,7 @@ public extension SessionCell.Accessory { source: ImageDataManager.DataSource?, customTint: ThemeValue? = nil, shouldFill: Bool = false, + pinEdges: [UIView.HorizontalEdge] = [.leading, .trailing], accessibility: Accessibility? = nil ) -> SessionCell.Accessory { return SessionCell.AccessoryConfig.IconAsync( @@ -85,6 +86,7 @@ public extension SessionCell.Accessory { source: source, customTint: customTint, shouldFill: shouldFill, + pinEdges: pinEdges, accessibility: accessibility ) } @@ -230,7 +232,7 @@ public extension SessionCell.AccessoryConfig { public let iconSize: IconSize public let customTint: ThemeValue? public let shouldFill: Bool - public let shouldFollowIconSize: Bool + public let pinEdges: [UIView.HorizontalEdge] fileprivate init( icon: Lucide.Icon?, @@ -238,7 +240,7 @@ public extension SessionCell.AccessoryConfig { iconSize: IconSize, customTint: ThemeValue?, shouldFill: Bool, - shouldFollowIconSize: Bool = false, + pinEdges: [UIView.HorizontalEdge], accessibility: Accessibility? ) { self.icon = icon @@ -246,7 +248,7 @@ public extension SessionCell.AccessoryConfig { self.iconSize = iconSize self.customTint = customTint self.shouldFill = shouldFill - self.shouldFollowIconSize = shouldFollowIconSize + self.pinEdges = pinEdges super.init(accessibility: accessibility) } @@ -259,7 +261,7 @@ public extension SessionCell.AccessoryConfig { iconSize.hash(into: &hasher) customTint.hash(into: &hasher) shouldFill.hash(into: &hasher) - shouldFollowIconSize.hash(into: &hasher) + pinEdges.hash(into: &hasher) accessibility.hash(into: &hasher) } @@ -272,7 +274,7 @@ public extension SessionCell.AccessoryConfig { iconSize == rhs.iconSize && customTint == rhs.customTint && shouldFill == rhs.shouldFill && - shouldFollowIconSize == rhs.shouldFollowIconSize && + pinEdges == rhs.pinEdges && accessibility == rhs.accessibility ) @@ -288,18 +290,21 @@ public extension SessionCell.AccessoryConfig { public let source: ImageDataManager.DataSource? public let customTint: ThemeValue? public let shouldFill: Bool + public let pinEdges: [UIView.HorizontalEdge] fileprivate init( iconSize: IconSize, source: ImageDataManager.DataSource?, customTint: ThemeValue?, shouldFill: Bool, + pinEdges: [UIView.HorizontalEdge], accessibility: Accessibility? ) { self.iconSize = iconSize self.source = source self.customTint = customTint self.shouldFill = shouldFill + self.pinEdges = pinEdges super.init(accessibility: accessibility) } @@ -311,6 +316,7 @@ public extension SessionCell.AccessoryConfig { source?.hash(into: &hasher) customTint.hash(into: &hasher) shouldFill.hash(into: &hasher) + pinEdges.hash(into: &hasher) accessibility.hash(into: &hasher) } @@ -322,6 +328,7 @@ public extension SessionCell.AccessoryConfig { source == rhs.source && customTint == rhs.customTint && shouldFill == rhs.shouldFill && + pinEdges == rhs.pinEdges && accessibility == rhs.accessibility ) } diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index a93a8c41f3..d3b47b0dd1 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -73,7 +73,7 @@ extension SessionCell { if let newView: UIView = maybeView { addSubview(newView) - newView.pin(to: self) + pin(view: newView, accessory: accessory) layout(view: newView, accessory: accessory) } @@ -189,13 +189,36 @@ extension SessionCell { } } + // Determine the type of accessory to decide how to pin the view. + private func pin(view: UIView?, accessory: Accessory) { + // Icon and IconAsync types have specific layouts, so we do nothing here. + switch accessory { + case _ as SessionCell.AccessoryConfig.Icon: break + case _ as SessionCell.AccessoryConfig.IconAsync: break + default: + // For all other accessory types, pin the view to the current view. + // This is a generic layout for accessories that don't need a custom position. + view?.pin(to: self) + } + } + private func layout(view: UIView?, accessory: Accessory) { switch accessory { case let accessory as SessionCell.AccessoryConfig.Icon: - layoutIconView(view, iconSize: accessory.iconSize, shouldFill: accessory.shouldFill) + layoutIconView( + view, + iconSize: accessory.iconSize, + shouldFill: accessory.shouldFill, + pin: accessory.pinEdges + ) case let accessory as SessionCell.AccessoryConfig.IconAsync: - layoutIconView(view, iconSize: accessory.iconSize, shouldFill: accessory.shouldFill) + layoutIconView( + view, + iconSize: accessory.iconSize, + shouldFill: accessory.shouldFill, + pin: accessory.pinEdges + ) case is SessionCell.AccessoryConfig.Toggle: layoutToggleView(view) case is SessionCell.AccessoryConfig.DropDown: layoutDropDownView(view) @@ -288,13 +311,25 @@ extension SessionCell { return result } - private func layoutIconView(_ view: UIView?, iconSize: IconSize, shouldFill: Bool) { + private func layoutIconView(_ view: UIView?, iconSize: IconSize, shouldFill: Bool, pin edges: [UIView.HorizontalEdge]) { guard let imageView: SessionImageView = view as? SessionImageView else { return } imageView.set(.width, to: iconSize.size) imageView.set(.height, to: iconSize.size) - imageView.pin(.leading, to: .leading, of: self, withInset: (shouldFill ? 0 : Values.smallSpacing)) - imageView.pin(.trailing, to: .trailing, of: self, withInset: (shouldFill ? 0 : -Values.smallSpacing)) + imageView.pin(.top, to: .top, of: self) + imageView.pin(.bottom, to: .bottom, of: self) + + let shouldInvertPadding: [UIView.HorizontalEdge] = [.left, .trailing] + + for edge in edges { + let inset: CGFloat = ( + (shouldFill ? 0 : Values.smallSpacing) * + (shouldInvertPadding.contains(edge) ? -1 : 1) + ) + + imageView.pin(edge, to: edge, of: self, withInset: inset) + } + fixedWidthConstraint.isActive = (iconSize.size <= fixedWidthConstraint.constant) minWidthConstraint.isActive = !fixedWidthConstraint.isActive } @@ -306,13 +341,6 @@ extension SessionCell { imageView.accessibilityLabel = accessory.accessibility?.label imageView.themeTintColor = (accessory.customTint ?? tintColor) imageView.contentMode = (accessory.shouldFill ? .scaleAspectFill : .scaleAspectFit) - - // Use icon size when displaying accessory view. - // 50 width causes large padding not aligning accessory to right - if accessory.shouldFollowIconSize { - fixedWidthConstraint.constant = accessory.iconSize.size - fixedWidthConstraint.isActive = true - } switch (accessory.icon, accessory.image) { case (.some(let icon), _): From fb13dee5900689db1b987e2332ba8e7bb2b9f51c Mon Sep 17 00:00:00 2001 From: mikoldin Date: Mon, 8 Sep 2025 07:51:18 +0800 Subject: [PATCH 32/66] Clean up code --- Session/Settings/Views/NewTagView.swift | 2 +- .../Views/SessionCell+AccessoryView.swift | 20 +++++-------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/Session/Settings/Views/NewTagView.swift b/Session/Settings/Views/NewTagView.swift index f50daca9d8..5156a4885a 100644 --- a/Session/Settings/Views/NewTagView.swift +++ b/Session/Settings/Views/NewTagView.swift @@ -34,7 +34,7 @@ final class NewTagView: UIView { private func setupUI() { addSubview(newTagLabel) - newTagLabel.pin(.leading, to: .leading, of: self, withInset: -Values.mediumSpacing + Values.verySmallSpacing) + newTagLabel.pin(.leading, to: .leading, of: self, withInset: -(Values.mediumSpacing + Values.verySmallSpacing)) newTagLabel.pin([ UIView.VerticalEdge.top, UIView.VerticalEdge.bottom, UIView.HorizontalEdge.trailing ], to: self) } diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index d3b47b0dd1..7a953315b8 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -73,7 +73,6 @@ extension SessionCell { if let newView: UIView = maybeView { addSubview(newView) - pin(view: newView, accessory: accessory) layout(view: newView, accessory: accessory) } @@ -188,20 +187,7 @@ extension SessionCell { return nil } } - - // Determine the type of accessory to decide how to pin the view. - private func pin(view: UIView?, accessory: Accessory) { - // Icon and IconAsync types have specific layouts, so we do nothing here. - switch accessory { - case _ as SessionCell.AccessoryConfig.Icon: break - case _ as SessionCell.AccessoryConfig.IconAsync: break - default: - // For all other accessory types, pin the view to the current view. - // This is a generic layout for accessories that don't need a custom position. - view?.pin(to: self) - } - } - + private func layout(view: UIView?, accessory: Accessory) { switch accessory { case let accessory as SessionCell.AccessoryConfig.Icon: @@ -595,6 +581,8 @@ extension SessionCell { let radioView: UIView = radioBorderView.subviews.first else { return } + label.pin(to: self) + label.pin(.top, to: .top, of: self) label.pin(.leading, to: .leading, of: self, withInset: Values.smallSpacing) label.pin(.trailing, to: .leading, of: radioBorderView, withInset: -Values.smallSpacing) @@ -790,6 +778,8 @@ extension SessionCell { minWidthConstraint.isActive = true } + view.pin(.top, to: .top, of: self) + view.pin(.bottom, to: .bottom, of: self) view.pin(.leading, to: .leading, of: self, withInset: Values.smallSpacing) view.pin(.trailing, to: .trailing, of: self, withInset: -Values.smallSpacing) } From b8c4196eadb2c05ecc2032b96dfc3e25b1c16dd8 Mon Sep 17 00:00:00 2001 From: mpretty-cyro <15862619+mpretty-cyro@users.noreply.github.com> Date: Mon, 8 Sep 2025 00:36:44 +0000 Subject: [PATCH 33/66] [Automated] Update translations from Crowdin --- .../Meta/Translations/Localizable.xcstrings | 1673 ++++++++++++++++- 1 file changed, 1671 insertions(+), 2 deletions(-) diff --git a/Session/Meta/Translations/Localizable.xcstrings b/Session/Meta/Translations/Localizable.xcstrings index 11c67635b7..5e0dfa04f5 100644 --- a/Session/Meta/Translations/Localizable.xcstrings +++ b/Session/Meta/Translations/Localizable.xcstrings @@ -20924,6 +20924,12 @@ "appearanceAutoDarkMode" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avto-qaranlıq rejimi" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -30959,6 +30965,12 @@ "appProBadge" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} nişanı" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -63360,6 +63372,30 @@ "value" : "Mesaj göndərmək üçün bu kontaktı əngəldən çıxardın." } }, + "bal" : { + "stringUnit" : { + "state" : "translated", + "value" : "پیغام بھیجنے کے لئے اس رابطہ کو غیر بلاک کریں۔" + } + }, + "be" : { + "stringUnit" : { + "state" : "translated", + "value" : "Разблакуйце гэты кантакт, каб адправіць паведамленне" + } + }, + "bg" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отблокирай този контакт за да изпратиш съобщение" + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "মেসেজ পাঠাতে এই কন্টাক্টটি আনব্লক করুন।" + } + }, "ca" : { "stringUnit" : { "state" : "translated", @@ -63408,6 +63444,12 @@ "value" : "Desbloquea este contacto para enviar mensajes." } }, + "eu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kontaktu hau desblokeatu mezu bat bidaltzeko" + } + }, "fi" : { "stringUnit" : { "state" : "translated", @@ -63426,12 +63468,24 @@ "value" : "कोई संदेश भेजने के लिए इस संपर्क को अनवरोधित करें" } }, + "hr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deblokiraj ovaj kontakt za slanje poruke" + } + }, "hu" : { "stringUnit" : { "state" : "translated", "value" : "Üzenet küldéséhez oldd fel a kontakt letiltását." } }, + "hy-AM" : { + "stringUnit" : { + "state" : "translated", + "value" : "Արգելաբացել այս կոնտակտը հաղորդագրություն ուղարկելու համար" + } + }, "id" : { "stringUnit" : { "state" : "translated", @@ -63486,6 +63540,12 @@ "value" : "Nyahsekat kontak ini untuk menghantar mesej" } }, + "my" : { + "stringUnit" : { + "state" : "translated", + "value" : "မက်ဆေ့ချ် ပို့ရန်အတွက်ဤဆက်သွယ်မှုသို့ ဘလော့ကိုဖြေပါ။" + } + }, "nb" : { "stringUnit" : { "state" : "translated", @@ -63510,6 +63570,12 @@ "value" : "Opphev blokkeringen på denne kontakten for å sende en melding." } }, + "ny" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pokankha Lamulo Llitsa lemba uthenga" + } + }, "pl" : { "stringUnit" : { "state" : "translated", @@ -63540,6 +63606,12 @@ "value" : "Разблокируйте этот контакт, чтобы отправить сообщение" } }, + "sq" : { + "stringUnit" : { + "state" : "translated", + "value" : "Që t’i dërgohet një mesazh, zhbllokojeni këtë kontakt" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", @@ -65030,6 +65102,12 @@ "blockedContactsManageDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Əngəllənmiş kontaktları görün və idarə edin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -78446,6 +78524,12 @@ "callsVoiceAndVideoModalDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Beta zənglərini istifadə edərkən IP-niz zəng tərəfdaşınıza və {session_foundation} serverinə görünür." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -83271,6 +83355,12 @@ "cancelPlan" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Planı ləğv et" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -83282,6 +83372,12 @@ "change" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dəyişdir" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -83772,6 +83868,12 @@ "changePasswordModalDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} üçün parolunuzu dəyişdirin. Daxili olaraq saxlanılmış verilər, yeni parolunuzla təkrar şifrələnəcək." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -113098,6 +113200,12 @@ "contentNotificationDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeni bir mesaj alındıqda daxili bildirişlərdə nümayiş olunacaq məzmunu seçin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -118923,6 +119031,12 @@ "conversationsEnterDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enter və Shift+Enter düymələrinin danışıqlarda necə işləyəcəyini təyin edin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -118934,6 +119048,12 @@ "conversationsEnterNewLine" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "SHIFT + ENTER mesajı göndərir, ENTER yeni sətrə keçir." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -118945,6 +119065,12 @@ "conversationsEnterSends" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER mesajı göndərir, SHIFT + ENTER yeni sətrə keçir." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -120393,6 +120519,12 @@ "conversationsMessageTrimmingTrimCommunitiesDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "2,000-dən çox mesajı olan icmalarda 6 aydan köhnə mesajları avtomatik sil." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -121362,6 +121494,12 @@ "conversationsSendWithEnterKey" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enter ilə göndər" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -121852,6 +121990,12 @@ "conversationsSendWithShiftEnter" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Shift+Enter ilə göndər" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -125413,6 +125557,12 @@ "currentPassword" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hazırkı parol" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -125424,6 +125574,12 @@ "currentPlan" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hazırkı plan" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -125920,6 +126076,12 @@ "darkMode" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Qaranlıq rejim" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -168918,6 +169080,12 @@ "display" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nümayiş" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -187186,6 +187354,12 @@ "enableNotifications" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeni mesaj aldığınız zaman bildirişlər göstərilsin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -187691,6 +187865,12 @@ "enter" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daxil ol" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -190437,6 +190617,12 @@ "feedback" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Əks-əlaqə" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -190448,6 +190634,12 @@ "feedbackDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Qısa anketi dolduraraq {app_name} ilə təcrübənizi paylaşın." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -191417,6 +191609,12 @@ "followSystemSettings" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sistem ayarlarını izlə." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -232304,6 +232502,12 @@ "helpFAQDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ümumi suallara cavab tapmaq üçün {app_name} TVS-yə baxın." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -232788,6 +232992,12 @@ "helpReportABug" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bir xəta bildir" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -234727,6 +234937,12 @@ "helpReportABugExportLogsSaveToDesktopDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu faylı saxlayın, sonra onu {app_name} gəlişdiriciləri ilə paylaşın." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -235217,6 +235433,12 @@ "helpTranslateSessionDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} tətbiqini 80-dən çox dildə tərcümə etməyə kömək edin!" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -236186,6 +236408,12 @@ "hideMenuBarDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sistem menyu çubuğunun görünməsini dəyişdir." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -237477,6 +237705,12 @@ "important" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vacib" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -242032,10 +242266,16 @@ "launchOnStartDescriptionDesktop" : { "extractionState" : "manual", "localizations" : { + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spustit {app_name} automaticky při spuštění počítače." + } + }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Launch Session automatically when your computer starts up." + "value" : "Launch {app_name} automatically when your computer starts up." } } } @@ -242043,6 +242283,12 @@ "launchOnStartDesktop" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Açılışda başlat" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -242054,10 +242300,16 @@ "launchOnStartupDisabledDesktop" : { "extractionState" : "manual", "localizations" : { + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toto nastavení spravuje váš systém s Linuxem. Chcete-li povolit automatické spuštění, přidejte {app_name} do spouštěných aplikací v nastavení systému." + } + }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "This setting is managed by your system on Linux. To enable automatic startup, add Session to your startup applications in system settings." + "value" : "This setting is managed by your system on Linux. To enable automatic startup, add {app_name} to your startup applications in system settings." } } } @@ -252019,6 +252271,12 @@ "links" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keçidlər" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -257790,6 +258048,12 @@ "logs" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Log-lar" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -257962,6 +258226,12 @@ "managePro" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} - idarə et" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -268192,6 +268462,12 @@ "menuBar" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Menyu çubuğu" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -268819,6 +269095,12 @@ "messageCopy" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mesajı kopyala" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -293004,6 +293286,12 @@ "newPassword" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeni parol" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -293494,6 +293782,12 @@ "nextSteps" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Növbəti addımlar" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -299001,6 +299295,12 @@ "notificationDisplay" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bildiriş nümayişi" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -301892,6 +302192,12 @@ "notificationSenderNameAndPreview" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mesajın göndərənin adı və mesaj məzmununun bir önizləməsi nümayiş olunsun." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -301903,6 +302209,12 @@ "notificationSenderNameOnly" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heç bir mesaj məzmunu olmadan yalnız mesajı göndərənin adı nümayiş olunsun." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -303506,6 +303818,12 @@ "notificationsGenericOnly" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mesajı göndərənin adı və ya mesajın məzmunu olmadan ümumi {app_name} bildirişi nümayiş olunsun." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -306840,6 +307158,12 @@ "notificationsMakeSound" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeni mesaj aldığınız zaman bir səs oxudulsun." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -323950,6 +324274,12 @@ "onDevice" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{device_type} cihazınızda" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -323961,6 +324291,12 @@ "onDeviceDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Başda qeydiyyatdan keçdiyiniz {platform_account} hesabına giriş etdiyiniz {device_type} cihazından bu {app_name} hesabını açın. Sonra planınızı {app_pro} ayarları vasitəsilə dəyişdirin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -328301,6 +328637,12 @@ "openStoreWebsite" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_store} veb saytını aç" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -328916,6 +329258,12 @@ "password" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parol" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -329412,6 +329760,12 @@ "passwordChangedDescriptionToast" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parolunuz dəyişdirilib. Lütfən onu güvəndə saxlayın." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -329423,6 +329777,12 @@ "passwordChangeShortDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} kilidini açmaq üçün tələb olunan parolu dəyişdir." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -329919,6 +330279,12 @@ "passwordCreate" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parol yarat" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -333875,6 +334241,12 @@ "passwordNewConfirm" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeni parolu təsdiqlə" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -334365,6 +334737,12 @@ "passwordRemovedDescriptionToast" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parolunuz silinib." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -334376,6 +334754,12 @@ "passwordRemoveShortDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} kilidini açmaq üçün tələb olunan parolu sil" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -334387,6 +334771,12 @@ "passwords" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parollar" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -334877,6 +335267,12 @@ "passwordSetDescriptionToast" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parolunuz təyin edilib. Lütfən onu güvəndə saxlayın." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -334888,6 +335284,12 @@ "passwordSetShortDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Açılışda {app_name} kilidini açmaq üçün parol tələb edilsin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -334899,6 +335301,12 @@ "passwordStrengthCharLength" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "12 xarakterdən uzun" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -334910,6 +335318,12 @@ "passwordStrengthIncludeNumber" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bir rəqəm ehtiva etməlidir" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -334921,6 +335335,12 @@ "passwordStrengthIncludesLowercase" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bir kiçik hərf ehtiva etməlidir" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -334932,6 +335352,12 @@ "passwordStrengthIncludesSymbol" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bir simvol daxildir" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -334943,6 +335369,12 @@ "passwordStrengthIncludesUppercase" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bir böyük hərf ehtiva etməlidir" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -334954,6 +335386,12 @@ "passwordStrengthIndicator" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parol gücü göstəricisi" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -334965,6 +335403,12 @@ "passwordStrengthIndicatorDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güclü bir parol təyin etmək, cihazınız itsə və ya oğurlansa belə mesajlarınızı və qoşmalarınızı qorumağa kömək edir." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -339458,6 +339902,12 @@ "permissionsKeepInSystemTrayDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pəncərəni bağladığınız zaman {app_name} arxaplanda çalışmağa davam edir." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -349013,6 +349463,12 @@ "plusLoadsMore" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Üstəgəl daha çoxu gəlir..." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -349024,6 +349480,12 @@ "plusLoadsMoreDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} üçün yeni özəlliklər tezliklə gəlir. {icon} {pro} Yol Xəritəsində yenilikləri kəşf edin" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -349035,6 +349497,12 @@ "preferences" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tərcihlər" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -349525,6 +349993,12 @@ "previewNotification" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bildirişi önizlə" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -349661,6 +350135,12 @@ "proAllSet" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hər şey hazırdır!" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -349672,6 +350152,12 @@ "proAllSetDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planınız güncəlləndi! Hazırkı {pro} planınız avtomatik olaraq {date} tarixində yeniləndiyi zaman ödəniş haqqı alınacaq." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -350326,6 +350812,12 @@ "proAnimatedDisplayPictures" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Animasiyalı ekran şəkilləri" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -350337,6 +350829,12 @@ "proAnimatedDisplayPicturesDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Animasiyalı GIF və WebP təsvirlərini ekran şəklini olaraq təyin edin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -350479,6 +350977,12 @@ "proAutoRenewTime" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro}, {time} tarixində avto-yenilənir" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -350490,6 +350994,12 @@ "proBadge" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} nişanı" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -350501,6 +351011,12 @@ "proBadges" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nişanlar" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -350512,6 +351028,12 @@ "proBadgesDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekran adınızın yanında eksklüziv bir nişanla {app_name} tətbiqini dəstəklədiyinizi göstərin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -350523,6 +351045,34 @@ "proBadgesSent" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} nişan göndərildi" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} nişan göndərildi" + } + } + } + } + } + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -350556,6 +351106,12 @@ "proBadgeVisible" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} nişanını digər istifadəçilərə göstər" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -350567,6 +351123,12 @@ "proBilledAnnually" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} - illik haqq" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -350578,6 +351140,12 @@ "proBilledMonthly" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} - aylıq haqq" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -350589,6 +351157,12 @@ "proBilledQuarterly" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} - rüblük haqq" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -350999,6 +351573,12 @@ "processingRefundRequest" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_account} geri ödəmə tələbinizi emal edir" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -351010,6 +351590,12 @@ "proDiscountTooltip" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hazırkı planınızda artıq\r\ntam {app_pro} qiymətinin {percent}% endirimi mövcuddur." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -351021,6 +351607,12 @@ "proExpired" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Müddəti bitib" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -351032,6 +351624,12 @@ "proExpiredDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Təəssüf ki, {pro} planınızın müddəti bitib. {app_pro} tətbiqinin eksklüziv imtiyazlarına və özəlliklərinə erişimi davam etdirmək üçün yeniləyin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -351043,6 +351641,12 @@ "proExpiringSoon" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tezliklə bitir" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -351054,6 +351658,12 @@ "proExpiringSoonDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} planınızın müddəti {time} vaxtında bitir. {app_pro} tətbiqinin eksklüziv imtiyazlarına və özəlliklərinə erişimi davam etdirmək üçün planınızı güncəlləyin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -351065,6 +351675,12 @@ "proExpiringTime" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro}, {time} vaxtında başa çatır" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -351076,6 +351692,12 @@ "proFaq" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} TVS" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -351087,6 +351709,12 @@ "proFaqDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} TVS-da tez-tez verilən suallara cavab tapın." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -351765,6 +352393,12 @@ "proFeatures" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} özəllikləri" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -354894,6 +355528,34 @@ "proGroupsUpgraded" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} qrup yüksəldildi" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} qrup yüksəldildi" + } + } + } + } + } + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -354927,6 +355589,12 @@ "proImportantDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri ödəniş tələbi qətidir. Əgər təsdiqlənsə, {pro} planınız dərhal ləğv ediləcək və bütün {pro} özəlliklərinə erişimi itirəcəksiniz." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -355176,6 +355844,12 @@ "proLargerGroups" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daha böyük qruplar" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -355187,6 +355861,12 @@ "proLargerGroupsDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Admin olduğunuz qruplar, avtomatik olaraq 300 üzvü dəstəkləmək üçün təkmilləşdirilir." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -355198,6 +355878,12 @@ "proLongerMessages" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daha uzun mesajlar" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -355209,6 +355895,12 @@ "proLongerMessagesDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bütün danışıqlarda 10,000 xarakterə qədər mesaj göndərə bilərsiniz." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -355220,6 +355912,34 @@ "proLongerMessagesSent" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} daha uzun mesaj göndərildi" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} daha uzun mesaj göndərildi" + } + } + } + } + } + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357465,6 +358185,12 @@ "proPercentOff" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{percent}% endirim" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357476,6 +358202,34 @@ "proPinnedConversations" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} sancılmış danışıq" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} sancılmış danışıq" + } + } + } + } + } + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357509,6 +358263,12 @@ "proPlanActivatedAuto" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planınız aktivdir!

Planınız avtomatik olaraq {date} tarixində başqa bir {current_plan} üçün yenilənəcək. Planınıza edilən güncəlləmələr növbəti {pro} yenilənməsi zamanı qüvvəyə minəcək." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357520,6 +358280,12 @@ "proPlanActivatedAutoShort" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planınız aktivdir!

Planınız avtomatik olaraq {date} tarixində başqa bir {current_plan} üçün yenilənəcək." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357531,6 +358297,12 @@ "proPlanActivatedNotAuto" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planınızın müddəti {date} tarixində bitir.

Eksklüziv Pro özəlliklərinə kəsintisiz erişimi təmin etmək üçün planınızı indi güncəlləyin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357542,6 +358314,12 @@ "proPlanExpireDate" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planınızın müddəti {date} tarixində bitir." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357553,6 +358331,12 @@ "proPlanNotFound" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} planı tapılmadı" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357564,6 +358348,12 @@ "proPlanNotFoundDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hesabınız üçün heç bir aktiv plan tapılmadı. Bunun bir səhv olduğunu düşünürsünüzsə, lütfən kömək üçün {app_name} dəstəyi ilə əlaqə saxlayın." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357575,6 +358365,12 @@ "proPlanPlatformRefund" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Başda {platform_store} Mağazası üzərindən {app_pro} üçün qeydiyyatdan keçdiyinizə görə, geri ödəmə tələbini göndərmək üçün eyni {platform_account} hesabını istifadə etməlisiniz." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357586,6 +358382,12 @@ "proPlanPlatformRefundLong" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Başda {platform_store} Mağazası üzərindən {app_pro} üçün qeydiyyatdan keçdiyinizə görə, geri qaytarma tələbiniz {app_name} Dəstək komandası tərəfindən icra olunacaq.

Aşağıdakı düyməyə basaraq və geri ödəniş formunu dolduraraq geri ödəmə tələbinizi göndərin.

{app_name} Dəstək komandası, geri ödəmə tələblərini adətən 24-72 saat ərzində emal edir, yüksək tələb həcminə görə bu proses daha uzun çəkə bilər." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357597,6 +358399,12 @@ "proPlanRecover" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} planını geri qaytar" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357608,6 +358416,12 @@ "proPlanRenew" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} planını yenilə" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357619,6 +358433,12 @@ "proPlanRenewDesktop" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hazırda, {pro} planları, yalnızca {platform_store} və {platform_store} Mağazaları vasitəsilə satın alına və yenilənə bilər. {app_name} Masaüstü istifadə etdiyinizə görə planınızı burada yeniləyə bilməzsiniz.

{app_pro} gəlişdiriciləri, istifadəçilərin {pro} planlarını {platform_store} və {platform_store} Mağazalarından kənarda almağına imkan verəcək alternativ ödəniş variantları üzərində ciddi şəkildə çalışırlar. {pro} Yol Xəritəsi" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357630,6 +358450,12 @@ "proPlanRenewDesktopLinked" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_store} və ya {platform_store} Mağazaları vasitəsilə planınızı {app_name} quraşdırılmış və əlaqələndirilmiş cihazda {app_pro} ayarlarında yeniləyin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357641,6 +358467,12 @@ "proPlanRenewDesktopStore" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} üçün qeydiyyatdan keçdiyiniz {platform_account} hesabınızla {platform_store} veb saytında planınızı yeniləyin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357652,6 +358484,12 @@ "proPlanRenewStart" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güclü {app_pro} özəlliklərini yenidən istifadə etməyə başlamaq üçün {app_pro} planınızı yeniləyin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357663,6 +358501,12 @@ "proPlanRenewSupport" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planınız yeniləndi! {network_name} dəstək verdiyiniz üçün təşəkkürlər." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357674,6 +358518,12 @@ "proPlanRestored" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} planı bərpa edildi" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357685,6 +358535,12 @@ "proPlanRestoredDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} üçün yararlı bir plan aşkarlandı və {pro} statusunuz bərpa edildi!" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357696,6 +358552,12 @@ "proPlanSignUp" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Başda {platform_store} Mağazası üzərindən {app_pro} üçün qeydiyyatdan keçdiyinizə görə planınızı həmin {platform_account} vasitəsilə güncəlləməlisiniz." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357707,6 +358569,12 @@ "proPriceOneMonth" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 ay - {monthly_price}/ay" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357718,6 +358586,12 @@ "proPriceThreeMonths" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "3 ay - {monthly_price}/ay" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357729,6 +358603,12 @@ "proPriceTwelveMonths" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "12 ay - {monthly_price}/ay" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357740,6 +358620,12 @@ "proRefundDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Getməyinizə məyus olduq. Geri ödəmə tələb etməzdən əvvəl bilməli olduğunuz şeylər." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357751,6 +358637,12 @@ "proRefunding" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} geri ödəməsi" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357762,6 +358654,12 @@ "proRefundingDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planları üçün geri ödəmələr yalnız {platform_store} Mağazası vasitəsilə {platform_account} tərəfindən həyata keçirilir.

{platform_account} geri ödəniş siyasətlərinə əsasən, {app_name} gəlişdiriciləri, geri ödəniş tələblərinin nəticəsinə təsir edə bilməz. Bu, tələbin qəbul olunub-olunmaması ilə yanaşı, tam və ya qismən geri ödənişin verilib-verilməməsini də əhatə edir." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357773,6 +358671,12 @@ "proRefundNextSteps" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_account} hazırda geri ödəniş tələbinizi emal edir. Bu, adətən 24-48 saat çəkir. Onların qərarından asılı olaraq, {app_name} tətbiqində {pro} statusunuzun dəyişdiyini görə bilərsiniz." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357784,6 +358688,12 @@ "proRefundRequestSessionSupport" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri qaytarma tələbiniz {app_name} Dəstək komandası tərəfindən icra olunacaq.

Aşağıdakı düyməyə basaraq və geri ödəniş formunu dolduraraq geri ödəniş tələbinizi göndərin.

{app_name} Dəstək komandası, geri ödəniş tələblərini adətən 24-72 saat ərzində emal edir, yüksək tələb həcminə görə bu proses daha uzun çəkə bilər." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357795,6 +358705,12 @@ "proRefundRequestStorePolicies" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri ödəniş tələbiniz yalnız {platform_account} veb saytında {platform_account} hesabı üzərindən icra olunacaq.

{platform_account} geri ödəniş siyasətlərinə əsasən, {app_name} gəlişdiriciləri, geri ödəniş tələblərinin nəticəsinə təsir edə bilməz. Bu, tələbin qəbul olunub-olunmaması ilə yanaşı, tam və ya qismən geri ödənişin verilib-verilməməsini də əhatə edir." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357806,6 +358722,12 @@ "proRefundSupport" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri ödəmə tələbinizlə bağlı daha çox güncəlləmə üçün lütfən {platform_account} ilə əlaqə saxlayın. {platform_account} geri ödəniş siyasətlərinə əsasən, {app_name} gəlişdiriciləri, geri ödəniş tələblərinin nəticəsinə təsir edə bilməz.

{platform_store} Geri ödəmə dəstəyi" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357817,6 +358739,12 @@ "proRequestedRefund" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri ödəmə tələb edildi" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357965,6 +358893,12 @@ "proSettings" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} ayarları" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357976,6 +358910,12 @@ "proStats" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} statistikalarınız" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357987,6 +358927,12 @@ "proStatsTooltip" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} statistikaları, bu cihazdakı istifadəni əks-etdirir və əlaqələndirilmiş cihazlarda fərqli görünə bilər." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357998,6 +358944,12 @@ "proSupportDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} planınızla bağlı kömək lazımdır? Dəstək komandamıza müraciət edin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -358009,6 +358961,12 @@ "proTosPrivacy" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güncəlləyərək, {app_pro} Xidmət Şərtləri {icon} və Məxfilik Siyasəti {icon} ilə razılaşırsınız" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -358020,6 +358978,12 @@ "proUnlimitedPins" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limitsiz sancma" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -358031,6 +358995,12 @@ "proUnlimitedPinsDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limitsiz sancılmış danışıqla bütün söhbətlərinizi təşkil edin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -358042,6 +359012,12 @@ "proUpdatePlanDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hazırda {current_plan} Planı üzərindəsiniz. {selected_plan} Planınana keçmək istədiyinizə əminsiniz?

Güncəlləsəniz, planınız {date} tarixində əlavə {selected_plan} {pro} erişimi üçün avtomatik yenilənəcək." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -358053,6 +359029,12 @@ "proUpdatePlanExpireDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Planınız {date} tarixində bitəcək.

Güncəlləsəniz, planınız {date} tarixində əlavə {selected_plan} Pro erişimi üçün avtomatik olaraq yenilənəcək." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -366256,6 +367238,12 @@ "recoveryPasswordDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hesabınızı yeni cihazlara yükləmək üçün geri qaytarma parolunuzu istifadə edin.

Geri qaytarma parolunuz olmadan hesabınız geri qaytarıla bilməz. Parolu təhlükəsiz və etibarlı yerdə saxladığınıza əmin olun və heç kəslə paylaşmayın." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -370272,6 +371260,12 @@ "recoveryPasswordHidePermanentlyDescription2" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri qaytarma parolunuzu bu cihazdan həmişəlik gizlətmək istədiyinizə əminsiniz?

Bunun geri dönüşü yoxdur." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -371720,6 +372714,12 @@ "recoveryPasswordView" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri qaytarma paroluna bax" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -371731,6 +372731,12 @@ "recoveryPasswordVisibility" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri qaytarma parolu görünməsi" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -372879,6 +373885,12 @@ "refundPlanNonOriginatorApple" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Başda fərqli {platform_account} vasitəsilə {app_pro} üçün qeydiyyatdan keçdiyinizə görə planınızı həmin {platform_account} vasitəsilə güncəlləməlisiniz." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -374428,6 +375440,12 @@ "removePasswordModalDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} üçün hazırkı parolunuzu silin. Daxili olaraq saxlanılmış verilər, cihazınızda saxlanılan təsadüfi yaradılmış açarla təkrar şifrələnəcək." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -374439,6 +375457,12 @@ "renew" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yenilə" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -374929,6 +375953,12 @@ "requestRefund" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri ödəmə tələb et" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -381414,6 +382444,28 @@ } } }, + "screenshotProtectionDescriptionDesktop" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Conceal the {app_name} window in screenshots taken on this device." + } + } + } + }, + "screenshotProtectionDesktop" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Screenshot Protection" + } + } + } + }, "screenshotTaken" : { "extractionState" : "manual", "localizations" : { @@ -395972,6 +397024,12 @@ "sessionProBeta" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} Beta" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -397581,6 +398639,12 @@ "setPasswordModalDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} üçün bir parol təyin edin. Daxili olaraq saxlanılmış verilər, bu parolla şifrələnəcək. {app_name} tətbiqini hər başlatdıqda, sizdən bu parolu daxil etməyiniz istənəcək." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -397592,6 +398656,12 @@ "settingsCannotChangeDesktop" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ayar güncəllənə bilmir" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -398079,9 +399149,494 @@ } } }, + "settingsScreenSecurityDesktop" : { + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skermveiligheid" + } + }, + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "أمان الشاشة" + } + }, + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekran güvənliyi" + } + }, + "bal" : { + "stringUnit" : { + "state" : "translated", + "value" : "سکرین سیکورٹی" + } + }, + "be" : { + "stringUnit" : { + "state" : "translated", + "value" : "Бяспека экрану" + } + }, + "bg" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сигурност на екрана" + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "স্ক্রীন সিকিউরিটি" + } + }, + "ca" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seguretat de pantalla" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zabezpečení obrazovky" + } + }, + "cy" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diogelu'r sgrin" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skærmsikkerhed" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bildschirmschutz" + } + }, + "el" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ασφάλεια Οθόνης" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Screen Security" + } + }, + "eo" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekrana sekurigo" + } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seguridad de pantalla" + } + }, + "es-ES" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seguridad de pantalla" + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekraani turvalisus" + } + }, + "eu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pantailaren Segurtasuna" + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "امنیت صفحه نمایش" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Näytön suojaus" + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "Screen Security" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sécurité d'écran" + } + }, + "gl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seguranza da pantalla" + } + }, + "ha" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tsaron Allo" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אבטחת מסך" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "स्क्रीन सुरक्षा" + } + }, + "hr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sigurnost zaslona" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Képernyőbiztonság" + } + }, + "hy-AM" : { + "stringUnit" : { + "state" : "translated", + "value" : "Էկրանի անվտանգություն" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keamanan Layar" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sicurezza Schermo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "スクリーンセキュリティ" + } + }, + "ka" : { + "stringUnit" : { + "state" : "translated", + "value" : "ეკრანის დაცვა" + } + }, + "km" : { + "stringUnit" : { + "state" : "translated", + "value" : "សុវត្ថិភាពអេក្រង់" + } + }, + "kn" : { + "stringUnit" : { + "state" : "translated", + "value" : "ಪರದೆಯ ಭದ್ರತೆ" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "화면 보안" + } + }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "پاراستنی پردە" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parastina Ekranê" + } + }, + "lg" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obukuumi bwa ekikola ekiriko akabonero" + } + }, + "lt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekrano saugumas" + } + }, + "lv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekrāna drošība" + } + }, + "mk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Екранска Безбедност" + } + }, + "mn" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дэлгэцийн аюулгүй байдал" + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keselamatan Skrin" + } + }, + "my" : { + "stringUnit" : { + "state" : "translated", + "value" : "မျက်နှာပြင် လုံခြုံရေး" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skjermsikkerhet" + } + }, + "nb-NO" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skjermsikkerhet" + } + }, + "ne-NP" : { + "stringUnit" : { + "state" : "translated", + "value" : "स्क्रीन सुरक्षा" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scherm beveiliging" + } + }, + "nn-NO" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skjermtryggleik" + } + }, + "ny" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rikuripa pakallayachina" + } + }, + "pa-IN" : { + "stringUnit" : { + "state" : "translated", + "value" : "ਸਕ੍ਰੀਨ ਸੁਰੱਖਿਆ" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ochrona ekranu" + } + }, + "ps" : { + "stringUnit" : { + "state" : "translated", + "value" : "د سکرین امنیت" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Segurança de Tela" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Segurança de ecrã" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Securitate ecran" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Защита экрана" + } + }, + "sh" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sigurnost ekrana" + } + }, + "si-LK" : { + "stringUnit" : { + "state" : "translated", + "value" : "තිර ආරක්ෂාව" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zabezpečenie obrazovky" + } + }, + "sl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Varnost zaslona" + } + }, + "sq" : { + "stringUnit" : { + "state" : "translated", + "value" : "Siguri ekrani" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Безбедност екрана" + } + }, + "sr-Latn" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bezbednost ekrana" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skärmsäkerhet" + } + }, + "sw" : { + "stringUnit" : { + "state" : "translated", + "value" : "Usalama wa Skrini" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "திரை பாதுகாப்பு" + } + }, + "te" : { + "stringUnit" : { + "state" : "translated", + "value" : "స్క్రీన్ భద్రత" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ความปลอดภัยหน้าจอ" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekran Güvenliği" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Безпека перегляду" + } + }, + "ur-IN" : { + "stringUnit" : { + "state" : "translated", + "value" : "سکرین سیکیورٹی" + } + }, + "uz" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekran xavfsizligi" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "An ninh màn hình" + } + }, + "xh" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ukhuseleko lweSikrini" + } + }, + "zh-CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "屏幕安全性" + } + }, + "zh-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "螢幕安全性" + } + } + } + }, "settingsStartCategoryDesktop" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Açılış" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -402390,6 +403945,12 @@ "spellChecker" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yazı yoxlanışı" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -402880,6 +404441,12 @@ "strength" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gücü" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -402891,6 +404458,12 @@ "supportDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Problemlə üzləşmisiniz? Kömək məqalələrini oxuyun, ya da {app_name} Dəstək ilə bir sorğu açın." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -405482,6 +407055,12 @@ "themePreview" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tema önizləməsi" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -405493,6 +407072,12 @@ "theReturn" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Qayıt" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -405766,6 +407351,12 @@ "translate" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tərcümə et" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -405777,6 +407368,12 @@ "tray" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sini" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -412499,6 +414096,12 @@ "updatePlan" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Planı güncəllə" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -412510,6 +414113,12 @@ "updatePlanTwo" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Planınızı güncəlləməyin iki yolu var:" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -412521,6 +414130,12 @@ "updateProfileInformation" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil məlumatlarını güncəllə" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -412532,6 +414147,12 @@ "updateProfileInformationDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekran adınız və ekran şəkliniz bütün danışıqlarda görünür." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -413022,6 +414643,12 @@ "updates" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güncəlləmələr" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -413997,6 +415624,12 @@ "updating" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güncəllənir..." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -416540,6 +418173,12 @@ "urlOpenDescriptionAlternative" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keçidlər, brauzerinizdə açılacaq." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -417030,6 +418669,12 @@ "viaStoreWebsite" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_store} veb saytı vasitəsilə" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -417041,6 +418686,12 @@ "viaStoreWebsiteDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Qeydiyyatdan keçərkən istifadə etdiyiniz {platform_account} hesabı ilə {platform_store} veb saytı üzərindən planınızı dəyişdirin." + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -421685,6 +423336,12 @@ "yourRecoveryPassword" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri qaytarma parolunuz" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -421696,6 +423353,12 @@ "zoomFactor" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Böyütmə amili" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -421707,6 +423370,12 @@ "zoomFactorDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mətnin və vizual elementlərin ölçüsünü ayarla." + } + }, "en" : { "stringUnit" : { "state" : "translated", From 5109ec037d70cfd3568e7a9c37c385afa0b96daf Mon Sep 17 00:00:00 2001 From: mikoldin Date: Mon, 8 Sep 2025 08:14:46 +0800 Subject: [PATCH 34/66] Updated font for call cells Adjusted call vertical padding --- .../Message Cells/CallMessageCell.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Session/Conversations/Message Cells/CallMessageCell.swift b/Session/Conversations/Message Cells/CallMessageCell.swift index 13566d5769..78b834ff6c 100644 --- a/Session/Conversations/Message Cells/CallMessageCell.swift +++ b/Session/Conversations/Message Cells/CallMessageCell.swift @@ -10,6 +10,8 @@ final class CallMessageCell: MessageCell { private static let iconSize: CGFloat = 16 private static let timerViewSize: CGFloat = 16 private static let inset = Values.mediumSpacing + private static let verticalInset = Values.mediumSmallSpacing // Added 4pt vertical to align margins with other bubbles with author padding in `VisibleMessageCell` + private static let horizontalInset = Values.mediumSmallSpacing private static let margin = UIScreen.main.bounds.width * 0.1 private var isHandlingLongPress: Bool = false @@ -48,7 +50,7 @@ final class CallMessageCell: MessageCell { private lazy var label: UILabel = { let result: UILabel = UILabel() - result.font = .boldSystemFont(ofSize: Values.mediumFontSize) + result.font = .systemFont(ofSize: Values.mediumFontSize) result.themeTextColor = .textPrimary result.textAlignment = .center result.lineBreakMode = .byWordWrapping @@ -63,28 +65,28 @@ final class CallMessageCell: MessageCell { result.layer.cornerRadius = 18 result.addSubview(label) - label.pin(.top, to: .top, of: result, withInset: CallMessageCell.inset) + label.pin(.top, to: .top, of: result, withInset: CallMessageCell.verticalInset) label.pin( .left, to: .left, of: result, - withInset: ((CallMessageCell.inset * 2) + infoImageView.bounds.size.width) + withInset: ((CallMessageCell.horizontalInset * 2) + infoImageView.bounds.size.width) ) label.pin( .right, to: .right, of: result, - withInset: -((CallMessageCell.inset * 2) + infoImageView.bounds.size.width) + withInset: -((CallMessageCell.horizontalInset * 2) + infoImageView.bounds.size.width) ) - label.pin(.bottom, to: .bottom, of: result, withInset: -CallMessageCell.inset) + label.pin(.bottom, to: .bottom, of: result, withInset: -CallMessageCell.verticalInset) result.addSubview(iconImageView) iconImageView.center(.vertical, in: result) - iconImageView.pin(.left, to: .left, of: result, withInset: CallMessageCell.inset) + iconImageView.pin(.left, to: .left, of: result, withInset: CallMessageCell.horizontalInset) result.addSubview(infoImageView) infoImageView.center(.vertical, in: result) - infoImageView.pin(.right, to: .right, of: result, withInset: -CallMessageCell.inset) + infoImageView.pin(.right, to: .right, of: result, withInset: -CallMessageCell.horizontalInset) return result }() From 89832abf055e9aa492841ccfc8176c220d2b0def Mon Sep 17 00:00:00 2001 From: mikoldin Date: Mon, 8 Sep 2025 09:56:43 +0800 Subject: [PATCH 35/66] Remove extra 4pt vertical padding on call cell --- Session/Conversations/Message Cells/CallMessageCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Conversations/Message Cells/CallMessageCell.swift b/Session/Conversations/Message Cells/CallMessageCell.swift index 78b834ff6c..de80fef7c1 100644 --- a/Session/Conversations/Message Cells/CallMessageCell.swift +++ b/Session/Conversations/Message Cells/CallMessageCell.swift @@ -10,7 +10,7 @@ final class CallMessageCell: MessageCell { private static let iconSize: CGFloat = 16 private static let timerViewSize: CGFloat = 16 private static let inset = Values.mediumSpacing - private static let verticalInset = Values.mediumSmallSpacing // Added 4pt vertical to align margins with other bubbles with author padding in `VisibleMessageCell` + private static let verticalInset = Values.smallSpacing private static let horizontalInset = Values.mediumSmallSpacing private static let margin = UIScreen.main.bounds.width * 0.1 From aff16e99c603ce851f75c90453aa924c6f646680 Mon Sep 17 00:00:00 2001 From: mikoldin Date: Tue, 9 Sep 2025 10:42:55 +0800 Subject: [PATCH 36/66] Fix failing test due to `unexpected ',' separator` --- Session/Shared/Types/SessionCell+Accessory.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Session/Shared/Types/SessionCell+Accessory.swift b/Session/Shared/Types/SessionCell+Accessory.swift index f27e6e19db..887a554092 100644 --- a/Session/Shared/Types/SessionCell+Accessory.swift +++ b/Session/Shared/Types/SessionCell+Accessory.swift @@ -41,7 +41,7 @@ public extension SessionCell.Accessory { customTint: ThemeValue? = nil, shouldFill: Bool = false, pinEdges: [UIView.HorizontalEdge] = [.leading, .trailing], - accessibility: Accessibility? = nil, + accessibility: Accessibility? = nil ) -> SessionCell.Accessory { return SessionCell.AccessoryConfig.Icon( icon: icon, @@ -60,7 +60,7 @@ public extension SessionCell.Accessory { customTint: ThemeValue? = nil, shouldFill: Bool = false, pinEdges: [UIView.HorizontalEdge] = [.leading, .trailing], - accessibility: Accessibility? = nil, + accessibility: Accessibility? = nil ) -> SessionCell.Accessory { return SessionCell.AccessoryConfig.Icon( icon: nil, From 84ed82760f857e69b82bb21b813505dac2d78fc4 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 9 Sep 2025 12:45:02 +1000 Subject: [PATCH 37/66] Fixed a layout issue, removed unused code --- Session.xcodeproj/project.pbxproj | 4 - .../Closed Groups/EditGroupViewModel.swift | 3 +- .../Settings/PrivacySettingsViewModel.swift | 1 - Session/Settings/SettingsViewModel.swift | 3 +- .../Shared/SessionTableViewController.swift | 43 -------- Session/Shared/Types/EditableState.swift | 76 ------------- .../Shared/Types/SessionCell+Styling.swift | 2 - .../Views/SessionCell+AccessoryView.swift | 19 ++-- Session/Shared/Views/SessionCell.swift | 102 ------------------ .../SessionHighlightingBackgroundLabel.swift | 5 + 10 files changed, 18 insertions(+), 240 deletions(-) delete mode 100644 Session/Shared/Types/EditableState.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index c3c86fd017..6e907cba39 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -466,7 +466,6 @@ FD10AF122AF85D11007709E5 /* Feature+ServiceNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD10AF112AF85D11007709E5 /* Feature+ServiceNetwork.swift */; }; FD11E22D2CA4D12C001BAF58 /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = FD2286782C38D4FF00BC06F7 /* DifferenceKit */; }; FD11E22E2CA4D12C001BAF58 /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = FDEF57292C3CF50B00131302 /* WebRTC */; }; - FD12A83D2AD63BCC00EEBA0D /* EditableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD12A83C2AD63BCC00EEBA0D /* EditableState.swift */; }; FD12A83F2AD63BDF00EEBA0D /* Navigatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD12A83E2AD63BDF00EEBA0D /* Navigatable.swift */; }; FD12A8412AD63BEA00EEBA0D /* NavigatableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD12A8402AD63BEA00EEBA0D /* NavigatableState.swift */; }; FD12A8432AD63BF600EEBA0D /* ObservableTableSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD12A8422AD63BF600EEBA0D /* ObservableTableSource.swift */; }; @@ -1841,7 +1840,6 @@ FD10AF0B2AF32B9A007709E5 /* SessionListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionListViewModel.swift; sourceTree = ""; }; FD10AF112AF85D11007709E5 /* Feature+ServiceNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Feature+ServiceNetwork.swift"; sourceTree = ""; }; FD11E22F2CA4F498001BAF58 /* DestinationSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestinationSpec.swift; sourceTree = ""; }; - FD12A83C2AD63BCC00EEBA0D /* EditableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableState.swift; sourceTree = ""; }; FD12A83E2AD63BDF00EEBA0D /* Navigatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Navigatable.swift; sourceTree = ""; }; FD12A8402AD63BEA00EEBA0D /* NavigatableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatableState.swift; sourceTree = ""; }; FD12A8422AD63BF600EEBA0D /* ObservableTableSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableTableSource.swift; sourceTree = ""; }; @@ -4455,7 +4453,6 @@ isa = PBXGroup; children = ( FD71164928E3EA5B00B47552 /* DismissType.swift */, - FD12A83C2AD63BCC00EEBA0D /* EditableState.swift */, FD12A83E2AD63BDF00EEBA0D /* Navigatable.swift */, FD12A8402AD63BEA00EEBA0D /* NavigatableState.swift */, FD12A8422AD63BF600EEBA0D /* ObservableTableSource.swift */, @@ -6740,7 +6737,6 @@ FDEF57222C3CF03D00131302 /* (null) in Sources */, 7BA37AFB2AEB64CA002438F8 /* DisappearingMessageTimerView.swift in Sources */, FD12A8492AD63C4700EEBA0D /* SessionNavItem.swift in Sources */, - FD12A83D2AD63BCC00EEBA0D /* EditableState.swift in Sources */, FD37EA0528AA00C1003AE748 /* NotificationSettingsViewModel.swift in Sources */, C328255225CA64470062D0A7 /* ContextMenuVC+ActionView.swift in Sources */, C3548F0824456AB6009433A8 /* UIView+Wrapping.swift in Sources */, diff --git a/Session/Closed Groups/EditGroupViewModel.swift b/Session/Closed Groups/EditGroupViewModel.swift index 01916f533c..e21687aed1 100644 --- a/Session/Closed Groups/EditGroupViewModel.swift +++ b/Session/Closed Groups/EditGroupViewModel.swift @@ -10,10 +10,9 @@ import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit -class EditGroupViewModel: SessionTableViewModel, NavigatableStateHolder, EditableStateHolder, ObservableTableSource { +class EditGroupViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource { public let dependencies: Dependencies public let navigatableState: NavigatableState = NavigatableState() - public let editableState: EditableState = EditableState() public let state: TableDataState = TableDataState() public let observableState: ObservableTableSourceState = ObservableTableSourceState() private let selectedIdsSubject: CurrentValueSubject<(name: String, ids: Set), Never> = CurrentValueSubject(("", [])) diff --git a/Session/Settings/PrivacySettingsViewModel.swift b/Session/Settings/PrivacySettingsViewModel.swift index 86f4dc37b7..d3d6b80444 100644 --- a/Session/Settings/PrivacySettingsViewModel.swift +++ b/Session/Settings/PrivacySettingsViewModel.swift @@ -12,7 +12,6 @@ import SessionUtilitiesKit class PrivacySettingsViewModel: SessionTableViewModel, NavigationItemSource, NavigatableStateHolder, ObservableTableSource { public let dependencies: Dependencies public let navigatableState: NavigatableState = NavigatableState() - public let editableState: EditableState = EditableState() public let state: TableDataState = TableDataState() public let observableState: ObservableTableSourceState = ObservableTableSourceState() private let shouldShowCloseButton: Bool diff --git a/Session/Settings/SettingsViewModel.swift b/Session/Settings/SettingsViewModel.swift index 2220e6279f..f8fa6a6a42 100644 --- a/Session/Settings/SettingsViewModel.swift +++ b/Session/Settings/SettingsViewModel.swift @@ -285,8 +285,7 @@ class SettingsViewModel: SessionTableViewModel, NavigationItemSource, Navigatabl title: SessionCell.TextInfo( state.profile.displayName(), font: .titleLarge, - alignment: .center, - interaction: .editable + alignment: .center ), trailingAccessory: .icon( .pencil, diff --git a/Session/Shared/SessionTableViewController.swift b/Session/Shared/SessionTableViewController.swift index 439421c5e3..8e0119bd9a 100644 --- a/Session/Shared/SessionTableViewController.swift +++ b/Session/Shared/SessionTableViewController.swift @@ -362,34 +362,6 @@ class SessionTableViewController: BaseVC, UITableViewDataSource, UITa disposables: &disposables ) - (viewModel as? ErasedEditableStateHolder)?.isEditing - .receive(on: DispatchQueue.main) - .sink { [weak self, weak tableView] isEditing in - UIView.animate(withDuration: 0.25) { - self?.setEditing(isEditing, animated: true) - - tableView?.visibleCells - .compactMap { $0 as? SessionCell } - .filter { $0.interactionMode == .editable || $0.interactionMode == .alwaysEditing } - .enumerated() - .forEach { index, cell in - cell.update( - isEditing: (isEditing || cell.interactionMode == .alwaysEditing), - becomeFirstResponder: ( - isEditing && - index == 0 && - cell.interactionMode != .alwaysEditing - ), - animated: true - ) - } - - tableView?.beginUpdates() - tableView?.endUpdates() - } - } - .store(in: &disposables) - viewModel.bannerInfo .receive(on: DispatchQueue.main) .sink { [weak self] info in @@ -489,21 +461,6 @@ class SessionTableViewController: BaseVC, UITableViewDataSource, UITa }, using: viewModel.dependencies ) - cell.update( - isEditing: (self.isEditing || (info.title?.interaction == .alwaysEditing)), - becomeFirstResponder: false, - animated: false - ) - - switch viewModel { - case let editableStateHolder as ErasedEditableStateHolder: - cell.textPublisher - .sink(receiveValue: { [weak editableStateHolder] text in - editableStateHolder?.textChanged(text, for: info.id) - }) - .store(in: &cell.disposables) - default: break - } case (let cell as FullConversationCell, let threadInfo as SessionCell.Info): cell.accessibilityIdentifier = info.accessibility?.identifier diff --git a/Session/Shared/Types/EditableState.swift b/Session/Shared/Types/EditableState.swift deleted file mode 100644 index 873e9da2b5..0000000000 --- a/Session/Shared/Types/EditableState.swift +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import Combine -import DifferenceKit -import SessionUtilitiesKit - -// MARK: - EditableStateHolder - -public protocol EditableStateHolder: AnyObject, TableData, ErasedEditableStateHolder { - var editableState: EditableState { get } -} - -public extension EditableStateHolder { - var textChanged: AnyPublisher<(text: String?, item: TableItem), Never> { editableState.textChanged } - - func setIsEditing(_ isEditing: Bool) { - editableState._isEditing.send(isEditing) - } - - func textChanged(_ text: String?, for item: TableItem) { - editableState._textChanged.send((text, item)) - } -} - -// MARK: - ErasedEditableStateHolder - -public protocol ErasedEditableStateHolder: AnyObject { - var isEditing: AnyPublisher { get } - - func setIsEditing(_ isEditing: Bool) - func textChanged(_ text: String?, for item: Item) -} - -public extension ErasedEditableStateHolder { - var isEditing: AnyPublisher { Just(false).eraseToAnyPublisher() } - - func setIsEditing(_ isEditing: Bool) {} - func textChanged(_ text: String?, for item: Item) {} -} - -public extension ErasedEditableStateHolder where Self: EditableStateHolder { - var isEditing: AnyPublisher { editableState.isEditing } - - func setIsEditing(_ isEditing: Bool) { - editableState._isEditing.send(isEditing) - } - - func textChanged(_ text: String?, for item: Item) { - guard let convertedItem: TableItem = item as? TableItem else { return } - - editableState._textChanged.send((text, convertedItem)) - } -} - -// MARK: - EditableState - -public struct EditableState { - let isEditing: AnyPublisher - let textChanged: AnyPublisher<(text: String?, item: TableItem), Never> - - // MARK: - Internal Variables - - fileprivate let _isEditing: CurrentValueSubject = CurrentValueSubject(false) - fileprivate let _textChanged: PassthroughSubject<(text: String?, item: TableItem), Never> = PassthroughSubject() - - // MARK: - Initialization - - init() { - self.isEditing = _isEditing - .removeDuplicates() - .shareReplay(1) - self.textChanged = _textChanged - .eraseToAnyPublisher() - } -} diff --git a/Session/Shared/Types/SessionCell+Styling.swift b/Session/Shared/Types/SessionCell+Styling.swift index a83a09a0da..8df10aa317 100644 --- a/Session/Shared/Types/SessionCell+Styling.swift +++ b/Session/Shared/Types/SessionCell+Styling.swift @@ -9,9 +9,7 @@ public extension SessionCell { struct TextInfo: Hashable, Equatable { public enum Interaction: Hashable, Equatable { case none - case editable case copy - case alwaysEditing case expandable } diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index 8d7c9ee551..5b16c44ffc 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -55,7 +55,6 @@ extension SessionCell { accessory: accessory, tintColor: tintColor, isEnabled: isEnabled, - maxContentWidth: maxContentWidth, using: dependencies ) return @@ -82,7 +81,6 @@ extension SessionCell { accessory: accessory, tintColor: tintColor, isEnabled: isEnabled, - maxContentWidth: maxContentWidth, using: dependencies ) @@ -163,11 +161,13 @@ extension SessionCell { return createIconView(using: dependencies) case is SessionCell.AccessoryConfig.Toggle: return createToggleView() - case is SessionCell.AccessoryConfig.DropDown: return createDropDownView() + case is SessionCell.AccessoryConfig.DropDown: + return createDropDownView(maxContentWidth: maxContentWidth) + case is SessionCell.AccessoryConfig.Radio: return createRadioView() case is SessionCell.AccessoryConfig.HighlightingBackgroundLabel: - return createHighlightingBackgroundLabelView() + return createHighlightingBackgroundLabelView(maxContentWidth: maxContentWidth) case is SessionCell.AccessoryConfig.HighlightingBackgroundLabelAndRadio: return createHighlightingBackgroundLabelAndRadioView() @@ -227,7 +227,6 @@ extension SessionCell { accessory: Accessory, tintColor: ThemeValue, isEnabled: Bool, - maxContentWidth: CGFloat, using dependencies: Dependencies ) { switch accessory { @@ -377,7 +376,7 @@ extension SessionCell { // MARK: -- DropDown - private func createDropDownView() -> UIView { + private func createDropDownView(maxContentWidth: CGFloat) -> UIView { let result: UIStackView = UIStackView() result.translatesAutoresizingMaskIntoConstraints = false result.axis = .horizontal @@ -397,6 +396,7 @@ extension SessionCell { label.themeTextColor = .textPrimary label.setContentHugging(to: .required) label.setCompressionResistance(to: .required) + label.preferredMaxLayoutWidth = (maxContentWidth * 0.4) /// Limit to 40% of content width label.numberOfLines = 0 result.addArrangedSubview(imageView) @@ -511,8 +511,11 @@ extension SessionCell { // MARK: -- HighlightingBackgroundLabel - private func createHighlightingBackgroundLabelView() -> UIView { - return SessionHighlightingBackgroundLabel() + private func createHighlightingBackgroundLabelView(maxContentWidth: CGFloat) -> UIView { + let result: SessionHighlightingBackgroundLabel = SessionHighlightingBackgroundLabel() + result.preferredMaxLayoutWidth = (maxContentWidth * 0.4) /// Limit to 40% of content width + + return result } private func layoutHighlightingBackgroundLabelView(_ view: UIView?) { diff --git a/Session/Shared/Views/SessionCell.swift b/Session/Shared/Views/SessionCell.swift index 47bdebb394..cb1bcdfce2 100644 --- a/Session/Shared/Views/SessionCell.swift +++ b/Session/Shared/Views/SessionCell.swift @@ -10,7 +10,6 @@ import SessionUtilitiesKit public class SessionCell: UITableViewCell { public static let cornerRadius: CGFloat = 17 - private var isEditingTitle = false public private(set) var interactionMode: SessionCell.TextInfo.Interaction = .none public var lastTouchLocation: UITouch? private var shouldHighlightTitle: Bool = true @@ -34,10 +33,6 @@ public class SessionCell: UITableViewCell { private lazy var contentStackViewHorizontalCenterConstraint: NSLayoutConstraint = contentStackView.center(.horizontal, in: cellBackgroundView) private lazy var contentStackViewWidthConstraint: NSLayoutConstraint = contentStackView.set(.width, lessThanOrEqualTo: .width, of: cellBackgroundView) private lazy var leadingAccessoryFillConstraint: NSLayoutConstraint = contentStackView.set(.height, to: .height, of: leadingAccessoryView) - private lazy var titleTextFieldLeadingConstraint: NSLayoutConstraint = titleTextField.pin(.leading, to: .leading, of: cellBackgroundView) - private lazy var titleTextFieldTrailingConstraint: NSLayoutConstraint = titleTextField.pin(.trailing, to: .trailing, of: cellBackgroundView) - private lazy var titleMinHeightConstraint: NSLayoutConstraint = titleStackView.heightAnchor - .constraint(greaterThanOrEqualTo: titleTextField.heightAnchor) private lazy var trailingAccessoryFillConstraint: NSLayoutConstraint = contentStackView.set(.height, to: .height, of: trailingAccessoryView) private lazy var accessoryWidthMatchConstraint: NSLayoutConstraint = leadingAccessoryView.set(.width, to: .width, of: trailingAccessoryView) @@ -109,17 +104,6 @@ public class SessionCell: UITableViewCell { return result }() - fileprivate let titleTextField: UITextField = { - let textField: SNTextField = SNTextField(placeholder: "", usesDefaultHeight: false) - textField.translatesAutoresizingMaskIntoConstraints = false - textField.textAlignment = .center - textField.alpha = 0 - textField.isHidden = true - textField.set(.height, to: Values.largeButtonHeight) - - return textField - }() - private let subtitleLabel: SRCopyableLabel = { let result: SRCopyableLabel = SRCopyableLabel() result.translatesAutoresizingMaskIntoConstraints = false @@ -195,8 +179,6 @@ public class SessionCell: UITableViewCell { titleStackView.addArrangedSubview(titleLabel) titleStackView.addArrangedSubview(subtitleLabel) - cellBackgroundView.addSubview(titleTextField) - setupLayout() } @@ -215,8 +197,6 @@ public class SessionCell: UITableViewCell { contentStackViewTopConstraint.isActive = true contentStackViewBottomConstraint.isActive = true - titleTextField.center(.vertical, in: titleLabel) - botSeparatorLeadingConstraint = botSeparator.pin(.leading, to: .leading, of: cellBackgroundView) botSeparatorTrailingConstraint = botSeparator.pin(.trailing, to: .trailing, of: cellBackgroundView) botSeparator.pin(.bottom, to: .bottom, of: cellBackgroundView) @@ -297,7 +277,6 @@ public class SessionCell: UITableViewCell { public override func prepareForReuse() { super.prepareForReuse() - isEditingTitle = false interactionMode = .none shouldHighlightTitle = true accessibilityIdentifier = nil @@ -315,18 +294,12 @@ public class SessionCell: UITableViewCell { contentStackViewTrailingConstraint.isActive = false contentStackViewHorizontalCenterConstraint.isActive = false contentStackViewWidthConstraint.isActive = false - titleMinHeightConstraint.isActive = false leadingAccessoryView.prepareForReuse() leadingAccessoryView.alpha = 1 leadingAccessoryFillConstraint.isActive = false titleLabel.text = "" titleLabel.themeTextColor = .textPrimary titleLabel.alpha = 1 - titleTextField.text = "" - titleTextField.textAlignment = .center - titleTextField.themeTextColor = .textPrimary - titleTextField.isHidden = true - titleTextField.alpha = 0 subtitleLabel.isUserInteractionEnabled = false subtitleLabel.attributedText = nil subtitleLabel.themeTextColor = .textPrimary @@ -418,16 +391,6 @@ public class SessionCell: UITableViewCell { return -(leadingFitToEdge || trailingFitToEdge ? 0 : Values.mediumSpacing) }() - titleTextFieldLeadingConstraint.constant = { - guard info.styling.backgroundStyle != .noBackground else { return 0 } - - return (leadingFitToEdge ? 0 : Values.mediumSpacing) - }() - titleTextFieldTrailingConstraint.constant = { - guard info.styling.backgroundStyle != .noBackground else { return 0 } - - return -(trailingFitToEdge ? 0 : Values.mediumSpacing) - }() // Styling and positioning let defaultEdgePadding: CGFloat @@ -567,12 +530,6 @@ public class SessionCell: UITableViewCell { titleLabel.accessibilityIdentifier = info.title?.accessibility?.identifier titleLabel.accessibilityLabel = info.title?.accessibility?.label titleLabel.isHidden = (info.title == nil) - titleTextField.text = info.title?.text - titleTextField.textAlignment = (info.title?.textAlignment ?? .left) - titleTextField.placeholder = info.title?.editingPlaceholder - titleTextField.isHidden = (info.title == nil) - titleTextField.accessibilityIdentifier = info.title?.accessibility?.identifier - titleTextField.accessibilityLabel = info.title?.accessibility?.label subtitleLabel.isUserInteractionEnabled = (info.subtitle?.interaction == .copy) subtitleLabel.font = info.subtitle?.font subtitleLabel.themeAttributedText = info.subtitle.map { subtitle -> ThemedAttributedString? in @@ -602,57 +559,17 @@ public class SessionCell: UITableViewCell { ) } - public func update(isEditing: Bool, becomeFirstResponder: Bool, animated: Bool) { - // Note: We set 'isUserInteractionEnabled' based on the 'info.isEditable' flag - // so can use that to determine whether this element can become editable - guard interactionMode == .editable || interactionMode == .alwaysEditing else { return } - - self.isEditingTitle = isEditing - - let changes = { [weak self] in - self?.titleLabel.alpha = (isEditing ? 0 : 1) - self?.titleTextField.alpha = (isEditing ? 1 : 0) - self?.leadingAccessoryView.alpha = (isEditing ? 0 : 1) - self?.trailingAccessoryView.alpha = (isEditing ? 0 : 1) - self?.titleMinHeightConstraint.isActive = isEditing - } - let completion: (Bool) -> Void = { [weak self] complete in - self?.titleTextField.text = self?.originalInputValue - } - - if animated { - UIView.animate(withDuration: 0.25, animations: changes, completion: completion) - } - else { - changes() - completion(true) - } - - if isEditing && becomeFirstResponder { - titleTextField.becomeFirstResponder() - } - else if !isEditing { - titleTextField.resignFirstResponder() - } - } - // MARK: - Interaction public override func setHighlighted(_ highlighted: Bool, animated: Bool) { super.setHighlighted(highlighted, animated: animated) - // When editing disable the highlighted state changes (would result in UI elements - // reappearing otherwise) - guard !self.isEditingTitle else { return } - // If the 'cellSelectedBackgroundView' is hidden then there is no background so we // should update the titleLabel to indicate the highlighted state if cellSelectedBackgroundView.isHidden && shouldHighlightTitle { // Note: We delay the "unhighlight" of the titleLabel so that the transition doesn't // conflict with the transition into edit mode DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(10)) { [weak self] in - guard self?.isEditingTitle == false else { return } - self?.titleLabel.alpha = (highlighted ? 0.8 : 1) } } @@ -675,22 +592,3 @@ public class SessionCell: UITableViewCell { lastTouchLocation = touches.first } } - -// MARK: - Compose - -extension CombineCompatible where Self: SessionCell { - var textPublisher: AnyPublisher { - return self.titleTextField.publisher(for: [.editingChanged, .editingDidEnd]) - .handleEvents( - receiveOutput: { [weak self] textField in - // When editing the text update the 'accessibilityLabel' of the cell to match - // the text - let targetText: String? = (textField.isEditing ? textField.text : self?.titleLabel.text) - self?.accessibilityLabel = (targetText ?? self?.accessibilityLabel) - } - ) - .filter { $0.isEditing } // Don't bother sending events for 'editingDidEnd' - .map { textField -> String in (textField.text ?? "") } - .eraseToAnyPublisher() - } -} diff --git a/Session/Shared/Views/SessionHighlightingBackgroundLabel.swift b/Session/Shared/Views/SessionHighlightingBackgroundLabel.swift index 7e6d6ead22..6bbe3022cb 100644 --- a/Session/Shared/Views/SessionHighlightingBackgroundLabel.swift +++ b/Session/Shared/Views/SessionHighlightingBackgroundLabel.swift @@ -15,6 +15,11 @@ public class SessionHighlightingBackgroundLabel: UIView { set { label.themeTextColor = newValue } } + var preferredMaxLayoutWidth: CGFloat { + get { label.preferredMaxLayoutWidth - (Values.smallSpacing * 2) } + set { label.preferredMaxLayoutWidth = (newValue - (Values.smallSpacing * 2)) } + } + // MARK: - Components private let label: UILabel = { From 145ad5be0a6b705fd9c46a9d1e9aee9b250dffc0 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 9 Sep 2025 12:49:06 +1000 Subject: [PATCH 38/66] Added the logic to `createHighlightingBackgroundLabelAndRadioView` --- Session/Shared/Views/SessionCell+AccessoryView.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index 5b16c44ffc..d601eb5bf5 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -170,7 +170,7 @@ extension SessionCell { return createHighlightingBackgroundLabelView(maxContentWidth: maxContentWidth) case is SessionCell.AccessoryConfig.HighlightingBackgroundLabelAndRadio: - return createHighlightingBackgroundLabelAndRadioView() + return createHighlightingBackgroundLabelAndRadioView(maxContentWidth: maxContentWidth) case is SessionCell.AccessoryConfig.DisplayPicture: return createDisplayPictureView() case is SessionCell.AccessoryConfig.Search: return createSearchView() @@ -544,10 +544,11 @@ extension SessionCell { // MARK: -- HighlightingBackgroundLabelAndRadio - private func createHighlightingBackgroundLabelAndRadioView() -> UIView { + private func createHighlightingBackgroundLabelAndRadioView(maxContentWidth: CGFloat) -> UIView { let result: UIView = UIView() let label: SessionHighlightingBackgroundLabel = SessionHighlightingBackgroundLabel() let radio: UIView = createRadioView() + label.preferredMaxLayoutWidth = (maxContentWidth * 0.4) /// Limit to 40% of content width result.addSubview(label) result.addSubview(radio) From 16db95c42ef7fad922c7d28c7bbd36ccffa67c27 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 9 Sep 2025 13:52:11 +1000 Subject: [PATCH 39/66] Fixed breaking SessionHeaderView and Separator constraints --- Session/Shared/Views/SessionHeaderView.swift | 55 ++++++++----------- SessionUIKit/Components/Separator.swift | 19 ++++--- .../Utilities/UIView+Constraints.swift | 22 +++++--- 3 files changed, 47 insertions(+), 49 deletions(-) diff --git a/Session/Shared/Views/SessionHeaderView.swift b/Session/Shared/Views/SessionHeaderView.swift index ebe88b1b95..b845d027bb 100644 --- a/Session/Shared/Views/SessionHeaderView.swift +++ b/Session/Shared/Views/SessionHeaderView.swift @@ -6,14 +6,10 @@ import SessionUIKit class SessionHeaderView: UITableViewHeaderFooterView { // MARK: - UI - private lazy var titleLabelConstraints: [NSLayoutConstraint] = [ - titleLabel.pin(.top, to: .top, of: self, withInset: Values.mediumSpacing), - titleLabel.pin(.bottom, to: .bottom, of: self, withInset: -Values.mediumSpacing) - ] - private lazy var titleLabelLeadingConstraint: NSLayoutConstraint = titleLabel.pin(.leading, to: .leading, of: self) - private lazy var titleLabelTrailingConstraint: NSLayoutConstraint = titleLabel.pin(.trailing, to: .trailing, of: self) - private lazy var titleSeparatorLeadingConstraint: NSLayoutConstraint = titleSeparator.pin(.leading, to: .leading, of: self) - private lazy var titleSeparatorTrailingConstraint: NSLayoutConstraint = titleSeparator.pin(.trailing, to: .trailing, of: self) + private var titleLabelLeadingConstraint: NSLayoutConstraint? + private var titleLabelTrailingConstraint: NSLayoutConstraint? + private var titleSeparatorLeadingConstraint: NSLayoutConstraint? + private var titleSeparatorTrailingConstraint: NSLayoutConstraint? private let titleLabel: UILabel = { let result: UILabel = UILabel() @@ -51,9 +47,9 @@ class SessionHeaderView: UITableViewHeaderFooterView { self.backgroundView = UIView() self.backgroundView?.themeBackgroundColor = .backgroundPrimary - addSubview(titleLabel) - addSubview(titleSeparator) - addSubview(loadingIndicator) + contentView.addSubview(titleLabel) + contentView.addSubview(titleSeparator) + contentView.addSubview(loadingIndicator) setupLayout() } @@ -63,12 +59,18 @@ class SessionHeaderView: UITableViewHeaderFooterView { } private func setupLayout() { - titleLabel.pin(.top, to: .top, of: self, withInset: Values.mediumSpacing) - titleLabel.pin(.bottom, to: .bottom, of: self, withInset: Values.mediumSpacing) - titleLabel.center(.vertical, in: self) + titleLabel.pin(.top, to: .top, of: contentView, withInset: Values.mediumSpacing) + titleLabelLeadingConstraint = titleLabel.pin(.leading, to: .leading, of: contentView) + titleLabelTrailingConstraint = titleLabel.pin(.trailing, to: .trailing, of: contentView) + titleLabel + .pin(.bottom, to: .bottom, of: contentView, withInset: -Values.mediumSpacing) + .setting(priority: .defaultHigh) - titleSeparator.center(.vertical, in: self) - loadingIndicator.center(in: self) + titleSeparator.center(.vertical, in: contentView) + titleSeparatorLeadingConstraint = titleSeparator.pin(.leading, to: .leading, of: contentView) + titleSeparatorTrailingConstraint = titleSeparator.pin(.trailing, to: .trailing, of: contentView) + + loadingIndicator.center(in: contentView) } // MARK: - Content @@ -79,14 +81,6 @@ class SessionHeaderView: UITableViewHeaderFooterView { titleLabel.isHidden = true titleSeparator.isHidden = true loadingIndicator.isHidden = true - - titleLabelLeadingConstraint.isActive = false - titleLabelTrailingConstraint.isActive = false - titleLabelConstraints.forEach { $0.isActive = false } - - titleSeparator.center(.vertical, in: self) - titleSeparatorLeadingConstraint.isActive = false - titleSeparatorTrailingConstraint.isActive = false } public func update( @@ -94,24 +88,19 @@ class SessionHeaderView: UITableViewHeaderFooterView { style: SessionTableSectionStyle = .titleRoundedContent ) { let titleIsEmpty: Bool = (title ?? "").isEmpty + titleLabelLeadingConstraint?.constant = style.edgePadding + titleLabelTrailingConstraint?.constant = -style.edgePadding + titleSeparatorLeadingConstraint?.constant = style.edgePadding + titleSeparatorTrailingConstraint?.constant = -style.edgePadding switch style { case .titleRoundedContent, .titleEdgeToEdgeContent, .titleNoBackgroundContent: titleLabel.text = title titleLabel.isHidden = titleIsEmpty - titleLabelLeadingConstraint.constant = style.edgePadding - titleLabelTrailingConstraint.constant = -style.edgePadding - titleLabelLeadingConstraint.isActive = !titleIsEmpty - titleLabelTrailingConstraint.isActive = !titleIsEmpty - titleLabelConstraints.forEach { $0.isActive = true } case .titleSeparator: titleSeparator.update(title: title) titleSeparator.isHidden = false - titleSeparatorLeadingConstraint.constant = style.edgePadding - titleSeparatorTrailingConstraint.constant = -style.edgePadding - titleSeparatorLeadingConstraint.isActive = !titleIsEmpty - titleSeparatorTrailingConstraint.isActive = !titleIsEmpty case .none, .padding: break case .loadMore: loadingIndicator.isHidden = false diff --git a/SessionUIKit/Components/Separator.swift b/SessionUIKit/Components/Separator.swift index d35add8fda..3230665d81 100644 --- a/SessionUIKit/Components/Separator.swift +++ b/SessionUIKit/Components/Separator.swift @@ -68,17 +68,20 @@ public final class Separator: UIView { addSubview(rightLine) addSubview(titleLabel) - titleLabel.center(.horizontal, in: self) - titleLabel.center(.vertical, in: self) - roundedLine.pin(.top, to: .top, of: self) - roundedLine.pin(.top, to: .top, of: titleLabel, withInset: -6) - roundedLine.pin(.leading, to: .leading, of: titleLabel, withInset: -10) - roundedLine.pin(.trailing, to: .trailing, of: titleLabel, withInset: 10) - roundedLine.pin(.bottom, to: .bottom, of: titleLabel, withInset: 6) - roundedLine.pin(.bottom, to: .bottom, of: self) + titleLabel.pin(.top, to: .top, of: roundedLine, withInset: 6) + titleLabel.pin(.leading, to: .leading, of: roundedLine, withInset: 10) + titleLabel.pin(.trailing, to: .trailing, of: roundedLine, withInset: -10) + titleLabel.pin(.bottom, to: .bottom, of: roundedLine, withInset: -6) + + roundedLine.center(.horizontal, in: self) + roundedLine.center(.vertical, in: self) + roundedLine.setContentHugging(.horizontal, to: .required) + roundedLine.setCompressionResistance(.horizontal, to: .required) + leftLine.pin(.leading, to: .leading, of: self) leftLine.pin(.trailing, to: .leading, of: roundedLine) leftLine.center(.vertical, in: self) + rightLine.pin(.leading, to: .trailing, of: roundedLine) rightLine.pin(.trailing, to: .trailing, of: self) rightLine.center(.vertical, in: self) diff --git a/SessionUIKit/Utilities/UIView+Constraints.swift b/SessionUIKit/Utilities/UIView+Constraints.swift index 0570ed3325..6d905e234a 100644 --- a/SessionUIKit/Utilities/UIView+Constraints.swift +++ b/SessionUIKit/Utilities/UIView+Constraints.swift @@ -211,16 +211,22 @@ public extension UIView { } } - func pin(to view: UIView) { - [ HorizontalEdge.leading, HorizontalEdge.trailing ].forEach { pin($0, to: $0, of: view) } - [ VerticalEdge.top, VerticalEdge.bottom ].forEach { pin($0, to: $0, of: view) } + @discardableResult + func pin(to view: UIView) -> [NSLayoutConstraint] { + return [ + [ HorizontalEdge.leading, HorizontalEdge.trailing ].map { pin($0, to: $0, of: view) }, + [ VerticalEdge.top, VerticalEdge.bottom ].map { pin($0, to: $0, of: view) } + ].flatMap { $0 } } - func pin(to view: UIView, withInset inset: CGFloat) { - pin(.leading, to: .leading, of: view, withInset: inset) - pin(.top, to: .top, of: view, withInset: inset) - view.pin(.trailing, to: .trailing, of: self, withInset: inset) - view.pin(.bottom, to: .bottom, of: self, withInset: inset) + @discardableResult + func pin(to view: UIView, withInset inset: CGFloat) -> [NSLayoutConstraint] { + return [ + pin(.leading, to: .leading, of: view, withInset: inset), + pin(.top, to: .top, of: view, withInset: inset), + view.pin(.trailing, to: .trailing, of: self, withInset: inset), + view.pin(.bottom, to: .bottom, of: self, withInset: inset) + ] } @discardableResult From d19cd4257cbcd368aadd49bfb5fdf394ccc2d853 Mon Sep 17 00:00:00 2001 From: mikoldin Date: Wed, 10 Sep 2025 11:15:42 +0800 Subject: [PATCH 40/66] Fix edit image via add text not working Fix gradient palette selector not touchable --- SessionUIKit/Style Guide/Values.swift | 2 ++ .../ImageEditorBrushViewController.swift | 3 ++- .../Image Editing/ImageEditorCanvasView.swift | 8 +++++--- .../Image Editing/ImageEditorPaletteView.swift | 12 +++++++----- .../ImageEditorTextViewController.swift | 8 ++++++-- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/SessionUIKit/Style Guide/Values.swift b/SessionUIKit/Style Guide/Values.swift index ffd825c2af..8f5037401f 100644 --- a/SessionUIKit/Style Guide/Values.swift +++ b/SessionUIKit/Style Guide/Values.swift @@ -31,6 +31,8 @@ public enum Values { public static let accentLineThickness = CGFloat(4) public static let searchBarHeight = CGFloat(36) + + public static let gradientPaletteWidth = CGFloat(12) public static var separatorThickness: CGFloat { return 1 / UIScreen.main.scale } diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift index b8adcf5717..35a39417a9 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift @@ -63,7 +63,8 @@ public class ImageEditorBrushViewController: OWSViewController { paletteView.delegate = self self.view.addSubview(paletteView) paletteView.center(.vertical, in: self.view, withInset: -(bottomInset / 2)) - paletteView.pin(.trailing, to: .trailing, of: self.view) + paletteView.pin(.trailing, to: .trailing, of: self.view, withInset: -Values.smallSpacing) + paletteView.set(.width, to: Values.gradientPaletteWidth) self.view.isUserInteractionEnabled = true diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift index f323b7adb9..3fd75252e0 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift @@ -501,10 +501,12 @@ public class ImageEditorCanvasView: UIView { ] ) let layer = EditorTextLayer(itemId: item.itemId) - layer.string = attributedString - layer.themeForegroundColorForced = .color(item.color.color) - layer.font = CGFont(item.font.fontName as CFString) + // Set as .strings, passing only attributed string does not display text + // `attributedString` is now only used to compute sizes + layer.string = attributedString.string + layer.font = item.font layer.fontSize = fontSize + layer.themeForegroundColorForced = .color(item.color.color) layer.isWrapped = true layer.alignmentMode = CATextLayerAlignmentMode.center // I don't think we need to enable allowsFontSubpixelQuantization diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift index 2d7ec71558..b98469d677 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift @@ -227,11 +227,13 @@ public class ImageEditorPaletteView: UIView { } addSubview(imageView) // We use an invisible margin to expand the hot area of this control. - let margin: CGFloat = 20 - imageView.pin(.top, to: .top, of: self, withInset: margin) - imageView.pin(.leading, to: .leading, of: self, withInset: -margin) - imageView.pin(.trailing, to: .trailing, of: self, withInset: margin) - imageView.pin(.bottom, to: .bottom, of: self, withInset: -margin) + let verticalMargin: CGFloat = 20 + let horizontalMargin: CGFloat = 8 + + imageView.pin(.top, to: .top, of: self, withInset: verticalMargin) + imageView.pin(.leading, to: .leading, of: self, withInset: -horizontalMargin) + imageView.pin(.trailing, to: .trailing, of: self, withInset: horizontalMargin) + imageView.pin(.bottom, to: .bottom, of: self, withInset: -verticalMargin) imageView.themeBorderColor = .white imageView.layer.borderWidth = 1 diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift index 77d8f261bb..4febd32c7b 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift @@ -190,8 +190,12 @@ public class ImageEditorTextViewController: OWSViewController, VAlignTextViewDel paletteView.delegate = self self.view.addSubview(paletteView) - paletteView.center(.horizontal, in: textView) - paletteView.pin(.trailing, to: .trailing, of: self.view) + paletteView.center(.vertical, in: self.view, withInset: -((bottomInset / 2) + Values.largeSpacing)) + paletteView.pin(.trailing, to: .trailing, of: self.view, withInset: -Values.smallSpacing) + + // Size of gradient image and touchable area + paletteView.set(.width, to: Values.gradientPaletteWidth) + // This will determine the text view's size. paletteView.pin(.leading, to: .trailing, of: textView) From 1466b8796e38601394e2054d12d91550e2ccd09e Mon Sep 17 00:00:00 2001 From: mikoldin Date: Wed, 10 Sep 2025 13:04:41 +0800 Subject: [PATCH 41/66] Added accessibility identifier for block contact cell --- Session/Settings/ConversationSettingsViewModel.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Session/Settings/ConversationSettingsViewModel.swift b/Session/Settings/ConversationSettingsViewModel.swift index d26cff429d..5b52afca21 100644 --- a/Session/Settings/ConversationSettingsViewModel.swift +++ b/Session/Settings/ConversationSettingsViewModel.swift @@ -192,6 +192,9 @@ class ConversationSettingsViewModel: SessionTableViewModel, NavigatableStateHold .chevronRight, pinEdges: [.right] ), + accessibility: Accessibility( + identifier: "Block contacts - Navigation" + ), onTap: { [weak viewModel, dependencies = viewModel.dependencies] in viewModel?.transitionToScreen( SessionTableViewController(viewModel: BlockedContactsViewModel(using: dependencies)) From d8d97e0b7e93dc7f49ee4cf6008c16d19090634c Mon Sep 17 00:00:00 2001 From: mikoldin Date: Thu, 11 Sep 2025 09:55:22 +0800 Subject: [PATCH 42/66] Fix padding icon padding in deleted message cell --- .../Content Views/DeletedMessageView.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift b/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift index 72f5e5c4b1..0d552b34b4 100644 --- a/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift +++ b/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift @@ -31,19 +31,15 @@ final class DeletedMessageView: UIView { } private func setUpViewHierarchy(textColor: ThemeValue, variant: Interaction.Variant, maxWidth: CGFloat) { - // Image view - let imageContainerView: UIView = UIView() - imageContainerView.set(.width, to: DeletedMessageView.iconImageViewSize) - imageContainerView.set(.height, to: DeletedMessageView.iconImageViewSize) + let trashIcon = Lucide.image(icon: .trash2, size: DeletedMessageView.iconSize)? + .withRenderingMode(.alwaysTemplate) - let imageView = UIImageView(image: Lucide.image(icon: .trash2, size: DeletedMessageView.iconSize)?.withRenderingMode(.alwaysTemplate)) + let imageView = UIImageView(image: trashIcon) imageView.themeTintColor = textColor imageView.alpha = Values.highOpacity imageView.contentMode = .scaleAspectFit imageView.set(.width, to: DeletedMessageView.iconSize) imageView.set(.height, to: DeletedMessageView.iconSize) - imageContainerView.addSubview(imageView) - imageView.center(in: imageContainerView) // Body label let titleLabel = UILabel() @@ -64,9 +60,13 @@ final class DeletedMessageView: UIView { titleLabel.numberOfLines = 2 // Stack view - let stackView = UIStackView(arrangedSubviews: [ imageContainerView, titleLabel ]) + let stackView = UIStackView(arrangedSubviews: [ + imageView, + titleLabel + ]) stackView.axis = .horizontal stackView.alignment = .center + stackView.spacing = Values.smallSpacing stackView.isLayoutMarginsRelativeArrangement = true stackView.layoutMargins = UIEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 6) addSubview(stackView) From 94baa24437f3f7201770b149a357edc814bbb611 Mon Sep 17 00:00:00 2001 From: mikoldin Date: Thu, 11 Sep 2025 14:36:19 +0800 Subject: [PATCH 43/66] Fix keyboard not presenting keyboard on longpress reply --- .../Conversations/ConversationVC+Interaction.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 1330cba707..e5e13f230f 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -2250,10 +2250,17 @@ extension ConversationVC: isOutgoing: (cellViewModel.variant == .standardOutgoing) ) - if isShowingSearchUI { willManuallyCancelSearchUI() } + // Add delay before doing any ui updates + // Delay added to give time for long press actions to dismiss + let delay = completion == nil ? 0 : ContextMenuVC.dismissDuration - _ = snInputView.becomeFirstResponder() - completion?() + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in + if self?.isShowingSearchUI == true { self?.willManuallyCancelSearchUI() } + + _ = self?.snInputView.becomeFirstResponder() + + completion?() + } } func copy(_ cellViewModel: MessageViewModel, completion: (() -> Void)?) { From aa1280e437affa7dc5a86d726cbea47573bf685b Mon Sep 17 00:00:00 2001 From: mikoldin Date: Fri, 12 Sep 2025 11:40:09 +0800 Subject: [PATCH 44/66] Fix previously selected app icon not re-selected on toggle default off --- Session/Settings/AppIconViewModel.swift | 23 ++++++++++++++++++- .../Types/UserDefaultsType.swift | 3 +++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Session/Settings/AppIconViewModel.swift b/Session/Settings/AppIconViewModel.swift index c0cf0f2da8..5a8386fab5 100644 --- a/Session/Settings/AppIconViewModel.swift +++ b/Session/Settings/AppIconViewModel.swift @@ -137,6 +137,12 @@ class AppIconViewModel: SessionTableViewModel, NavigatableStateHolder, Observabl lazy var observation: TargetObservation = ObservationBuilderOld .subject(selectedOptionsSubject) .mapWithPrevious { [weak self, dependencies] previous, current -> [SectionModel] in + + if let currentIcon = current { + // Save latest app icon disguise selected + dependencies[defaults: .standard, key: .lastSelectedAppIconDisguise] = currentIcon + } + return [ SectionModel( model: .appIcon, @@ -154,7 +160,7 @@ class AppIconViewModel: SessionTableViewModel, NavigatableStateHolder, Observabl onTap: { [weak self] in switch current { case .some: self?.updateAppIcon(nil) - case .none: self?.updateAppIcon(.weather) + case .none: self?.restorePreviousIcon(previous) // Previous is String?? } } ) @@ -189,4 +195,19 @@ class AppIconViewModel: SessionTableViewModel, NavigatableStateHolder, Observabl selectedOptionsSubject.send(icon?.rawValue) } + + private func restorePreviousIcon(_ identifier: String??) { + var previousIcon: AppIcon? { + if let previousIcon = identifier { + // Set previous app icon + return AppIcon(name: previousIcon) + } else if let previousIcon = dependencies[defaults: .standard, key: .lastSelectedAppIconDisguise] { + // Handles app close instance to restore previously selected + return AppIcon(name: previousIcon) + } + return .weather + } + + updateAppIcon(previousIcon) + } } diff --git a/SessionUtilitiesKit/Types/UserDefaultsType.swift b/SessionUtilitiesKit/Types/UserDefaultsType.swift index e7db0491ee..968b66c2e4 100644 --- a/SessionUtilitiesKit/Types/UserDefaultsType.swift +++ b/SessionUtilitiesKit/Types/UserDefaultsType.swift @@ -228,6 +228,9 @@ public extension UserDefaults.StringKey { /// The id of the thread that a message was just shared to static let lastSharedThreadId: UserDefaults.StringKey = "lastSharedThreadId" + + /// The app-icon name of the previously selected app icon disguise + static let lastSelectedAppIconDisguise: UserDefaults.StringKey = "lastSelectedAppIconDisguise" } // MARK: - Keys From ae10a428c6c75998c1904a6dae82210bf5724e68 Mon Sep 17 00:00:00 2001 From: mikoldin Date: Fri, 12 Sep 2025 13:34:05 +0800 Subject: [PATCH 45/66] Fix keyboard not showing when replying from message info --- .../ConversationVC+Interaction.swift | 8 ++++-- Session/Conversations/ConversationVC.swift | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index e5e13f230f..e565a4bfbf 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -2257,8 +2257,12 @@ extension ConversationVC: DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in if self?.isShowingSearchUI == true { self?.willManuallyCancelSearchUI() } - _ = self?.snInputView.becomeFirstResponder() - + if self?.checkIfEventWasTriggerWhileNotVisible() == true { + self?.hasPendingInputKeyboardPresentationEvent = true + } else { + _ = self?.snInputView.becomeFirstResponder() + } + completion?() } } diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index ab8471b452..b32a17a57f 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -29,6 +29,10 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa /// never have disappeared before - this is only needed for value observers since they run asynchronously) private var hasReloadedThreadDataAfterDisappearance: Bool = true + /// This flag indicates that a need for inputview keyboard presentation is needed, this is in events + /// where a delegate action is trigger before poping back into `ConversationVC` + var hasPendingInputKeyboardPresentationEvent: Bool = false + var focusedInteractionInfo: Interaction.TimestampInfo? var focusBehaviour: ConversationViewModel.FocusBehaviour = .none @@ -581,6 +585,12 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa self?.didFinishInitialLayout = true self?.viewIsAppearing = false self?.lastPresentedViewController = nil + + // Show inputview keyboard + if self?.hasPendingInputKeyboardPresentationEvent == true { + self?.makeInputViewFirstResponder() + self?.hasPendingInputKeyboardPresentationEvent = false + } } } @@ -1647,6 +1657,22 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa completion?() } } + + private func makeInputViewFirstResponder() { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + self?.inputAccessoryView?.becomeFirstResponder() + } + } + + func checkIfEventWasTriggerWhileNotVisible() -> Bool { + // Delegate reply action triggered by MessageInfoViewController + if let navigationStack = self.navigationController?.viewControllers { + if navigationStack.contains(where: { $0 is ConversationVC }) && navigationStack.last is MessageInfoViewController { + return true + } + } + return false + } // MARK: - UITableViewDataSource From e0bcf03b4e92e173cc6f38ca8c4229ccd07bc33e Mon Sep 17 00:00:00 2001 From: mikoldin Date: Mon, 15 Sep 2025 08:37:19 +0800 Subject: [PATCH 46/66] Code clean ups --- .../ConversationVC+Interaction.swift | 19 ++++++++++------- Session/Conversations/ConversationVC.swift | 21 ++++--------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index e565a4bfbf..818a233b59 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -2250,19 +2250,24 @@ extension ConversationVC: isOutgoing: (cellViewModel.variant == .standardOutgoing) ) + // If the `MessageInfoViewController` is visible then we want to show the keyboard after + // the pop transition completes (and don't want to delay triggering the completion closure) + let messageInfoScreenVisible: Bool = (self.navigationController?.viewControllers.last is MessageInfoViewController) + + guard !messageInfoScreenVisible else { + if self.isShowingSearchUI == true { self.willManuallyCancelSearchUI() } + self.hasPendingInputKeyboardPresentationEvent = true + completion?() + return + } + // Add delay before doing any ui updates // Delay added to give time for long press actions to dismiss let delay = completion == nil ? 0 : ContextMenuVC.dismissDuration DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in if self?.isShowingSearchUI == true { self?.willManuallyCancelSearchUI() } - - if self?.checkIfEventWasTriggerWhileNotVisible() == true { - self?.hasPendingInputKeyboardPresentationEvent = true - } else { - _ = self?.snInputView.becomeFirstResponder() - } - + _ = self?.snInputView.becomeFirstResponder() completion?() } } diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index b32a17a57f..fd52a451dc 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -588,7 +588,10 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa // Show inputview keyboard if self?.hasPendingInputKeyboardPresentationEvent == true { - self?.makeInputViewFirstResponder() + // Added 0.1 delay to remove inputview stutter animation glitch while keyboard is animating up + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + _ = self?.snInputView.becomeFirstResponder() + } self?.hasPendingInputKeyboardPresentationEvent = false } } @@ -1657,22 +1660,6 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa completion?() } } - - private func makeInputViewFirstResponder() { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in - self?.inputAccessoryView?.becomeFirstResponder() - } - } - - func checkIfEventWasTriggerWhileNotVisible() -> Bool { - // Delegate reply action triggered by MessageInfoViewController - if let navigationStack = self.navigationController?.viewControllers { - if navigationStack.contains(where: { $0 is ConversationVC }) && navigationStack.last is MessageInfoViewController { - return true - } - } - return false - } // MARK: - UITableViewDataSource From c90b57dba3a1c9ac24ced6876d9b71e687cf85ef Mon Sep 17 00:00:00 2001 From: mpretty-cyro <15862619+mpretty-cyro@users.noreply.github.com> Date: Mon, 15 Sep 2025 00:41:40 +0000 Subject: [PATCH 47/66] [Automated] Update translations from Crowdin --- .../Meta/Translations/Localizable.xcstrings | 5536 ++++++++++++++++- 1 file changed, 5512 insertions(+), 24 deletions(-) diff --git a/Session/Meta/Translations/Localizable.xcstrings b/Session/Meta/Translations/Localizable.xcstrings index 5e0dfa04f5..31da116616 100644 --- a/Session/Meta/Translations/Localizable.xcstrings +++ b/Session/Meta/Translations/Localizable.xcstrings @@ -18625,6 +18625,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "ناردنی پرۆمۆشنی ئەدمین" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "ناردنی پرۆمۆشنی ئەدمین" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -18653,6 +18681,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sender adminforfremmelse" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sender adminforfremmelser" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -20930,11 +20986,65 @@ "value" : "Avto-qaranlıq rejimi" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatický tmavý režim" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatischer Dunkler Modus" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Auto Dark Mode" } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mode sombre automatique" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatische nachtmodus" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mod întunecat automat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматический тёмный режим" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisk mörkt läge" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otomatik karanlık tema" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматичний темний режим" + } } } }, @@ -28195,7 +28305,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Ícono de la app" + "value" : "Ícono de la Aplicación" } }, "es-ES" : { @@ -28252,6 +28362,18 @@ "value" : "앱 아이콘" } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئایکۆنی ئەپ" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئایکۆنی ئەپ" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -28666,7 +28788,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "El ícono y nombre alternativos de la app se muestran en la pantalla principal y el cajón de aplicaciones." + "value" : "Ícono y nombre alternativos para la aplicación se muestran en la pantalla de inicio y en el menú de aplicaciones." } }, "es-ES" : { @@ -29143,7 +29265,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "El ícono alternativo de la app se muestra en la pantalla principal y en la biblioteca de apps. El nombre de la app seguirá apareciendo como \"{app_name}\"." + "value" : "El ícono alternativo para la aplicación se muestra en la pantalla de inicio y en el menú de aplicaciones. El nombre de la aplicación seguirá apareciendo como '{app_name}'." } }, "es-ES" : { @@ -29304,7 +29426,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Usar ícono alternativo de la app" + "value" : "Usar un ícono alternativo para la aplicación" } }, "es-ES" : { @@ -29477,7 +29599,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Usar ícono y nombre alternativos de la app" + "value" : "Usar un ícono y nombre alternativos para la aplicación" } }, "es-ES" : { @@ -29650,7 +29772,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccionar ícono alternativo de la app" + "value" : "Seleccione un ícono alternativo para la aplicación" } }, "es-ES" : { @@ -29701,6 +29823,18 @@ "value" : "대체 앱 아이콘을 선택" } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئایکۆن ئەپی جێگرەوە هەڵبژێرە" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئایکۆن ئەپی جێگرەوە هەڵبژێرە" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -30163,7 +30297,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "MeetingSE" + "value" : "Eventos" } }, "es-ES" : { @@ -30971,11 +31105,35 @@ "value" : "{app_pro} nişanı" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odznak {app_pro}" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} Abzeichen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{app_pro} Badge" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} Badge" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Значок {app_pro}" + } } } }, @@ -63414,6 +63572,12 @@ "value" : "Dadrwystro'r cyswllt hwn i anfon neges" } }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fjern blokering af denne kontakt for at sende en besked" + } + }, "de" : { "stringUnit" : { "state" : "translated", @@ -63432,6 +63596,12 @@ "value" : "Unblock this contact to send a message" } }, + "eo" : { + "stringUnit" : { + "state" : "translated", + "value" : "Malbloki tiun kontakton por sendi mesaĝon" + } + }, "es-419" : { "stringUnit" : { "state" : "translated", @@ -63444,24 +63614,60 @@ "value" : "Desbloquea este contacto para enviar mensajes." } }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sõnumi saatmiseks eemalda selle kontakti blokeering" + } + }, "eu" : { "stringUnit" : { "state" : "translated", "value" : "Kontaktu hau desblokeatu mezu bat bidaltzeko" } }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "برای ارسال پیام،‌ ابتدا این مخاطب را از مسدود بودن درآورید!" + } + }, "fi" : { "stringUnit" : { "state" : "translated", "value" : "Lähettääksesi viestin tälle yhteystiedolle sinun tulee ensin poistaa asettamasi esto." } }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "I-unblock ang contact na ito para magpadala ng mensahe" + } + }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Débloquez ce contact pour envoyer un message" } }, + "gl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desbloquea este contacto para enviar unha mensaxe" + } + }, + "ha" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cire katanga wannan saduwa don aika saƙo" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "בטל חסימה של איש קשר זה כדי לשלוח הודעה" + } + }, "hi" : { "stringUnit" : { "state" : "translated", @@ -63510,6 +63716,18 @@ "value" : "შეტყობინების გაგზავნისთვის ბლოკი მოხსენით" } }, + "km" : { + "stringUnit" : { + "state" : "translated", + "value" : "ដោះការហាមឃាត់លេខទំនាក់ទំនងនេះ ដើម្បីផ្ញើសារ" + } + }, + "kn" : { + "stringUnit" : { + "state" : "translated", + "value" : "ಸಂದೇಶವೊಂದನ್ನು ಕಳುಹಿಸಲು ಈ ಸಂಪರ್ಕವನ್ನು ಬ್ಲಾಕ್ ಮಾಡಿ" + } + }, "ko" : { "stringUnit" : { "state" : "translated", @@ -63525,7 +63743,31 @@ "ku-TR" : { "stringUnit" : { "state" : "translated", - "value" : "ئەم پەیوەندە لابردن بۆ بریتیە لە ناردنی پەیامێک." + "value" : "Ji bo şandina peyamê vê bloka vî kontaktê rake" + } + }, + "lg" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sazaamu omukozesa kuno okusindika obubaka" + } + }, + "lt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atblokuokite šį kontaktą, kad išsiųstumėte žinutę" + } + }, + "lv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atbloķējiet šo kontaktu, lai nosūtītu ziņojumu" + } + }, + "mk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Одблокирај го овој контакт за да испратиш порака" } }, "mn" : { @@ -63558,6 +63800,12 @@ "value" : "Opphev blokkeringen på denne kontakten for å sende en melding." } }, + "ne-NP" : { + "stringUnit" : { + "state" : "translated", + "value" : "सन्देश पठाउन यो सम्पर्क अनब्लक गर्नुहोस्।" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -63567,7 +63815,7 @@ "nn-NO" : { "stringUnit" : { "state" : "translated", - "value" : "Opphev blokkeringen på denne kontakten for å sende en melding." + "value" : "Opphev blokkeringen på denne kontakten for å sende en melding" } }, "ny" : { @@ -63576,12 +63824,24 @@ "value" : "Pokankha Lamulo Llitsa lemba uthenga" } }, + "pa-IN" : { + "stringUnit" : { + "state" : "translated", + "value" : "ਸੁਨੇਹਾ ਭੇਜਣ ਲਈ ਇਸ ਸੰਪਰਕ ਨੂੰ ਅਨਬਲੌਕ ਕਰੋ।" + } + }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Odblokuj ten kontakt, aby wysłać wiadomość" } }, + "ps" : { + "stringUnit" : { + "state" : "translated", + "value" : "د پیغام استولو لپاره له دې اړیکې بې بندیز وکړئ" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -63606,24 +63866,72 @@ "value" : "Разблокируйте этот контакт, чтобы отправить сообщение" } }, + "sh" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odblokirajte ovog kontakta da biste poslali poruku" + } + }, + "si-LK" : { + "stringUnit" : { + "state" : "translated", + "value" : "පණිවිඩය යැවීමට මෙම සබඳතාවය අනවහිර කරන්න" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pre odoslanie správy kontakt odblokujte" + } + }, + "sl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Za pošiljanje sporočila morate najprej odblokirati ta stik" + } + }, "sq" : { "stringUnit" : { "state" : "translated", "value" : "Që t’i dërgohet një mesazh, zhbllokojeni këtë kontakt" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Одблокирајте дописника да би послали поруку" + } + }, + "sr-Latn" : { + "stringUnit" : { + "state" : "translated", + "value" : "Одблокирајте дописника да би послали поруку" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Avblockera denna kontakt för att skicka meddelanden." } }, + "sw" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ondolea kizuizi kwa mawasiliano haya kutuma ujumbe" + } + }, "ta" : { "stringUnit" : { "state" : "translated", "value" : "ஒரு செய்தியை அனுப்ப இந்த தொடர்பை விடுவிக்கவும்." } }, + "te" : { + "stringUnit" : { + "state" : "translated", + "value" : "సందేశాన్ని పంపడానికి ఈ పరిచయాన్ని అనుమతించు" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -63636,6 +63944,24 @@ "value" : "Розблокувати контакт для надсилання повідомлення." } }, + "ur-IN" : { + "stringUnit" : { + "state" : "translated", + "value" : "پیغام بھیجنے کے لیے اس رابطے کو ان بلاک کریں" + } + }, + "uz" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xabar yuborish uchun ushbu kontaktni blokdan chiqaring" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mở khóa người (liên lạc) này để gởi thông báo" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -65108,11 +65434,47 @@ "value" : "Əngəllənmiş kontaktları görün və idarə edin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit a spravovat blokované kontakty." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blockierte Kontakte anzeigen und verwalten." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "View and manage blocked contacts." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bekijk en beheer geblokkeerde contacten." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Просматривайте и управляйте списком заблокированных контактов." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visa och hantera blockerade kontakter." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Переглядайте та керуйте заблокованими контактами." + } } } }, @@ -78530,11 +78892,41 @@ "value" : "Beta zənglərini istifadə edərkən IP-niz zəng tərəfdaşınıza və {session_foundation} serverinə görünür." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Funkce hlasových hovorů, která je nyní ve vývojové fázi (beta), odhalí vaši IP adresu těm, se kterými si voláte a také {session_foundation} serveru." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your IP is visible to your call partner and a {session_foundation} server while using beta calls." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uw IP is zichtbaar voor uw oproep partner en een {session_foundation} server tijdens het gebruik van bètagesprekken." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your IP is visible to your call partner and a {session_foundation} server while using beta calls." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Din IP är synlig för din samtalspartner och en {session_foundation}-server när du använder beta-samtal." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Під час здійснення бета-викликів Ваш IP може побачити співрозмовник та сервер {session_foundation}." + } } } }, @@ -83361,11 +83753,29 @@ "value" : "Planı ləğv et" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zrušit tarif" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Cancel Plan" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abonnement annuleren" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Скасувати тарифний план" + } } } }, @@ -83378,11 +83788,53 @@ "value" : "Dəyişdir" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Změnit" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ändern" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Change" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wijzigen" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schimba" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Изменить" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ändra" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Змінити" + } } } }, @@ -83874,11 +84326,41 @@ "value" : "{app_name} üçün parolunuzu dəyişdirin. Daxili olaraq saxlanılmış verilər, yeni parolunuzla təkrar şifrələnəcək." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Změňte své heslo pro {app_name}. Lokálně uložená data budou znovu zašifrována pomocí vašeho nového hesla." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Change your password for {app_name}. Locally stored data will be re-encrypted with your new password." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wijzig je wachtwoord voor {app_name}. Lokaal opgeslagen gegevens worden opnieuw versleuteld met je nieuwe wachtwoord." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Измените пароль для {app_name}. Локально сохранённые данные будут повторно зашифрованы с использованием нового пароля." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ändra ditt lösenord för {app_name}. Lokalt lagrad data kommer att krypteras om med ditt nya lösenord." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Змінити ваш пароль для {app_name}. Локально збережені дані будуть наново шифровані з застосуванням нового паролю." + } } } }, @@ -98509,12 +98991,24 @@ "value" : "Introdu o descriere a comunității" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Введите описание сообщества" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Ange en communitybeskrivning" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Введіть опис спільноти" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -105352,12 +105846,24 @@ "value" : "Introdu numele comunității" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Введите название сообщества" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Ange ett communitynamn" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Введіть назву спільноти" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -105465,12 +105971,24 @@ "value" : "Te rugăm să introduci un nume al comunității" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пожалуйста, введите название сообщества" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Vänligen ange ett communitynamn" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Будь ласка, введіть назву спільноти" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -113206,11 +113724,41 @@ "value" : "Yeni bir mesaj alındıqda daxili bildirişlərdə nümayiş olunacaq məzmunu seçin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vyberte obsah, který se zobrazí v místních upozorněních při přijetí zprávy." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Choose the content displayed in local notifications when an incoming message is received." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kies de inhoud die wordt weergegeven in lokale meldingen wanneer een inkomend bericht wordt ontvangen." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Выберите содержимое, отображаемое в локальных уведомлениях при получении входящего сообщения." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Välj vilket innehåll som ska visas i lokala aviseringar när ett inkommande meddelande tas emot." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обирайте, який вміст показуватиметься у сповіщеннях після отримання повідомлення." + } } } }, @@ -119037,11 +119585,47 @@ "value" : "Enter və Shift+Enter düymələrinin danışıqlarda necə işləyəcəyini təyin edin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Definujte, jak budou fungovat klávesy Enter a Shift+Enter v konverzacích." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Definiere, wie Eingabe- und Umschalttaste in Konversationen funktionieren." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Define how the Enter and Shift+Enter keys function in conversations." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stel in hoe de toetsen Enter en Shift+Enter functioneren in gesprekken." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Определите, как будут работать клавиши Enter и Shift+Enter в переписке." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Definiera hur Enter- och Skift+Enter-tangenterna fungerar i konversationer." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Налаштування дій клавіш Enter та Shift+Enter у розмовах." + } } } }, @@ -119054,11 +119638,41 @@ "value" : "SHIFT + ENTER mesajı göndərir, ENTER yeni sətrə keçir." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "SHIFT + ENTER odešle zprávu, ENTER začne nový řádek." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "SHIFT + ENTER sends a message, ENTER starts a new line." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "SHIFT + ENTER verzendt een bericht, ENTER begint een nieuwe regel." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "SHIFT + ENTER отправляет сообщение, ENTER начинает новую строку." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "SHIFT + ENTER skickar ett meddelande, ENTER startar en ny rad." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "SHIFT + ENTER надсилає повідомлення, ENTER починає новий рядок." + } } } }, @@ -119071,11 +119685,53 @@ "value" : "ENTER mesajı göndərir, SHIFT + ENTER yeni sətrə keçir." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zmáčknutím ENTER se zpráva odešle, SHIFT + ENTER vytvoří nový řádek." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "ENTER sends a message, SHIFT + ENTER starts a new line." } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER envía un mensaje, SHIFT + ENTER inicia una nueva línea." + } + }, + "es-ES" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER envía un mensaje, SHIFT + ENTER inicia una nueva línea." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER verzendt een bericht, SHIFT + ENTER begint een nieuwe regel." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER для отправки, SHIFT + ENTER для новой строки." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER skickar ett meddelande, SHIFT + ENTER påbörjar en ny rad." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER надсилає повідомлення, SHIFT + ENTER починає новий рядок." + } } } }, @@ -120525,11 +121181,47 @@ "value" : "2,000-dən çox mesajı olan icmalarda 6 aydan köhnə mesajları avtomatik sil." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Z komunit automaticky mazat zprávy starší než 6 měsíců, pokud jich je více než 2000." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nachrichten älter als 6 Monate in Communities mit mehr als 2000 Nachrichten automatisch löschen." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Auto-delete messages older than 6 months in communities with 2000+ messages." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Berichten ouder dan 6 maanden automatisch verwijderen in community's met meer dan 2000 berichten." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Удалять сообщения старше 6 месяцев в сообществах с более чем 2000 сообщений." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ta bort meddelanden som är äldre än 6 månader i gemenskaper med 2000+ meddelanden." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автовидалення повідомлень старших за 6 місяців у спільнотах з 2000+ повідомлень." + } } } }, @@ -121500,11 +122192,65 @@ "value" : "Enter ilə göndər" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odeslat klávesou Enter" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mit Eingabetaste senden" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Send with Enter" } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enter para enviar" + } + }, + "es-ES" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enter para enviar" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verzenden met Enter" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apasă Enter pentru a trimite" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отправлять по Enter" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skicka med Enter" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Надіслати з Enter" + } } } }, @@ -121996,11 +122742,47 @@ "value" : "Shift+Enter ilə göndər" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odeslat klávesou Shift+Enter" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mit Umschalt- und Eingabetaste senden" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Send with Shift+Enter" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verzenden met Shift+Enter" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отправить с Shift+Enter" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skicka med Shift+Enter" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Надіслати з Shift+Enter" + } } } }, @@ -125563,11 +126345,35 @@ "value" : "Hazırkı parol" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktuální heslo" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Current Password" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Huidig wachtwoord" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nuvarande lösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поточний пароль" + } } } }, @@ -125580,11 +126386,29 @@ "value" : "Hazırkı plan" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Současný tarif" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Current Plan" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Huidig abonnement" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поточна передплата" + } } } }, @@ -126082,11 +126906,53 @@ "value" : "Qaranlıq rejim" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tmavý režim" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dunkelmodus" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Dark Mode" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Donkere modus" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mod întunecat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тёмный режим" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mörkt läge" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Темний режим" + } } } }, @@ -126326,6 +127192,12 @@ "value" : "데이터베이스 오류가 발생했습니다.

문제 해결을 위해 애플리케이션 로그를 내보내서 공유하십시오. 실패할 경우, {app_name}을 다시 설치하고 계정을 복원하십시오." } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "En databasefeil har oppstått.

Eksporter dine applikasjon logger for å dele feilsøkingen. Hvis dette ikke vellykkes, installer {app_name} på nytt og gjenopprett kontoen din." + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -139724,6 +140596,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "دڵنیایت دەتەوێت ئەم پەیامە بسڕیتەوە؟" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "دڵنیایت دەتەوێت ئەم پەیامانە بسڕیتەوە؟" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -139752,6 +140652,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er du sikker på at du vil slette denne meldingen?" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er du sikker på at du vil slette disse meldingene?" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -143834,6 +144762,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئایا دڵنیای کە دەتەوێت ئەم پەیامە تەنها لەم ئامێرە بسڕیتەوە؟" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئایا دڵنیای کە دەتەوێت ئەم پەیامە تەنها لەم ئامێرە بسڕیتەوە؟" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -148918,6 +149874,34 @@ } } }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "پرشین" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "برخی از پیام‌هایی که انتخاب کرده‌اید، نمی‌توانند از همهٔ دستگاه‌های شما پاک شوند" + } + } + } + } + } + } + }, "fi" : { "stringUnit" : { "state" : "translated", @@ -149152,6 +150136,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئەم پەیامە ناتوانرێت لە هەموو ئامێرەکانت بسڕدرێتەوە" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "هەندێک لەو نامانەی کە هەڵتبژاردووە ناتوانرێت لە هەموو ئامێرەکانت بسڕدرێنەوە" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -149230,6 +150242,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Denne meldingen kan ikke bli slettet fra alle dine enheter" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Noen av meldingene du valgte kan ikke bli slettet fra alle dine enheter" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -150766,6 +151806,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئەم پەیامە بۆ هەموو کەسێک ناسڕدرێتەوە" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "هەندێک لەو نامانەی کە هەڵتبژاردووە ناتوانرێت بۆ هەموو کەسێک بسڕدرێتەوە" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -150858,13 +151926,13 @@ "one" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Nachricht kann nicht gelöscht werden" + "value" : "Denne meldingen kan ikke bli slettet for alle" } }, "other" : { "stringUnit" : { "state" : "translated", - "value" : "Ein paar Nachrichten könnten nicht gelöscht werden" + "value" : "Noen av meldingene du valgte kan ikke bli slettet for alle" } } } @@ -169086,11 +170154,47 @@ "value" : "Nümayiş" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazení" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Display" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Display" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Weergave" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дисплей" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skärm" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Зовнішній вигляд" + } } } }, @@ -187360,11 +188464,47 @@ "value" : "Yeni mesaj aldığınız zaman bildirişlər göstərilsin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit upozornění při přijetí nových zpráv." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Benachrichtigungen anzeigen, wenn du neue Nachrichten erhältst." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Show notifications when you receive new messages." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toon meldingen wanneer je nieuwe berichten ontvangt." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показывать уведомления при получении новых сообщений." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visa aviseringar när du får nya meddelanden." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показувати сповіщення, коли ви отримуєте нові повідомлення." + } } } }, @@ -187467,6 +188607,12 @@ "value" : "Gillar du {app_name}?" } }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name}'i beğendiniz mi?" + } + }, "uk" : { "stringUnit" : { "state" : "translated", @@ -187592,6 +188738,12 @@ "value" : "Behöver förbättras {emoji}" } }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geliştirilmesi Gerekiyor {emoji}" + } + }, "uk" : { "stringUnit" : { "state" : "translated", @@ -187717,6 +188869,12 @@ "value" : "Det är fantastiskt {emoji}" } }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Harika {emoji}" + } + }, "uk" : { "stringUnit" : { "state" : "translated", @@ -187871,11 +189029,47 @@ "value" : "Daxil ol" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vstoupit" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bestätigen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Enter" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verder" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Войти" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enter" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Увійти" + } } } }, @@ -190623,11 +191817,47 @@ "value" : "Əks-əlaqə" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zpětná vazba" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Feedback" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Feedback" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Feedback" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отзыв" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Feedback" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відгук" + } } } }, @@ -190640,11 +191870,47 @@ "value" : "Qısa anketi dolduraraq {app_name} ilə təcrübənizi paylaşın." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Podělte se o své zkušenosti s {app_name} vyplněním krátkého dotazníku." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Teile deine Erfahrungen mit {app_name}, indem du eine kurze Umfrage ausfüllst." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Share your experience with {app_name} by completing a short survey." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deel je ervaring met {app_name} door een korte enquête in te vullen." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поделитесь своим опытом использования {app_name}, пройдя короткий опрос." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dela med dig av din upplevelse med {app_name} genom att fylla i en kort undersökning." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поділіться вашим досвідом використання {app_name} пройшовши коротке опитування." + } } } }, @@ -191615,11 +192881,53 @@ "value" : "Sistem ayarlarını izlə." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Použít nastavení systému." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Systemeinstellungen übernehmen." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Follow system settings." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Systeeminstellingen volgen." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Urmărește setările sistemului." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Использовать настройки системы." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Följ systeminställningen." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Використовувати системні налаштування." + } } } }, @@ -196474,6 +197782,12 @@ "value" : "정말로 {group_name}을 제거하시겠습니까?

모든 멤버가 제거되고 모든 그룹 컨텐츠가 삭제됩니다." } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er du sikker på at du vil slette {group_name}?

Dette vil fjerne alle medlemmer og slette alt av innholdet i gruppen." + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -203862,6 +205176,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "ناردنی بانگ" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "ناردنی بانگه کان" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -203890,6 +205232,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sender invitasjon" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sender invitasjoner" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -204836,6 +206206,12 @@ "value" : "초대 상태를 알 수 없습니다" } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Status på invitasjonen er ukjent" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -214265,6 +215641,12 @@ "value" : "あなた{other_name} はグループに招待されました。チャット履歴が共有されました。" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "당신{other_name}이 그룹에 초대 되었습니다. 대화 내용이 공유 되었습니다." + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -231952,6 +233334,18 @@ "value" : "연결 후보 처리 중" } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "مامەڵەکردن لەگەڵ کاندیدەکانی پەیوەندی" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "مامەڵەکردن لەگەڵ کاندیدەکانی پەیوەندی" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -232508,11 +233902,47 @@ "value" : "Ümumi suallara cavab tapmaq üçün {app_name} TVS-yə baxın." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odpovědi na časté otázky najdete v sekci FAQ {app_name}." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sieh dir die {app_name}-FAQ an, um Antworten auf häufig gestellte Fragen zu erhalten." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Check the {app_name} FAQ for answers to common questions." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bekijk de {app_name} FAQ voor antwoorden op veelgestelde vragen." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ознакомьтесь с часто задаваемыми вопросами {app_name}, чтобы найти ответы на распространённые вопросы." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kolla in FAQ på {app_name} för svar på vanliga frågor." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Перегляд ЧЗП {app_name} для перегляду відповідей на часті запитання." + } } } }, @@ -232998,11 +234428,59 @@ "value" : "Bir xəta bildir" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nahlásit chybu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einen Fehler melden" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Report a Bug" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meld een bug" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Raportează o eroare" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сообщить об ошибке" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rapportera ett fel" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hata Bildir" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Повідомити про помилку" + } } } }, @@ -234943,11 +236421,47 @@ "value" : "Bu faylı saxlayın, sonra onu {app_name} gəlişdiriciləri ilə paylaşın." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uložte tento soubor, poté jej sdílejte s vývojáři aplikace {app_name}." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Speichere diese Datei und teile sie dann mit den {app_name} Entwicklern." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Save this file, then share it with {app_name} developers." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sla dit bestand op en deel het vervolgens met de {app_name} ontwikkelaars." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сохраните этот файл, затем поделитесь им с разработчиками {app_name}." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spara denna fil, dela den sedan med {app_name} utvecklarna." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Збережіть цей файл, а потім надішліть його розробникам {app_name}." + } } } }, @@ -235439,11 +236953,47 @@ "value" : "{app_name} tətbiqini 80-dən çox dildə tərcümə etməyə kömək edin!" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pomozte přeložit {app_name} do více než 80 jazyků!" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hilf mit, {app_name} in über 80 Sprachen zu übersetzen!" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Help translate {app_name} into over 80 languages!" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Help met het vertalen van {app_name} in meer dan 80 talen!" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Помогите перевести {app_name} на более чем 80 языков!" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hjälp till att översätta {app_name} till över 80 språk!" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Допоможіть перекласти {app_name} на більше ніж 80 мов!" + } } } }, @@ -236414,11 +237964,41 @@ "value" : "Sistem menyu çubuğunun görünməsini dəyişdir." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Přepínač viditelnosti lišty systémového menu." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Toggle system menu bar visibility." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zichtbaarheid systeem-menubalk in-/uitschakelen." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Спрятать или показать системное меню." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Växla synlighet för systemmenyraden." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Видимість панелі меню." + } } } }, @@ -237711,11 +239291,29 @@ "value" : "Vacib" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Důležité" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Important" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Belangrijk" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Важливо" + } } } }, @@ -240108,6 +241706,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "بانگهێشتکردن شکستی هێنا" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "بانگهێشتەکان شکستی هێنا" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -240136,6 +241762,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invitasjon mislykket" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invitasjoner mislykket" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -240943,6 +242597,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "بانگهێشتنامەکە نەتوانرا بنێردرێت. حەز دەکەیت هەوڵی دووبارە بدەیتەوە؟" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "بانگهێشتنامەکان نەتوانرا بنێردرێت. حەز دەکەیت هەوڵی دووبارە بدەیتەوە؟" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -240971,6 +242653,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invitasjon kunne ikke bli sent. Vil du prøve på nytt?" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invitasjoner kunne ikke bli sent. Vil du prøve på nytt?" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -242277,6 +243987,12 @@ "state" : "translated", "value" : "Launch {app_name} automatically when your computer starts up." } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматично запускати {app_name} під час увімкнення компʼютера." + } } } }, @@ -242289,11 +244005,23 @@ "value" : "Açılışda başlat" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spuštění při startu systému" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Launch on Startup" } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автозапуск при старті системи" + } } } }, @@ -242311,6 +244039,12 @@ "state" : "translated", "value" : "This setting is managed by your system on Linux. To enable automatic startup, add {app_name} to your startup applications in system settings." } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Цим параметром у Linux керує ваша система. Щоб увімкнути автоматичний запуск, додайте {app_name} до програм автозапуску в системних параметрах." + } } } }, @@ -244497,7 +246231,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "El historial de chat no se transferirá al nuevo grupo. Todavía puedes ver todo el historial de chat en tu grupo antiguo." + "value" : "El historial de chat no se transferirá al nuevo grupo. Aún puedes ver todo el historial de chat en tu antiguo grupo." } }, "es-ES" : { @@ -252277,11 +254011,35 @@ "value" : "Keçidlər" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odkazy" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Links" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Koppelingen" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Länkar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Посилання" + } } } }, @@ -258054,11 +259812,35 @@ "value" : "Log-lar" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logy" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Logs" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logboeken" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loggar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Журнали" + } } } }, @@ -258232,11 +260014,29 @@ "value" : "{pro} - idarə et" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spravovat {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Manage {pro}" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} beheren" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Налаштування {pro}" + } } } }, @@ -268468,11 +270268,47 @@ "value" : "Menyu çubuğu" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Panel menu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Menüleiste" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Menu Bar" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Menubalk" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Панель меню" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Menyrad" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Панель меню" + } } } }, @@ -269101,11 +270937,47 @@ "value" : "Mesajı kopyala" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kopírovat zprávu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nachricht kopieren" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Copy Message" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bericht kopiëren" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Копировать текст сообщения" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kopiera meddelande" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Копіювати повідомлення" + } } } }, @@ -278084,6 +279956,24 @@ } } }, + "nb" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Du har fått en ny melding i {group_name}." + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Du har fått %lld nye meldinger i {group_name}." + } + } + } + } + }, "nl" : { "variations" : { "plural" : { @@ -292386,6 +294276,24 @@ } } }, + "nb" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meldinger har en tegngrense på {limit} tegn. Du har %lld tegn igjen." + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meldinger har en tegngrense på {limit} tegn. Du har %lld tegn igjen." + } + } + } + } + }, "nl" : { "variations" : { "plural" : { @@ -293292,11 +295200,35 @@ "value" : "Yeni parol" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nové heslo" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "New Password" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nieuw wachtwoord" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nytt Lösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Новий пароль" + } } } }, @@ -293788,11 +295720,29 @@ "value" : "Növbəti addımlar" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Další kroky" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Next Steps" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Volgende stappen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подальші кроки" + } } } }, @@ -299301,11 +301251,47 @@ "value" : "Bildiriş nümayişi" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazení upozornění" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Notification Display" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notificatie weergave" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vizualizare notificări" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отображение уведомлений" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aviseringsvisning" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сповіщення" + } } } }, @@ -302198,11 +304184,47 @@ "value" : "Mesajın göndərənin adı və mesaj məzmununun bir önizləməsi nümayiş olunsun." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit jméno odesílatele a náhled obsahu zprávy." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeigt den Namen des Absenders und eine Vorschau des Nachrichteninhalts an." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Display the sender's name and a preview of the message content." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toon de naam van de afzender en een voorbeeld van de berichtinhoud." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показывать имя отправителя и предварительный просмотр содержимого сообщения." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visa avsändarens namn och en förhandsvisning av meddelandets innehåll." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показувати ім’я відправника та стислий вміст повідомлення." + } } } }, @@ -302215,11 +304237,41 @@ "value" : "Heç bir mesaj məzmunu olmadan yalnız mesajı göndərənin adı nümayiş olunsun." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit pouze jméno odesílatele bez obsahu zprávy." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Display only the sender's name without any message content." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toon alleen de naam van de afzender zonder enige berichtinhoud." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отображать только имя отправителя без содержимого сообщения." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visa endast avsändarens namn utan något meddelandeinnehåll." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показувати лише ім'я відправника без вмісту повідомлення." + } } } }, @@ -303824,11 +305876,47 @@ "value" : "Mesajı göndərənin adı və ya mesajın məzmunu olmadan ümumi {app_name} bildirişi nümayiş olunsun." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit obecné oznámení {app_name} bez jména odesílatele a obsahu zprávy." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeige eine allgemeine {app_name}-Benachrichtigung ohne Namen des Absenders oder Nachrichteninhalt an." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Display a generic {app_name} notification without the sender's name or message content." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toon een algemene {app_name} melding zonder de naam van de afzender of de inhoud van het bericht." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показывать общие уведомления {app_name} без имени отправителя и содержимого сообщения." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visa en generell {app_name}-avisering utan avsändarens namn eller meddelandets innehåll." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показувати типове сповіщення {app_name} без імені відправника або вмісту повідомлення." + } } } }, @@ -307164,11 +309252,47 @@ "value" : "Yeni mesaj aldığınız zaman bir səs oxudulsun." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Přehrát zvuk při přijetí nových zpráv." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einen Ton abspielen, wenn neue Nachrichten empfangen werden." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Play a sound when you receive receive new messages." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Speel een geluid af wanneer je nieuwe berichten ontvangt." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Воспроизводить звук при получении новых сообщений." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spela upp ett ljud när du får nya meddelanden." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відтворювати звук, коли ви отримуєте нові повідомлення." + } } } }, @@ -324280,11 +326404,23 @@ "value" : "{device_type} cihazınızda" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Na vašem zařízení {device_type}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "On your {device_type} device" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Op je {device_type} apparaat" + } } } }, @@ -324297,11 +326433,23 @@ "value" : "Başda qeydiyyatdan keçdiyiniz {platform_account} hesabına giriş etdiyiniz {device_type} cihazından bu {app_name} hesabını açın. Sonra planınızı {app_pro} ayarları vasitəsilə dəyişdirin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otevřete tento účet {app_name} na zařízení {device_type}, které je přihlášeno do účtu {platform_account}, pomocí kterého jste se původně zaregistrovali. Poté změňte svůj tarif v nastavení {app_pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Open this {app_name} account on an {device_type} device logged into the {platform_account} you originally signed up with. Then, change your plan via the {app_pro} settings." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open dit {app_name} account op een {device_type} apparaat waarop je bent aangemeld met het {platform_account} waarmee je je oorspronkelijk hebt geregistreerd. Wijzig vervolgens je abonnement via de instellingen van {app_pro}." + } } } }, @@ -328643,11 +330791,23 @@ "value" : "{platform_store} veb saytını aç" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otevřít webovou stránku {platform_store}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Open {platform_store} Website" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open de {platform_store} website" + } } } }, @@ -329264,11 +331424,53 @@ "value" : "Parol" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heslo" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwort" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Password" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wachtwoord" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parolă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароль" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароль" + } } } }, @@ -329766,11 +331968,65 @@ "value" : "Parolunuz dəyişdirilib. Lütfən onu güvəndə saxlayın." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your password has been changed. Please keep it safe." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dein Passwort wurde geändert. Bitte bewahre es sicher auf." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your password has been changed. Please keep it safe." } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tu contraseña ha sido cambiada. Por favor, guárdala en un lugar seguro." + } + }, + "es-ES" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tu contraseña ha sido cambiada. Por favor, guárdala en un lugar seguro." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uw wachtwoord is gewijzigd. Hou het veilig." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parola ta a fost modificata. Securizați-va parola." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль был изменен. Пожалуйста, храните его в безопасном месте." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ditt lösenord har ändrats. Håll det säkert." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль змінено. Будь ласка, зберігайте його надійно." + } } } }, @@ -329783,11 +332039,47 @@ "value" : "{app_name} kilidini açmaq üçün tələb olunan parolu dəyişdir." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Změnit heslo pro odemykání {app_name}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Change the password required to unlock {app_name}." } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obligation de changer le mot de passe pour déverrouiller {app_name}." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wijzig het wachtwoord dat nodig is om {app_name} te ontgrendelen." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Измените пароль, необходимый для разблокировки {app_name}." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ändra lösenordet som krävs att låsa upp {app_name}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Змінити пароль, необхідний для розблокування {app_name}." + } } } }, @@ -330285,11 +332577,59 @@ "value" : "Parol yarat" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vytvořit heslo" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwort erstellen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Create Password" } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créer un mot de passe" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wachtwoord aanmaken" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Создать пароль" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skapa lösenord" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Şifre Oluştur" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Створити пароль" + } } } }, @@ -332781,12 +335121,24 @@ "value" : "Parola trebuie să aibă între {min} și {max} caractere." } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароль должен содержать от {min} до {max} символов" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Lösenordet måste vara mellan {min} och {max} tecken långt" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароль має містити від {min} до {max} символів" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -334247,11 +336599,53 @@ "value" : "Yeni parolu təsdiqlə" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Potvrďte nové heslo" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neues Passwort wiederholen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Confirm New Password" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bevestig nieuwe wachtwoord" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Confirmați noua parolă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подтвердите новый пароль" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bekräfta nytt lösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Підтвердити новий пароль" + } } } }, @@ -334743,11 +337137,53 @@ "value" : "Parolunuz silinib." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaše heslo bylo odebráno." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dein Passwort wurde entfernt." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your password has been removed." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uw wachtwoord is verwijderd." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parola ta a fost ștearsă." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль удалён." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ditt lösenord har tagits bort." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль видалено." + } } } }, @@ -334760,11 +337196,41 @@ "value" : "{app_name} kilidini açmaq üçün tələb olunan parolu sil" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odebrat heslo pro odemykání {app_name}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Remove the password required to unlock {app_name}" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verwijder het wachtwoord dat nodig is om {app_name} te ontgrendelen" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Удалить пароль, необходимый для разблокировки {app_name}" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ta bort lösenordet som krävs för att låsa upp {app_name}" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Видалити пароль, потрібний для розблокування {app_name}" + } } } }, @@ -334777,11 +337243,53 @@ "value" : "Parollar" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hesla" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwörter" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Passwords" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wachtwoorden" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parole" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароли" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Паролі" + } } } }, @@ -335273,11 +337781,53 @@ "value" : "Parolunuz təyin edilib. Lütfən onu güvəndə saxlayın." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaše heslo bylo nastaveno. Pečlivě si jej uložte." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dein Passwort wurde festgelegt. Bitte bewahre es sicher auf." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your password has been set. Please keep it safe." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uw wachtwoord is ingesteld. Hou het veilig." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parola ta a fost setata. Securizați-va parola." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль установлен. Пожалуйста, храните его в безопасном месте." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ditt lösenord har angetts. Håll det säkert." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль встановлено. Будь ласка, зберігайте його надійно." + } } } }, @@ -335290,11 +337840,41 @@ "value" : "Açılışda {app_name} kilidini açmaq üçün parol tələb edilsin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vyžadovat heslo k odemknutí {app_name} při spuštění." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Require password to unlock {app_name} on startup." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wachtwoord vereisen om {app_name} bij het opstarten te ontgrendelen." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Требовать пароль для разблокировки {app_name} при запуске." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kräv ett lösenord för ett låsa upp {app_name} vid start." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вимагати пароль для розблокування {app_name} при вході." + } } } }, @@ -335307,11 +337887,53 @@ "value" : "12 xarakterdən uzun" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delší než 12 znaků" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Länger als 12 Zeichen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Longer than 12 characters" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Langer dan 12 tekens" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mai mare de 12 caractere" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Длина больше 12 символов" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Längre än 12 tecken" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Довший 12 символів" + } } } }, @@ -335324,11 +337946,53 @@ "value" : "Bir rəqəm ehtiva etməlidir" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obsahuje číslici" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enthält eine Zahl" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Includes a number" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bevat een cijfer" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Include un număr" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Содержит цифру" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inkluderar en siffra" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Містить цифру" + } } } }, @@ -335341,11 +338005,53 @@ "value" : "Bir kiçik hərf ehtiva etməlidir" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obsahuje malé písmeno" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enthält einen Kleinbuchstaben" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Includes a lowercase letter" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bevat een kleine letter" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Include o literă mică" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Содержит строчную букву" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inkluderar en liten bokstav" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Містить літеру нижнього регістру" + } } } }, @@ -335358,11 +338064,23 @@ "value" : "Bir simvol daxildir" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obsahuje symbol" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Includes a symbol" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bevat een symbool" + } } } }, @@ -335375,11 +338093,53 @@ "value" : "Bir böyük hərf ehtiva etməlidir" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obsahuje velké písmeno" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enthält einen Großbuchstaben" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Includes a uppercase letter" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bevat een hoofdletter" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Include o literă mare" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Содержит заглавную букву" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inkluderar en stor bokstav" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Містить літеру верхнього регістру" + } } } }, @@ -335392,11 +338152,53 @@ "value" : "Parol gücü göstəricisi" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Indikátor síly hesla" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwortstärke-Anzeige" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Password Strength Indicator" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wachtwoordsterkte indicator" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Indicator de parolă puternică" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Индикатор надёжности пароля" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Indikator för lösenordsstyrka" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Індикатор надійності паролю" + } } } }, @@ -335409,11 +338211,47 @@ "value" : "Güclü bir parol təyin etmək, cihazınız itsə və ya oğurlansa belə mesajlarınızı və qoşmalarınızı qorumağa kömək edir." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nastavení silného hesla pomáhá chránit vaše zprávy a přílohy v případě ztráty nebo odcizení zařízení." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Setting a strong password helps protect your messages and attachments if your device is ever lost or stolen." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Een sterk wachtwoord helpt je berichten en bijlagen te beschermen als je apparaat ooit verloren raakt of wordt gestolen." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Setarea unei parole puternice ajută la protejarea mesajelor și fișierelor în cazul pierderii sau furtului dispozitivului dumneavoastră." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Надёжный пароль помогает защитить ваши сообщения и вложения в случае утери или кражи устройства." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Att skapa ett starkt lösenord hjälper till att skydda dina meddelanden och bilagor om din enhet skulle gå förlorad eller bli stulen." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Встановлення надійного пароля допомагає захистити ваші повідомлення та вкладення у разі втрати або крадіжки пристрою." + } } } }, @@ -335950,7 +338788,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Cambio de permiso" + "value" : "Cambiar Permisos" } }, "es-ES" : { @@ -339908,11 +342746,65 @@ "value" : "Pəncərəni bağladığınız zaman {app_name} arxaplanda çalışmağa davam edir." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} pokračuje v běhu na pozadí, když zavřete okno." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} läuft im Hintergrund weiter, wenn du das Fenster schließt." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{app_name} continues running in the background when you close the window." } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} continue à fonctionner en arrière-plan lorsque vous fermez la fenêtre." + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} fortsetter å kjøre i bakgrunnen når du lukker vinduet." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} blijft op de achtergrond draaien wanneer je het venster sluit." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} продолжит работать в фоновом режиме даже после закрытия окна." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} fortsätter köras i bakgrunden när du stänger fönstret." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} pencereyi kapattığınızda arka planda çalışmaya devam eder." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} продовжує працювати у фоновому режимі, коли ви його згортає." + } } } }, @@ -340443,7 +343335,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Se requiere acceso a la red local para facilitar las llamadas. Activa el permiso de \"Red local\" en Configuración para continuar." + "value" : "Se requiere acceso a la Red Local para realizar llamadas. En Configuración, active el permiso de \"Red Local\" para continuar." } }, "es-ES" : { @@ -340914,7 +343806,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir acceso a la red local para facilitar llamadas de voz y video." + "value" : "Permitir acceso a la red local para realizar llamadas de voz y video." } }, "es-ES" : { @@ -341075,7 +343967,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Red local" + "value" : "Red Local" } }, "es-ES" : { @@ -349469,11 +352361,29 @@ "value" : "Üstəgəl daha çoxu gəlir..." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plus načte další..." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Plus Loads More..." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plus laad meer..." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Та багато іншого..." + } } } }, @@ -349486,11 +352396,29 @@ "value" : "{pro} üçün yeni özəlliklər tezliklə gəlir. {icon} {pro} Yol Xəritəsində yenilikləri kəşf edin" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nové funkce {pro} již brzy. Podívejte se, co chystáme, na plánu vývoje {pro} {icon}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "New features coming soon to {pro}. Discover what's next on the {pro} Roadmap {icon}" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nieuwe functies komen binnenkort naar {pro}. Ontdek wat er komt op de {pro} Roadmap {icon}" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нові можливості незабаром виникнуть у {pro}. Пізнай, що буде далі, у дороговказі {pro} {icon}" + } } } }, @@ -349503,11 +352431,65 @@ "value" : "Tərcihlər" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Předvolby" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einstellungen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Preferences" } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preferencias" + } + }, + "es-ES" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preferencias" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voorkeuren" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preferințe" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Предпочтения" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inställningar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Налаштування" + } } } }, @@ -349999,11 +352981,47 @@ "value" : "Bildirişi önizlə" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Náhled upozornění" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Benachrichtigungsvorschau" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Preview Notification" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voorbeeldmelding" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Предпросмотр уведомления" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Förhandsgranska avisering" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Попередній перегляд сповіщень" + } } } }, @@ -350141,11 +353159,29 @@ "value" : "Hər şey hazırdır!" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vše je nastaveno!" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "You're all set!" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alles is geregeld!" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Готово!" + } } } }, @@ -350158,11 +353194,29 @@ "value" : "{app_pro} planınız güncəlləndi! Hazırkı {pro} planınız avtomatik olaraq {date} tarixində yeniləndiyi zaman ödəniş haqqı alınacaq." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {app_pro} byl aktualizován! Účtování proběhne při automatickém obnovení vašeho aktuálního tarifu {pro} dne {date}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {app_pro} plan was updated! You will be billed when your current {pro} plan is automatically renewed on {date}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {app_pro} abonnement is bijgewerkt! Je wordt gefactureerd wanneer je huidige {pro} abonnement automatisch wordt verlengd op {date}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Твою підписку {app_pro} оновлено. {date} коли підписку {pro} буде подовжено, тоді й стягнуть гроші." + } } } }, @@ -350652,12 +353706,24 @@ "value" : "Poză de profil animată" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Анимированное изображение профиля" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Animerad visningsbild" } }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil Onur Seçin" + } + }, "uk" : { "stringUnit" : { "state" : "translated", @@ -350818,11 +353884,29 @@ "value" : "Animasiyalı ekran şəkilləri" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Animované zobrazované obrázky" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Animated Display Pictures" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geanimeerde profielfoto's" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Анімовані зображення облікового запису" + } } } }, @@ -350835,11 +353919,29 @@ "value" : "Animasiyalı GIF və WebP təsvirlərini ekran şəklini olaraq təyin edin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nastavte si animované obrázky GIF a WebP jako svůj zobrazovaný profilový obrázek." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Set animated GIFs and WebP images as your display picture." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stel geanimeerde GIF's en WebP-afbeeldingen in als je profielfoto." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Встановлювати анімовані зображення GIF та WebP як ваше зображення облікового запису." + } } } }, @@ -350983,11 +354085,29 @@ "value" : "{pro}, {time} tarixində avto-yenilənir" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} se automaticky obnoví za {time}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} auto-renewing in {time}" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} wordt automatisch verlengd over {time}" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} автоматично оновиться за {time}" + } } } }, @@ -351000,11 +354120,29 @@ "value" : "{pro} nişanı" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odznak {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} Badge" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} badge" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Значок {pro}" + } } } }, @@ -351017,11 +354155,29 @@ "value" : "Nişanlar" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odznaky" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Badges" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Badges" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Позначки" + } } } }, @@ -351034,11 +354190,29 @@ "value" : "Ekran adınızın yanında eksklüziv bir nişanla {app_name} tətbiqini dəstəklədiyinizi göstərin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vyjádřete svou podporu {app_name} pomocí exkluzivního odznaku vedle svého zobrazovaného jména." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Show your support for {app_name} with an exclusive badge next to your display name." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toon je steun voor {app_name} met een exclusieve badge naast je schermnaam." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Продемонструйте свою підтримку {app_name} з ексклюзивним значком поруч з власним іменем." + } } } }, @@ -351073,6 +354247,46 @@ } } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} odznaky odeslány" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} odznaků odesláno" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} odznak odeslán" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} odznaků odesláno" + } + } + } + } + } + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -351100,6 +354314,34 @@ } } } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} Merke Sendt" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} Merker Sendt" + } + } + } + } + } + } } } }, @@ -351112,11 +354354,29 @@ "value" : "{app_pro} nişanını digər istifadəçilərə göstər" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit odznak {app_pro} ostatním uživatelům" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Show {app_pro} badge to other users" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toon het {app_pro} badge aan andere gebruikers" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показувати значок {app_pro} іншим користувачам" + } } } }, @@ -351129,11 +354389,29 @@ "value" : "{price} - illik haqq" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} účtováno ročně" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{price} Billed Annually" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} Jaarlijks gefactureerd" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} сплата щорічно" + } } } }, @@ -351146,11 +354424,29 @@ "value" : "{price} - aylıq haqq" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} účtováno měsíčně" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{price} Billed Monthly" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} Maandelijks gefactureerd" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} сплата щомісячно" + } } } }, @@ -351163,11 +354459,29 @@ "value" : "{price} - rüblük haqq" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} účtováno čtvrtletně" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{price} Billed Quarterly" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} per kwartaal gefactureerd" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} сплата щоквартально" + } } } }, @@ -351579,11 +354893,23 @@ "value" : "{platform_account} geri ödəmə tələbinizi emal edir" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_account} zpracovává vaši žádost o vrácení peněz" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{platform_account} is processing your refund request" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_account} verwerkt je restitutieverzoek" + } } } }, @@ -351596,11 +354922,29 @@ "value" : "Hazırkı planınızda artıq\r\ntam {app_pro} qiymətinin {percent}% endirimi mövcuddur." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš aktuální tarif je již zlevněn o {percent} % z plné ceny {app_pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your current plan is already discounted by {percent}% of the full {app_pro} price." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je huidige abonnement is al met {percent}% korting ten opzichte van de volledige {app_pro} prijs." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "На поточну підписку ти вже маєш знижку {percent}% від загальної ціни {app_pro}." + } } } }, @@ -351613,11 +354957,29 @@ "value" : "Müddəti bitib" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Platnost vypršela" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Expired" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verlopen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Підписка сплила" + } } } }, @@ -351630,11 +354992,29 @@ "value" : "Təəssüf ki, {pro} planınızın müddəti bitib. {app_pro} tətbiqinin eksklüziv imtiyazlarına və özəlliklərinə erişimi davam etdirmək üçün yeniləyin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bohužel, váš tarif {pro} vypršel. Obnovte jej, abyste nadále měli přístup k exkluzivním výhodám a funkcím {app_pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Unfortunately, your {pro} plan has expired. Renew to keep accessing the exclusive perks and features of {app_pro}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Helaas is je {pro} abonnement verlopen. Verleng om toegang te blijven houden tot de exclusieve voordelen en functies van {app_pro}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "На жаль, підписка {pro} сплила. Онови її задля збереження переваг і можливостей {app_pro}." + } } } }, @@ -351647,11 +355027,29 @@ "value" : "Tezliklə bitir" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brzy vyprší" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Expiring Soon" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verloopt binnenkort" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Невдовзі спливе підписка" + } } } }, @@ -351664,11 +355062,29 @@ "value" : "{pro} planınızın müddəti {time} vaxtında bitir. {app_pro} tətbiqinin eksklüziv imtiyazlarına və özəlliklərinə erişimi davam etdirmək üçün planınızı güncəlləyin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {pro} vyprší za {time}. Aktualizujte si tarif, abyste i nadále měli přístup k exkluzivním výhodám a funkcím {app_pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {pro} plan is expiring in {time}. Update your plan to keep accessing the exclusive perks and features of {app_pro}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {pro} abonnement verloopt over {time}. Werk je abonnement bij om toegang te blijven houden tot de exclusieve voordelen en functies van {app_pro}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Підписка {pro} спливе {time}. Онови підписку задля збереження переваг і можливостей {app_pro}." + } } } }, @@ -351681,11 +355097,23 @@ "value" : "{pro}, {time} vaxtında başa çatır" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} vyprší za {time}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} expiring in {time}" } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} спливає за {time}" + } } } }, @@ -351698,11 +355126,29 @@ "value" : "{pro} TVS" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} FAQ" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} FAQ" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} FAQ" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} ЧАП" + } } } }, @@ -351715,11 +355161,29 @@ "value" : "{app_name} TVS-da tez-tez verilən suallara cavab tapın." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Najděte odpovědi na časté dotazy v nápovědě {app_name}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Find answers to common questions in the {app_name} FAQ." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vind antwoorden op veelgestelde vragen in de {app_name} FAQ." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відповіді на загальні запитання знайдеш у ЧаПи {app_name}." + } } } }, @@ -352399,11 +355863,29 @@ "value" : "{pro} özəllikləri" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Funkce {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} Features" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} functies" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Можливості {pro}" + } } } }, @@ -355380,6 +358862,12 @@ "value" : "Grup activat" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Группа активирована" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", @@ -355499,6 +358987,12 @@ "value" : "Acest grup are capacitate extinsă! Poate susține până la 300 de membri deoarece un administrator de grup are" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "У этой группы увеличена вместимость! Теперь она поддерживает до 300 участников, потому что администратор группы активировал" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", @@ -355556,6 +359050,46 @@ } } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} skupiny navýšeny" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} skupin navýšeno" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} skupina navýšena" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} skupin navýšeno" + } + } + } + } + } + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -355583,6 +359117,34 @@ } } } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} Gruppe Oppgradert" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} Grupper Oppgradert" + } + } + } + } + } + } } } }, @@ -355595,11 +359157,29 @@ "value" : "Geri ödəniş tələbi qətidir. Əgər təsdiqlənsə, {pro} planınız dərhal ləğv ediləcək və bütün {pro} özəlliklərinə erişimi itirəcəksiniz." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žádost o vrácení peněz je konečné. Pokud bude schváleno, váš tarif {pro} bude ihned zrušen a ztratíte přístup ke všem funkcím {pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Requesting a refund is final. If approved, your {pro} plan will be canceled immediately and you will lose access to all {pro} features." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Het aanvragen van een terugbetaling is definitief. Indien goedgekeurd, wordt je {pro} abonnement onmiddellijk geannuleerd en verlies je de toegang tot alle {pro} functies." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вимагання повернення грошей закінчено. В разі схвалення твою підписку {pro} негайно скасують і ти втратиш всі можливості {pro}." + } } } }, @@ -355696,6 +359276,12 @@ "value" : "Dimensiune mărită a atașamentului" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Увеличенный размер вложений" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", @@ -355815,6 +359401,12 @@ "value" : "Lungime extinsă a mesajului" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Увеличенная длина сообщения" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", @@ -355850,11 +359442,29 @@ "value" : "Daha böyük qruplar" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Větší skupiny" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Larger Groups" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Grotere groepen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Більші групи" + } } } }, @@ -355867,11 +359477,29 @@ "value" : "Admin olduğunuz qruplar, avtomatik olaraq 300 üzvü dəstəkləmək üçün təkmilləşdirilir." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skupiny, ve kterých jste správcem, jsou automaticky navýšeny na kapacitu až 300 členů." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Groups you are an admin in are automatically upgraded to support 300 members." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Groepen waarvan jij beheerder bent, worden automatisch geüpgraded om 300 leden te ondersteunen." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Групи, у яких ви є адміністратором, автоматично оновлюються для підтримки до 300 учасників." + } } } }, @@ -355884,11 +359512,29 @@ "value" : "Daha uzun mesajlar" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delší zprávy" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Longer Messages" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Langere berichten" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Довші повідомлення" + } } } }, @@ -355901,11 +359547,29 @@ "value" : "Bütün danışıqlarda 10,000 xarakterə qədər mesaj göndərə bilərsiniz." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ve všech konverzacích můžete posílat zprávy až o délce 10 000 znaků." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "You can send messages up to 10,000 characters in all conversations." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je kunt berichten tot 10.000 tekens verzenden in alle gesprekken." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ви можете надсилати повідомлення до 10 000 символів у всіх розмовах." + } } } }, @@ -355940,6 +359604,46 @@ } } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} delší zprávy odeslány" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} delších zpráv odesláno" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} delší zpráva odeslána" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} delších zpráv odesláno" + } + } + } + } + } + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -355967,6 +359671,34 @@ } } } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} Lengre Melding Sendt" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} Lengre Meldinger Sendt" + } + } + } + } + } + } } } }, @@ -356063,6 +359795,12 @@ "value" : "Acest mesaj a folosit următoarele funcționalități {app_pro}:" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Это сообщение использовало следующие функции {app_pro}:" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", @@ -357041,6 +360779,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forfremmelse mislykket" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forfremmelser mislykket" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -357848,6 +361614,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forfremmelsen kunne ikke bli påført. Vil du prøve på nytt?" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forfremmelser kunne ikke bli påført. Vil du prøve på nytt?" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -358191,11 +361985,23 @@ "value" : "{percent}% endirim" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sleva {percent} %" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{percent}% Off" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{percent}% korting" + } } } }, @@ -358230,6 +362036,46 @@ } } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} připnuté konverzace" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} připnutých konverzací" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} připnutá konverzace" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} připnutých konverzací" + } + } + } + } + } + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -358257,6 +362103,34 @@ } } } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} Samtale Festet" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} Samtaler Festet" + } + } + } + } + } + } } } }, @@ -358269,11 +362143,29 @@ "value" : "{app_pro} planınız aktivdir!

Planınız avtomatik olaraq {date} tarixində başqa bir {current_plan} üçün yenilənəcək. Planınıza edilən güncəlləmələr növbəti {pro} yenilənməsi zamanı qüvvəyə minəcək." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {app_pro} je aktivní!

Váš tarif se automaticky obnoví na další {current_plan} dne {date}. Změny tarifu se projeví při příštím obnovení {pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {app_pro} plan is active!

Your plan will automatically renew for another {current_plan} on {date}. Updates to your plan take effect when {pro} is next renewed." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {app_pro} abonnement is actief!

Je abonnement wordt automatisch verlengd voor een nieuw {current_plan} op {date}. Wijzigingen aan je abonnement gaan in wanneer {pro} de volgende keer wordt verlengd." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Для тебе діє підписка {app_pro}.

{date} твою підписку буде самодійно поновлено як {current_plan}. Оновлення підписки настане під час наступного оновлення {pro}." + } } } }, @@ -358286,11 +362178,29 @@ "value" : "{app_pro} planınız aktivdir!

Planınız avtomatik olaraq {date} tarixində başqa bir {current_plan} üçün yenilənəcək." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {app_pro} je aktivní!

Tarif se automaticky obnoví na další {current_plan} dne {date}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {app_pro} plan is active!

Your plan will automatically renew for another {current_plan} on {date}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {app_pro} abonnement is actief!

Je abonnement wordt automatisch verlengd met een {current_plan} op {date}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Для тебе діє підписка {app_pro}.

{date} твою підписку буде самодійно поновлено як {current_plan}." + } } } }, @@ -358303,11 +362213,29 @@ "value" : "{app_pro} planınızın müddəti {date} tarixində bitir.

Eksklüziv Pro özəlliklərinə kəsintisiz erişimi təmin etmək üçün planınızı indi güncəlləyin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {app_pro} vyprší dne {date}.

Aktualizujte si tarif nyní, abyste měli i nadále nepřerušený přístup k exkluzivním funkcím Pro." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {app_pro} plan will expire on {date}.

Update your plan now to ensure uninterrupted access to exclusive Pro features." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {app_pro} abonnement verloopt op {date}.

Werk je abonnement nu bij om ononderbroken toegang te behouden tot exclusieve Pro functies." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Твоя підписка {app_pro} спливе {date}.

Для збереження особливих можливостей подовж свою підписку." + } } } }, @@ -358320,11 +362248,29 @@ "value" : "{app_pro} planınızın müddəti {date} tarixində bitir." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {app_pro} vyprší dne {date}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {app_pro} plan will expire on {date}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {app_pro} abonnement verloopt op {date}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Підписка {app_pro} спливе {date}." + } } } }, @@ -358337,11 +362283,29 @@ "value" : "{pro} planı tapılmadı" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} nebyl nalezen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} Plan Not Found" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} abonnement niet gevonden" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Передплата {pro} не знайдена" + } } } }, @@ -358354,11 +362318,23 @@ "value" : "Hesabınız üçün heç bir aktiv plan tapılmadı. Bunun bir səhv olduğunu düşünürsünüzsə, lütfən kömək üçün {app_name} dəstəyi ilə əlaqə saxlayın." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pro váš účet nebyl nalezen žádný aktivní tarif. Pokud si myslíte, že se jedná o chybu, kontaktujte podporu {app_name} a požádejte o pomoc." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "No active plan was found for your account. If you believe this is a mistake, please reach out to {app_name} support for assistance." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er is geen actief abonnement gevonden voor je account. Als je denkt dat dit een vergissing is, neem dan contact op met de ondersteuning van {app_name} voor hulp." + } } } }, @@ -358371,11 +362347,23 @@ "value" : "Başda {platform_store} Mağazası üzərindən {app_pro} üçün qeydiyyatdan keçdiyinizə görə, geri ödəmə tələbini göndərmək üçün eyni {platform_account} hesabını istifadə etməlisiniz." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protože jste se původně zaregistrovali do {app_pro} přes obchod {platform_store}, budete muset pro žádost o vrácení peněz použít stejný účet {platform_account}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Because you originally signed up for {app_pro} via the {platform_store} Store, you'll need to use the same {platform_account} to request a refund." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Omdat je je oorspronkelijk hebt aangemeld voor {app_pro} via de {platform_store} winkel, moet je hetzelfde {platform_account} gebruiken om een terugbetaling aan te vragen." + } } } }, @@ -358388,11 +362376,23 @@ "value" : "Başda {platform_store} Mağazası üzərindən {app_pro} üçün qeydiyyatdan keçdiyinizə görə, geri qaytarma tələbiniz {app_name} Dəstək komandası tərəfindən icra olunacaq.

Aşağıdakı düyməyə basaraq və geri ödəniş formunu dolduraraq geri ödəmə tələbinizi göndərin.

{app_name} Dəstək komandası, geri ödəmə tələblərini adətən 24-72 saat ərzində emal edir, yüksək tələb həcminə görə bu proses daha uzun çəkə bilər." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protože jste si původně zakoupili {app_pro} přes obchod {platform_store}, váš požadavek na vrácení peněz bude zpracován podporou {app_name}.

Požádejte o vrácení peněz kliknutím na tlačítko níže a vyplněním formuláře žádosti o vrácení peněz.

Ačkoliv se podpora {app_name} snaží zpracovat žádosti o vrácení peněz během 24–72 hodin, může zpracování trvat i déle, pokud dochází k vysokému počtu žádostí." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Because you originally signed up for {app_pro} via the {platform_store} Store, your refund request will be processed by {app_name} Support.

Request a refund by hitting the button below and completing the refund request form.

While {app_name} Support strives to process refund requests within 24-72 hours, processing may take longer during times of high request volume." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Omdat je je oorspronkelijk hebt aangemeld voor {app_pro} via de {platform_store} winkel, wordt je restitutieverzoek afgehandeld door {app_name} Support.

Vraag een restitutie aan door op de knop hieronder te drukken en het restitutieformulier in te vullen.

Hoewel {app_name} Support ernaar streeft om restitutieverzoeken binnen 24-72 uur te verwerken, kan het tijdens drukte langer duren" + } } } }, @@ -358405,11 +362405,29 @@ "value" : "{pro} planını geri qaytar" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Znovu nabýt tarif {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Recover {pro} Plan" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} abonnement herstellen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відновити передплату {pro}" + } } } }, @@ -358422,11 +362440,29 @@ "value" : "{pro} planını yenilə" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obnovit tarif {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Renew {pro} Plan" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} abonnement verlengen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оновити підписку {pro}" + } } } }, @@ -358439,11 +362475,23 @@ "value" : "Hazırda, {pro} planları, yalnızca {platform_store} və {platform_store} Mağazaları vasitəsilə satın alına və yenilənə bilər. {app_name} Masaüstü istifadə etdiyinizə görə planınızı burada yeniləyə bilməzsiniz.

{app_pro} gəlişdiriciləri, istifadəçilərin {pro} planlarını {platform_store} və {platform_store} Mağazalarından kənarda almağına imkan verəcək alternativ ödəniş variantları üzərində ciddi şəkildə çalışırlar. {pro} Yol Xəritəsi" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "V současnosti lze tarify {pro} zakoupit a obnovit pouze prostřednictvím obchodů {platform_store} nebo {platform_store}. Protože používáte {app_name} Desktop, nemůžete zde svůj plán obnovit.

Vývojáři {app_pro} intenzivně pracují na alternativních platebních možnostech, které by uživatelům umožnily zakoupit tarify {pro} mimo obchody {platform_store} a {platform_store}. Plán vývoje {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Currently, {pro} plans can only be purchased and renewed via the {platform_store} or {platform_store} Stores. Because you are using {app_name} Desktop, you're not able to renew your plan here.

{app_pro} developers are working hard on alternative payment options to allow users to purchase {pro} plans outside of the {platform_store} and {platform_store} Stores. {pro} Roadmap" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Momenteel kunnen {pro} abonnementen alleen worden gekocht en verlengd via de {platform_store}- of {platform_store} winkels. Omdat je {app_name} Desktop gebruikt, kun je je abonnement hier niet verlengen.

De ontwikkelaars van {app_pro} werken hard aan alternatieve betaalmogelijkheden, zodat gebruikers {pro} abonnementen buiten de {platform_store}- en {platform_store} winkels kunnen aanschaffen. {pro} Routekaart" + } } } }, @@ -358456,11 +362504,23 @@ "value" : "{platform_store} və ya {platform_store} Mağazaları vasitəsilə planınızı {app_name} quraşdırılmış və əlaqələndirilmiş cihazda {app_pro} ayarlarında yeniləyin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obnovte svůj tarif v nastavení {app_pro} na propojeném zařízení s nainstalovanou aplikací {app_name} prostřednictvím obchodu {platform_store} nebo {platform_store}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Renew your plan in the {app_pro} settings on a linked device with {app_name} installed via the {platform_store} or {platform_store} Store." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verleng je abonnement in de {app_pro} instellingen op een gekoppeld apparaat met {app_name} geïnstalleerd via de {platform_store} of {platform_store} winkel." + } } } }, @@ -358473,27 +362533,33 @@ "value" : "{pro} üçün qeydiyyatdan keçdiyiniz {platform_account} hesabınızla {platform_store} veb saytında planınızı yeniləyin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obnovte svůj tarif na webu {platform_store} pomocí účtu {platform_account}, se kterým jste si pořídili {pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Renew your plan on the {platform_store} website using the {platform_account} you signed up for {pro} with." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verleng je abonnement op de {platform_store} website met het {platform_account} waarmee je je voor {pro} hebt aangemeld." + } } } }, "proPlanRenewStart" : { "extractionState" : "manual", "localizations" : { - "az" : { - "stringUnit" : { - "state" : "translated", - "value" : "Güclü {app_pro} özəlliklərini yenidən istifadə etməyə başlamaq üçün {app_pro} planınızı yeniləyin." - } - }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Renew your {app_pro} plan to start using powerful {app_pro} features again." + "value" : "Renew your {app_pro} plan to start using powerful {app_pro} Beta features again." } } } @@ -358507,11 +362573,23 @@ "value" : "{app_pro} planınız yeniləndi! {network_name} dəstək verdiyiniz üçün təşəkkürlər." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {app_pro} byl obnoven! Děkujeme, že podporujete síť {network_name}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {app_pro} plan has been renewed! Thank you for supporting the {network_name}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {app_pro} abonnement is verlengd! Bedankt voor je steun aan de {network_name}." + } } } }, @@ -358524,11 +362602,29 @@ "value" : "{pro} planı bərpa edildi" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} obnoven" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} Plan Restored" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} abonnement hersteld" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "План {pro} відновлено" + } } } }, @@ -358541,11 +362637,23 @@ "value" : "{app_pro} üçün yararlı bir plan aşkarlandı və {pro} statusunuz bərpa edildi!" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Byl rozpoznán platný tarif {app_pro} a váš stav {pro} byl obnoven!" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "A valid plan for {app_pro} was detected and your {pro} status has been restored!" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Een geldig abonnement voor {app_pro} is gedetecteerd en je {pro} status is hersteld!" + } } } }, @@ -358558,11 +362666,23 @@ "value" : "Başda {platform_store} Mağazası üzərindən {app_pro} üçün qeydiyyatdan keçdiyinizə görə planınızı həmin {platform_account} vasitəsilə güncəlləməlisiniz." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protože jste se původně zaregistrovali do {app_pro} přes {platform_store}, je třeba abyste pro aktualizaci vašeho tarifu použili svůj {platform_account}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Because you originally signed up for {app_pro} via the {platform_store} Store, you'll need to use your {platform_account} to update your plan." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Omdat je je oorspronkelijk hebt aangemeld voor {app_pro} via de {platform_store} winkel, moet je je {platform_account} gebruiken om je abonnement bij te werken." + } } } }, @@ -358575,11 +362695,29 @@ "value" : "1 ay - {monthly_price}/ay" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 měsíc – {monthly_price} / měsíc" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "1 Month - {monthly_price} / Month" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 maand - {monthly_price} / maand" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 місяць — {monthly_price} / місяць" + } } } }, @@ -358592,11 +362730,29 @@ "value" : "3 ay - {monthly_price}/ay" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "3 měsíce – {monthly_price} / měsíc" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "3 Months - {monthly_price} / Month" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "3 maanden - {monthly_price} / maand" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "3 місяці — {monthly_price} / місяць" + } } } }, @@ -358609,11 +362765,29 @@ "value" : "12 ay - {monthly_price}/ay" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "12 měsíců – {monthly_price} / měsíc" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "12 Months - {monthly_price} / Month" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "12 maanden - {monthly_price} / maand" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "12 місяців – {monthly_price} / місяць" + } } } }, @@ -358626,11 +362800,29 @@ "value" : "Getməyinizə məyus olduq. Geri ödəmə tələb etməzdən əvvəl bilməli olduğunuz şeylər." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mrzí nás, že to rušíte. Než požádáte o vrácení peněz, přečtěte si informace, které byste měli vědět." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "We’re sorry to see you go. Here's what you need to know before requesting a refund." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Het spijt ons dat je vertrekt. Dit moet je weten voordat je een terugbetaling aanvraagt." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шкода, же ти передумав(ла). Перед вимогою повернення грошей ти мусиш знати ось що." + } } } }, @@ -358643,11 +362835,29 @@ "value" : "{pro} geri ödəməsi" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vracení peněz za {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Refunding {pro}" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terugbetalen {pro}" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Повернення грошей за {pro}" + } } } }, @@ -358660,11 +362870,23 @@ "value" : "{app_pro} planları üçün geri ödəmələr yalnız {platform_store} Mağazası vasitəsilə {platform_account} tərəfindən həyata keçirilir.

{platform_account} geri ödəniş siyasətlərinə əsasən, {app_name} gəlişdiriciləri, geri ödəniş tələblərinin nəticəsinə təsir edə bilməz. Bu, tələbin qəbul olunub-olunmaması ilə yanaşı, tam və ya qismən geri ödənişin verilib-verilməməsini də əhatə edir." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vrácení peněz za tarify {app_pro} je vyřizováno výhradně prostřednictvím {platform_account} v obchodě {platform_store}.

Vzhledem k pravidlům vracení peněz služby {platform_account} nemají vývojáři {app_name} žádný vliv na výsledek žádostí o vrácení peněz. To zahrnuje i rozhodnutí, zda bude žádost schválena nebo zamítnuta, a také, zda bude vrácena část peněz, nebo všechny peníze." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Refunds for {app_pro} plans are handled exclusively by {platform_account} through the {platform_store} Store.

Due to {platform_account} refund policies, {app_name} developers have no ability to influence the outcome of refund requests. This includes whether the request is approved or denied, as well as whether a full or partial refund is issued." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terugbetalingen voor {app_pro} abonnementen worden uitsluitend afgehandeld door {platform_account} via de {platform_store} Store.

Vanwege het restitutiebeleid van {platform_account} hebben ontwikkelaars van {app_name} geen invloed op de uitkomst van terugbetalingsverzoeken. Dit geldt zowel voor de goedkeuring of afwijzing van het verzoek als voor het wel of niet toekennen van een volledige of gedeeltelijke terugbetaling." + } } } }, @@ -358677,11 +362899,23 @@ "value" : "{platform_account} hazırda geri ödəniş tələbinizi emal edir. Bu, adətən 24-48 saat çəkir. Onların qərarından asılı olaraq, {app_name} tətbiqində {pro} statusunuzun dəyişdiyini görə bilərsiniz." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_account} nyní zpracovává vaši žádost o vrácení peněz. Obvykle to trvá 24–48 hodin. V závislosti na jejich rozhodnutí se může váš stav {pro} v aplikaci {app_name} změnit." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{platform_account} is now processing your refund request. This typically takes 24-48 hours. Depending on their decision, you may see your {pro} status change in {app_name}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_account} verwerkt nu je terugbetalingsverzoek. Dit duurt meestal 24-48 uur. Afhankelijk van hun beslissing kan je {pro} status wijzigen in {app_name}." + } } } }, @@ -358694,11 +362928,23 @@ "value" : "Geri qaytarma tələbiniz {app_name} Dəstək komandası tərəfindən icra olunacaq.

Aşağıdakı düyməyə basaraq və geri ödəniş formunu dolduraraq geri ödəniş tələbinizi göndərin.

{app_name} Dəstək komandası, geri ödəniş tələblərini adətən 24-72 saat ərzində emal edir, yüksək tələb həcminə görə bu proses daha uzun çəkə bilər." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš požadavek na vrácení peněz bude zpracován podporou {app_name}.

Požádejte o vrácení peněz kliknutím na tlačítko níže a vyplněním formuláře žádosti o vrácení peněz.

Ačkoliv se podpora {app_name} snaží zpracovat žádosti o vrácení peněz během 24–72 hodin, může zpracování trvat i déle, v případě že je vyřizováno mnoho žádostí." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your refund request will be handled by {app_name} Support.

Request a refund by hitting the button below and completing the refund request form.

While {app_name} Support strives to process refund requests within 24-72 hours, processing may take longer during times of high request volume." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je restitutieverzoek wordt afgehandeld door {app_name} Support.

Vraag een restitutie aan door op de knop hieronder te drukken en het restitutieformulier in te vullen.

Hoewel {app_name} Support ernaar streeft om restitutieverzoeken binnen 24-72 uur te verwerken, kan het tijdens drukte langer duren." + } } } }, @@ -358711,11 +362957,23 @@ "value" : "Geri ödəniş tələbiniz yalnız {platform_account} veb saytında {platform_account} hesabı üzərindən icra olunacaq.

{platform_account} geri ödəniş siyasətlərinə əsasən, {app_name} gəlişdiriciləri, geri ödəniş tələblərinin nəticəsinə təsir edə bilməz. Bu, tələbin qəbul olunub-olunmaması ilə yanaşı, tam və ya qismən geri ödənişin verilib-verilməməsini də əhatə edir." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaši žádost o vrácení peněz bude vyřizovat výhradně {platform_account} prostřednictvím webových stránek {platform_account}.

Vzhledem k pravidlům vracení peněz {platform_account} nemají vývojáři {app_name} žádný vliv na výsledek žádostí o vrácení peněz. To zahrnuje i rozhodnutí, zda bude žádost schválena nebo zamítnuta, a také, zda bude vrácena část peněz, nebo všechny peníze." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your refund request will be handled exclusively by {platform_account} through the {platform_account} website.

Due to {platform_account} refund policies, {app_name} developers have no ability to influence the outcome of refund requests. This includes whether the request is approved or denied, as well as whether a full or partial refund is issued." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je terugbetalingsverzoek wordt uitsluitend afgehandeld door {platform_account} via de website van {platform_account}.

Vanwege het restitutiebeleid van {platform_account} hebben ontwikkelaars van {app_name} geen invloed op de uitkomst van terugbetalingsverzoeken. Dit geldt zowel voor de goedkeuring of afwijzing van het verzoek als voor het wel of niet toekennen van een volledige of gedeeltelijke terugbetaling." + } } } }, @@ -358728,11 +362986,23 @@ "value" : "Geri ödəmə tələbinizlə bağlı daha çox güncəlləmə üçün lütfən {platform_account} ilə əlaqə saxlayın. {platform_account} geri ödəniş siyasətlərinə əsasən, {app_name} gəlişdiriciləri, geri ödəniş tələblərinin nəticəsinə təsir edə bilməz.

{platform_store} Geri ödəmə dəstəyi" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pro další informace o vaší žádosti o vrácení peněz kontaktujte prosím {platform_account}. Vzhledem k zásadám pro vrácení peněz {platform_account} nemají vývojáři aplikace {app_name} žádnou možnost ovlivnit výsledek žádosti o vrácení.

Podpora vrácení peněz {platform_store}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Please contact {platform_account} for further updates on your refund request. Due to {platform_account} refund policies, {app_name} developers have no ability to influence the outcome of refund requests.

{platform_store} Refund Support" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neem contact op met {platform_account} voor verdere updates over je restitutieverzoek. Vanwege het restitutiebeleid van {platform_account} hebben de ontwikkelaars van {app_name} geen invloed op de uitkomst van restitutieverzoeken.

{platform_store} Terugbetalingsondersteuning" + } } } }, @@ -358745,11 +363015,29 @@ "value" : "Geri ödəmə tələb edildi" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žádost o vrácení peněz" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Refund Requested" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terugbetaling aangevraagd" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вимогу повернення грошей надіслано" + } } } }, @@ -358899,11 +363187,29 @@ "value" : "{pro} ayarları" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nastavení {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} Settings" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} instellingen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Налаштування {pro}" + } } } }, @@ -358916,11 +363222,29 @@ "value" : "{pro} statistikalarınız" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaše statistiky {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {pro} Stats" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {pro} statistieken" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваша статистика {pro}" + } } } }, @@ -358933,11 +363257,29 @@ "value" : "{pro} statistikaları, bu cihazdakı istifadəni əks-etdirir və əlaqələndirilmiş cihazlarda fərqli görünə bilər." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Statistiky {pro} ukazují používání na tomto zařízení a mohou se lišit na jiných propojených zařízeních" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} stats reflect usage on this device and may appear differently on linked devices" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} statistieken weerspiegelen het gebruik op dit apparaat en kunnen anders weergegeven worden op gekoppelde apparaten" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Звіти підписки {pro} відображають використання лише цього пристрою, тож, мабуть, матимуть иншого вигляду на инших пристроях" + } } } }, @@ -358950,11 +363292,29 @@ "value" : "{pro} planınızla bağlı kömək lazımdır? Dəstək komandamıza müraciət edin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Potřebujete pomoc se svým tarifem {pro}? Pošlete žádost týmu podpory." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Need help with your {pro} plan? Submit a request to the support team." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hulp nodig met je {pro} abonnement? Dien een verzoek in bij het ondersteuningsteam." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Якщо потребуєш допомоги щодо підписки {pro}, надійшли звернення до відділу підтримки." + } } } }, @@ -358967,11 +363327,29 @@ "value" : "Güncəlləyərək, {app_pro} Xidmət Şərtləri {icon} və Məxfilik Siyasəti {icon} ilə razılaşırsınız" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualizací souhlasíte s Podmínkami služby {icon} a Zásadami ochrany osobních údajů {icon} {app_pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "By updating, you agree to the {app_pro} Terms of Service {icon} and Privacy Policy {icon}" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Door bij te werken ga je akkoord met de {app_pro} Gebruiksvoorwaarden {icon} en het Privacybeleid {icon}" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Цією дією ти надаси згоду щодо дотримання Правил послуги {app_pro} {icon} і Ставлення до особистих відомостей {icon}" + } } } }, @@ -358984,11 +363362,29 @@ "value" : "Limitsiz sancma" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neomezený počet připnutí" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Unlimited Pins" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Onbeperkte Pins" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Необмежена кількість закріплених бесід" + } } } }, @@ -359001,11 +363397,29 @@ "value" : "Limitsiz sancılmış danışıqla bütün söhbətlərinizi təşkil edin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Organizujte si komunikaci pomocí neomezeného počtu připnutých konverzací." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Organize all your chats with unlimited pinned conversations." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Organiseer al je chats met onbeperkt vastgezette gesprekken." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Закріплення необмеженої кількості співрозмовників в головному переліку." + } } } }, @@ -359018,11 +363432,23 @@ "value" : "Hazırda {current_plan} Planı üzərindəsiniz. {selected_plan} Planınana keçmək istədiyinizə əminsiniz?

Güncəlləsəniz, planınız {date} tarixində əlavə {selected_plan} {pro} erişimi üçün avtomatik yenilənəcək." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "V současné době jste na tarifu {current_plan}. Jste si jisti, že chcete přepnout na tarif {selected_plan}?

Po aktualizaci bude váš tarif automaticky obnoven {date} na další {selected_plan} přístupu {pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "You are currently on the {current_plan} Plan. Are you sure you want to switch to the {selected_plan} Plan?

By updating, your plan will automatically renew on {date} for an additional {selected_plan} of {pro} access." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je zit momenteel op het {current_plan} abonnement. Weet je zeker dat je wilt overschakelen naar het {selected_plan} abonnement?

Als je dit bijwerkt, wordt je abonnement op {date} automatisch verlengd met {selected_plan} {pro} toegang." + } } } }, @@ -359035,11 +363461,23 @@ "value" : "Planınız {date} tarixində bitəcək.

Güncəlləsəniz, planınız {date} tarixində əlavə {selected_plan} Pro erişimi üçün avtomatik olaraq yenilənəcək." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif vyprší {date}.

Po aktualizaci se váš tarif automaticky obnoví {date} na další {selected_plan} přístupu Pro." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your plan will expire on {date}.

By updating, your plan will automatically renew on {date} for an additional {selected_plan} of Pro access." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je abonnement verloopt op {date}.

Door bij te werken wordt je abonnement automatisch verlengd op {date} voor een extra {selected_plan} aan Pro-toegang." + } } } }, @@ -359136,12 +363574,24 @@ "value" : "Vrei să profiți mai mult de {app_name}? Fă upgrade la {app_pro} pentru o experiență de mesagerie mai puternică." } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Хотите больше возможностей от {app_name}? Перейдите на {app_pro}, чтобы получить более мощный опыт обмена сообщениями." + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Vill du få ut mer av {app_name}? Uppgradera till {app_pro} för en kraftfullare meddelandeupplevelse." } }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name}'den daha fazla yararlanmak ister misiniz? Daha güçlü bir mesajlaşma deneyimi için {app_pro}'ya yükseltin." + } + }, "uk" : { "stringUnit" : { "state" : "translated", @@ -365390,6 +369840,18 @@ "value" : "응답 수신됨" } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "وەڵامی وەرگیراو" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "وەڵامی وەرگیراو" + } + }, "nb" : { "stringUnit" : { "state" : "translated", @@ -365575,6 +370037,18 @@ "value" : "통화 제안 받는 중" } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "پێشنیاری پەیوەندی بنێرە" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "پێشنیاری پەیوەندی بنێرە" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -367244,11 +371718,53 @@ "value" : "Hesabınızı yeni cihazlara yükləmək üçün geri qaytarma parolunuzu istifadə edin.

Geri qaytarma parolunuz olmadan hesabınız geri qaytarıla bilməz. Parolu təhlükəsiz və etibarlı yerdə saxladığınıza əmin olun və heç kəslə paylaşmayın." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Použijte své heslo pro obnovení pro načtení účtu na nových zařízeních.

Bez hesla pro obnovení nelze obnovit účet. Ujistěte se, že je uložené na bezpečném místě — a nesdílejte ho s nikým." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verwende dein Wiederherstellungspasswort, um deinen Account auf neue Geräten zu laden.

Dein Account kann ohne dein Wiederherstellungspasswort nicht wiederhergestellt werden. Stelle sicher, dass es an einem sicheren Ort aufbewahrt ist – und teile es niemandem mit." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Use your recovery password to load your account on new devices.

Your account cannot be recovered without your recovery password. Make sure it's stored somewhere safe and secure — and don't share it with anyone." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gebruik uw herstelwachtwoord om uw account op nieuwe apparaten te laden.

Uw account kan niet worden hersteld zonder uw herstelwachtwoord. Zorg ervoor dat het ergens veilig is opgeslagen – en deel het met niemand." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Используйте пароль восстановления, чтобы загрузить свою учетную запись на новых устройствах.

Ваша учетная запись не может быть восстановлена без пароля восстановления. Убедитесь, что он хранится в безопасном месте — и не делитесь им ни с кем." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Använd ditt återställningslösenord för att ladda ditt konto på nya enheter.

Ditt konto kan inte återställas utan ditt återställningslösenord. Se till att det lagras på en säker och trygg plats — och dela det inte med någon." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kurtarma şifrenizi kullanarak hesabınızı yeni cihazlara yükleyin.

Kurtarma şifreniz olmadan hesabınız kurtarılamaz. Şifrenizi güvenli bir yerde sakladığınızdan emin olun ve kimseyle paylaşmayın." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Використовуйте пароль для відновлення для завантаження свого облікового запису на нових пристроях.

Ваш обліковий запис не може бути відновлений без пароля для відновлення. Переконайтеся, що він зберігається у надійному місці та не передавайте його нікому." + } } } }, @@ -371266,11 +375782,65 @@ "value" : "Geri qaytarma parolunuzu bu cihazdan həmişəlik gizlətmək istədiyinizə əminsiniz?

Bunun geri dönüşü yoxdur." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opravdu chcete trvale skrýt heslo pro obnovení na tomto zařízení?

Tuto akci nelze vrátit." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bist du sicher, dass du dein Wiederherstellungspasswort auf diesem Gerät dauerhaft ausblenden möchtest?

Dies kann nicht mehr rückgängig gemacht werden." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Are you sure you want to permanently hide your recovery password on this device?

This cannot be undone." } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Êtes-vous sûr de vouloir cacher définitivement votre mot de passe de récupération sur cet appareil ?

Cette action est irréversible." + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er du sikker på at du har lyst til å permanent skjule gjenopprettingspassordet ditt?

Dette kan ikke angres." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Weet u zeker dat u uw herstelwachtwoord permanent wilt verbergen op dit apparaat?

Dit kan niet ongedaan gemaakt worden." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вы уверены, что хотите навсегда скрыть ваш пароль восстановления на этом устройстве?

Это действие не может быть отменено." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Är du säker på att du vill dölja ditt återställningslösenord permanent på denna enhet?

Detta kan inte ångras." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu cihazda kurtarma şifrenizi kalıcı olarak gizlemek istediğinizden emin misiniz?

Bu işlem geri alınamaz." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ви впевнені, що хочете назавжди приховати пароль для відновлення на цьому пристрої?

Цю дію неможливо скасувати." + } } } }, @@ -372720,11 +377290,53 @@ "value" : "Geri qaytarma paroluna bax" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit heslo pro obnovení" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wiederherstellungspasswort anzeigen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "View Recovery Password" } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vis Gjenopprettingspassord" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bekijk Herstelwachtwoord" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показать пароль восстановления" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visa återställningslösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Перегляд паролю для відновлення" + } } } }, @@ -372737,11 +377349,53 @@ "value" : "Geri qaytarma parolu görünməsi" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Viditelnost hesla pro obnovení" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sichtbarkeit des Wiederherstellungspassworts" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Recovery Password Visibility" } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gjenopprettingspassord Synlighet" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zichtbaarheid herstelwachtwoord" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Видимость пароля восстановления" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Synlighet för återställningslösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Видимість пароля для відновлення" + } } } }, @@ -373891,11 +378545,23 @@ "value" : "Başda fərqli {platform_account} vasitəsilə {app_pro} üçün qeydiyyatdan keçdiyinizə görə planınızı həmin {platform_account} vasitəsilə güncəlləməlisiniz." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protože jste se původně zaregistrovali do {app_pro} přes jiný {platform_account}, je třeba použít ten {platform_account}, abyste aktualizovali váš tarif." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Because you originally signed up for {app_pro} via a different {platform_account}, you'll need to use that {platform_account} to update your plan." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Omdat je je oorspronkelijk hebt aangemeld voor {app_pro} via een ander {platform_account}, moet je datzelfde {platform_account} gebruiken om je abonnement bij te werken." + } } } }, @@ -374267,6 +378933,24 @@ "value" : "{count}자 입력 가능" } }, + "nb" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld tegn igjen" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld tegn igjen" + } + } + } + } + }, "nl" : { "variations" : { "plural" : { @@ -375446,11 +380130,47 @@ "value" : "{app_name} üçün hazırkı parolunuzu silin. Daxili olaraq saxlanılmış verilər, cihazınızda saxlanılan təsadüfi yaradılmış açarla təkrar şifrələnəcək." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odstraňte své aktuální heslo pro {app_name}. Lokálně uložená data budou znovu zašifrována pomocí náhodně vygenerovaného klíče uloženého ve vašem zařízení." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entferne dein aktuelles Passwort für {app_name}. Lokal gespeicherte Daten werden mit einem zufällig generierten Schlüssel, der auf deinem Gerät gespeichert wird, erneut verschlüsselt." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Remove your current password for {app_name}. Locally stored data will be re-encrypted with a randomly generated key, stored on your device." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verwijder je huidige wachtwoord voor {app_name}. Lokaal opgeslagen gegevens worden opnieuw versleuteld met een willekeurig gegenereerde sleutel, opgeslagen op je apparaat." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Удалите текущий пароль для {app_name}. Локально сохранённые данные будут повторно зашифрованы с использованием случайно сгенерированного ключа, хранящегося на вашем устройстве." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ta bort ditt nuvarande lösenord för {app_name}. Lokalt lagrad data kommer att krypteras om med en slumpmässigt genererad nyckel som lagras på din enhet." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Видаліть свій поточний пароль для {app_name}. Локально збережені дані буде повторно зашифровано випадково згенерованим ключем, який зберігатиметься на вашому пристрої." + } } } }, @@ -375463,11 +380183,29 @@ "value" : "Yenilə" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obnovit" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Renew" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verlengen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поновити" + } } } }, @@ -375959,11 +380697,29 @@ "value" : "Geri ödəmə tələb et" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Požádat o vrácení platby" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Request Refund" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terugbetaling aanvragen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Запит на повернення коштів" + } } } }, @@ -390673,6 +395429,18 @@ "value" : "연결 후보 전송 중" } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "ناردنی کاندیدەکانی پەیوەندی" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ناردنی کاندیدەکانی پەیوەندی" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -397030,11 +401798,29 @@ "value" : "{app_pro} Beta" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} Beta" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{app_pro} Beta" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} Bèta" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} бета" + } } } }, @@ -398645,11 +403431,41 @@ "value" : "{app_name} üçün bir parol təyin edin. Daxili olaraq saxlanılmış verilər, bu parolla şifrələnəcək. {app_name} tətbiqini hər başlatdıqda, sizdən bu parolu daxil etməyiniz istənəcək." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nastavte heslo pro {app_name}. Lokálně uložená data budou šifrována tímto heslem. Při každém spuštění {app_name} budete vyzváni k zadání tohoto hesla." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Set a password for {app_name}. Locally stored data will be encrypted with this password. You will be asked to enter this password each time {app_name} starts." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stel een wachtwoord in voor {app_name}. Lokaal opgeslagen gegevens worden versleuteld met dit wachtwoord. Je wordt gevraagd dit wachtwoord in te voeren telkens wanneer {app_name} wordt gestart." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Установите пароль для {app_name}. Локально сохранённые данные будут зашифрованы с использованием этого пароля. При каждом запуске {app_name} вам потребуется вводить этот пароль." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ställ in ett lösenord för {app_name}. Lokalt lagrade data kommer att krypteras med detta lösenord. Du kommer att bli ombedd att ange detta lösenord varje gång {app_name} startas." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Встановіть пароль для {app_name}. Дані, збережені локально, буде зашифровано цим паролем. Цей пароль запитуватиметься при кожному запуску {app_name}." + } } } }, @@ -398662,11 +403478,23 @@ "value" : "Ayar güncəllənə bilmir" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nelze aktualizovat volbu" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Cannot Update Setting" } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Неможливо оновити налаштування" + } } } }, @@ -399637,11 +404465,23 @@ "value" : "Açılış" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spuštění" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Startup" } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автозапуск" + } } } }, @@ -403951,11 +408791,47 @@ "value" : "Yazı yoxlanışı" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kontrola pravopisu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rechtschreibprüfung" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Spell Checker" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spellingcontrole" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Проверка орфографии" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stavningskontroll" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Перевірка орфографії" + } } } }, @@ -404447,11 +409323,53 @@ "value" : "Gücü" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Síla" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwortstärke" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Strength" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sterkte" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Puternic" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Надёжность" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Styrka" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Надійність" + } } } }, @@ -404464,11 +409382,47 @@ "value" : "Problemlə üzləşmisiniz? Kömək məqalələrini oxuyun, ya da {app_name} Dəstək ilə bir sorğu açın." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Máte problémy? Projděte si články nápovědy nebo kontaktujte podporu {app_name}." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Probleme? Erkunde die Hilfeartikel oder öffne ein Ticket bei dem {app_name} Support." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Having issues? Explore help articles or open a ticket with {app_name} Support." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Problemen? Bekijk de hulpartikelen of open een ticket bij {app_name} Support." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Возникли проблемы? Ознакомьтесь со статьями в справке или отправьте запрос в службу поддержки {app_name}." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Har du problem? Utforska hjälpartiklar eller öppna en supportförfrågan hos {app_name}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Виникли проблеми? Перегляньте довідкові статті або створіть запит до служби підтримки {app_name}." + } } } }, @@ -405478,7 +410432,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Toca para reintentar" + "value" : "Toque para reintentar" } }, "es-ES" : { @@ -407061,11 +412015,47 @@ "value" : "Tema önizləməsi" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Náhled motivu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Design-Vorschau" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Theme Preview" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thema voorbeeld" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Предпросмотр темы" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Förhandsvisning av tema" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Попередній перегляд теми" + } } } }, @@ -407078,11 +412068,29 @@ "value" : "Qayıt" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zpět" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Return" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terug" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Зворотно" + } } } }, @@ -407357,11 +412365,47 @@ "value" : "Tərcümə et" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Překlad" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Übersetzen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Translate" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vertalen" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Перевод" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Översätt" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Переклад" + } } } }, @@ -407374,11 +412418,41 @@ "value" : "Sini" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lišta" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Tray" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Systeemvak" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Трей" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivitetsfält" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Область сповіщень" + } } } }, @@ -410504,12 +415578,24 @@ "value" : "Actualizează informațiile comunității" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обновить информацию о сообществе" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Uppdatera communityinformation" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оновити інформацію про спільноту" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -410617,12 +415703,24 @@ "value" : "Numele și descrierea comunității sunt vizibile pentru toți membrii comunității" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Название и описание Community видны всем участникам Community" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Communitynamn och beskrivning är synliga för alla communitymedlemmar" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Назву та опис спільноти бачать усі учасники" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -410730,12 +415828,24 @@ "value" : "Te rugăm să introduci o descriere a comunității mai scurtă" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Введите более короткое описание сообщества" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Vänligen ange en kortare communitybeskrivning" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Будь ласка, введіть коротший опис спільноти" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -410843,12 +415953,24 @@ "value" : "Te rugăm să introduci un nume al comunității mai scurt" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пожалуйста, введите более короткое название сообщества" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Vänligen ange ett kortare communitynamn" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Будь ласка, введіть коротшу назву спільноти" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -414025,6 +419147,12 @@ "value" : "Versîyoneke nû ({version}) ya {app_name} berdest e." } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "En ny versjon ({version}) av {app_name} er tilgjengelig." + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -414102,11 +419230,29 @@ "value" : "Planı güncəllə" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualizovat tarif" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Update Plan" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abonnement bijwerken" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оновити тарифний план" + } } } }, @@ -414119,11 +419265,29 @@ "value" : "Planınızı güncəlləməyin iki yolu var:" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dva způsoby, jak aktualizovat váš tarif:" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Two ways to update your plan:" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Twee manieren om je abonnement bij te werken:" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Два шляхи поновлення твоєї підписки:" + } } } }, @@ -414136,11 +419300,65 @@ "value" : "Profil məlumatlarını güncəllə" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Upravit informace profilu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profilinformationen aktualisieren" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Update Profile Information" } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualizar información de perfil" + } + }, + "es-ES" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualizar información de perfil" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profielinformatie bijwerken" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualizarea informațiilor de profil" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обновить информацию профиля" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uppdatera profilinformation" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оновити інформацію облікового запису" + } } } }, @@ -414153,11 +419371,53 @@ "value" : "Ekran adınız və ekran şəkliniz bütün danışıqlarda görünür." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaše zobrazované jméno a profilová fotka jsou viditelné ve všech konverzacích." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dein Anzeigename und Profilbild sind in allen Unterhaltungen sichtbar." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your display name and display picture are visible in all conversations." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je weergavenaam en profielfoto zijn zichtbaar in alle gesprekken." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Numele și poza de profil sunt vizibile în toate conversațiile tale." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваше отображаемое имя и фотография профиля видны во всех беседах." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ditt visningsnamn och din visningsbild är synliga i alla konversationer." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваше відображуване ім’я та зображення профілю видимі у всіх розмовах." + } } } }, @@ -414649,11 +419909,47 @@ "value" : "Güncəlləmələr" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualizace" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualisierungen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Updates" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Updates" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обновления" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uppdateringar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оновлення" + } } } }, @@ -415630,11 +420926,47 @@ "value" : "Güncəllənir..." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualizuji..." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wird aktualisiert..." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Updating..." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bijwerken..." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualizare..." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uppdaterar..." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оновлення..." + } } } }, @@ -418179,11 +423511,29 @@ "value" : "Keçidlər, brauzerinizdə açılacaq." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odkazy se otevřou ve vašem prohlížeči." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Links will open in your browser." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Links worden in uw browser geopend." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "За ланкою перейде твоє оглядало мережців за промовчання." + } } } }, @@ -418675,11 +424025,23 @@ "value" : "{platform_store} veb saytı vasitəsilə" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Přes webové stránky {platform_store}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Via the {platform_store} website" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Via de {platform_store} website" + } } } }, @@ -418692,11 +424054,23 @@ "value" : "Qeydiyyatdan keçərkən istifadə etdiyiniz {platform_account} hesabı ilə {platform_store} veb saytı üzərindən planınızı dəyişdirin." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Změňte svůj tarif pomocí {platform_account}, se kterým jste se zaregistrovali, prostřednictvím webu {platform_store}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Change your plan using the {platform_account} you used to sign up with, via the {platform_store} website." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wijzig je abonnement met het {platform_account} waarmee je je hebt aangemeld, via de {platform_store} website." + } } } }, @@ -423342,11 +428716,53 @@ "value" : "Geri qaytarma parolunuz" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaše heslo pro obnovení" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dein Wiederherstellungspasswort" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your Recovery Password" } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ditt Gjenopprettingspassord" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je herstelwachtwoord" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш Пароль Восстановления" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ditt återställningslösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль для відновлення" + } } } }, @@ -423359,11 +428775,47 @@ "value" : "Böyütmə amili" } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Měřítko přiblížení" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vergrößerungsfaktor" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Zoom Factor" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zoomfactor" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Масштабирование приложения" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zoomfaktor" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Масштаб" + } } } }, @@ -423376,11 +428828,47 @@ "value" : "Mətnin və vizual elementlərin ölçüsünü ayarla." } }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Upravte velikost textu a vizuálních prvků." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passe die Größe von Text und visuellen Elementen an." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Adjust the size of text and visual elements." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pas de grootte van tekst en visuele elementen aan." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Настройте размер текста и визуальных элементов." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Justera storleken på text och visuella element." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Налаштування розміру тексту та візуальних елементів." + } } } } From 8c634657b4841360ca58ab72177f552e55117cea Mon Sep 17 00:00:00 2001 From: mikoldin Date: Mon, 15 Sep 2025 09:04:54 +0800 Subject: [PATCH 48/66] Clean up update icon handlers --- Session/Settings/AppIconViewModel.swift | 29 +++++++------------------ 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/Session/Settings/AppIconViewModel.swift b/Session/Settings/AppIconViewModel.swift index 5a8386fab5..5ebefe1e55 100644 --- a/Session/Settings/AppIconViewModel.swift +++ b/Session/Settings/AppIconViewModel.swift @@ -137,12 +137,6 @@ class AppIconViewModel: SessionTableViewModel, NavigatableStateHolder, Observabl lazy var observation: TargetObservation = ObservationBuilderOld .subject(selectedOptionsSubject) .mapWithPrevious { [weak self, dependencies] previous, current -> [SectionModel] in - - if let currentIcon = current { - // Save latest app icon disguise selected - dependencies[defaults: .standard, key: .lastSelectedAppIconDisguise] = currentIcon - } - return [ SectionModel( model: .appIcon, @@ -158,9 +152,11 @@ class AppIconViewModel: SessionTableViewModel, NavigatableStateHolder, Observabl oldValue: (previous != nil) ), onTap: { [weak self] in + let lastSelected: String? = dependencies[defaults: .standard, key: .lastSelectedAppIconDisguise] + switch current { case .some: self?.updateAppIcon(nil) - case .none: self?.restorePreviousIcon(previous) // Previous is String?? + case .none: self?.updateAppIcon(lastSelected.map { AppIcon(name: $0) } ?? .weather) } } ) @@ -194,20 +190,11 @@ class AppIconViewModel: SessionTableViewModel, NavigatableStateHolder, Observabl } selectedOptionsSubject.send(icon?.rawValue) - } - - private func restorePreviousIcon(_ identifier: String??) { - var previousIcon: AppIcon? { - if let previousIcon = identifier { - // Set previous app icon - return AppIcon(name: previousIcon) - } else if let previousIcon = dependencies[defaults: .standard, key: .lastSelectedAppIconDisguise] { - // Handles app close instance to restore previously selected - return AppIcon(name: previousIcon) - } - return .weather - } - updateAppIcon(previousIcon) + // Only store custom icons + if let currentIconName = icon?.rawValue { + // Save latest app icon disguise selected + dependencies[defaults: .standard, key: .lastSelectedAppIconDisguise] = currentIconName + } } } From 5e384eb7fb1129ac34183530837917c156f21614 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 15 Sep 2025 14:22:39 +1000 Subject: [PATCH 49/66] Moved and renamed remaining API definitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Cleaned up API structures to be more consistent • Moved OpenGroupAPI into SessionNetworkingKit • Moved PushNotificationAPI into SessionNetworkingKit • Renamed to OpenGroupAPI to SOGSAPI --- Session.xcodeproj/project.pbxproj | 648 +++++--- .../Closed Groups/EditGroupViewModel.swift | 2 +- .../ConversationVC+Interaction.swift | 20 +- .../New Conversation/NewMessageScreen.swift | 2 +- .../MessageInfoScreen.swift | 2 +- Session/Meta/AppDelegate.swift | 2 +- Session/Notifications/SyncPushTokensJob.swift | 4 +- Session/Open Groups/JoinOpenGroupVC.swift | 7 +- .../Open Groups/OpenGroupSuggestionGrid.swift | 5 +- .../Settings/DeveloperSettingsViewModel.swift | 16 +- Session/Settings/NukeDataModal.swift | 6 +- .../SessionNetworkScreen+ViewModel.swift | 4 +- .../Crypto/Crypto+SessionMessagingKit.swift | 34 +- .../_023_SplitSnodeReceivedMessageInfo.swift | 4 +- .../_024_ResetUserConfigLastHashes.swift | 2 +- .../_036_GroupsRebuildChanges.swift | 25 +- .../Database/Models/Attachment.swift | 12 +- .../Database/Models/ClosedGroup.swift | 41 +- .../Database/Models/ConfigDump.swift | 22 +- .../Database/Models/OpenGroup.swift | 3 +- .../Jobs/AttachmentDownloadJob.swift | 2 +- .../Jobs/ConfigMessageReceiveJob.swift | 4 +- .../Jobs/ConfigurationSyncJob.swift | 6 +- .../Jobs/DisplayPictureDownloadJob.swift | 6 +- .../Jobs/ExpirationUpdateJob.swift | 2 +- .../Jobs/GarbageCollectionJob.swift | 2 +- .../Jobs/GetExpirationJob.swift | 2 +- .../Jobs/GroupLeavingJob.swift | 2 +- SessionMessagingKit/Jobs/MessageSendJob.swift | 2 +- ...ProcessPendingGroupMemberRemovalsJob.swift | 8 +- .../RetrieveDefaultOpenGroupRoomsJob.swift | 28 +- .../LibSession+GroupInfo.swift | 2 +- .../LibSession/Types/Config.swift | 2 +- .../Messages/Message+Destination.swift | 4 +- .../Messages/Message+Origin.swift | 2 +- SessionMessagingKit/Messages/Message.swift | 16 +- .../Open Groups/Crypto/Crypto+OpenGroup.swift | 87 - .../Open Groups/OpenGroupAPI.swift | 1396 ----------------- .../Open Groups/OpenGroupManager.swift | 60 +- .../Open Groups/Types/Capabilities.swift | 17 - .../Open Groups/Types/PendingChange.swift | 24 +- .../AttachmentUploader.swift | 28 +- .../MessageReceiver+Groups.swift | 6 +- .../MessageReceiver+UnsendRequests.swift | 2 +- .../MessageSender+Groups.swift | 23 +- .../Sending & Receiving/MessageSender.swift | 16 +- .../Models/LegacyGroupOnlyRequest.swift | 12 - .../Models/LegacyGroupRequest.swift | 10 - .../Models/LegacyNotifyRequest.swift | 15 - .../Models/LegacyPushServerResponse.swift | 10 - .../Models/LegacyUnsubscribeRequest.swift | 14 - .../PushNotificationAPI+SMK.swift | 127 ++ .../Notifications/PushNotificationAPI.swift | 320 ---- .../Pollers/CommunityPoller.swift | 88 +- .../Pollers/CurrentUserPoller.swift | 2 +- .../Pollers/GroupPoller.swift | 4 +- .../Pollers/PollerType.swift | 2 +- .../Pollers/SwarmPoller.swift | 16 +- .../MessageViewModel+DeletionActions.swift | 12 +- .../Authentication+SessionMessagingKit.swift | 45 +- .../Utilities/ExtensionHelper.swift | 4 +- .../Jobs/DisplayPictureDownloadJobSpec.swift | 2 +- ...RetrieveDefaultOpenGroupRoomsJobSpec.swift | 102 +- .../LibSession/LibSessionGroupInfoSpec.swift | 2 +- ...PISpec.swift => CryptoOpenGroupSpec.swift} | 159 +- ...ilitiesSpec.swift => CapabilitySpec.swift} | 28 +- .../Open Groups/OpenGroupManagerSpec.swift | 223 +-- .../Open Groups/Types/SOGSErrorSpec.swift | 24 - .../MessageReceiverGroupsSpec.swift | 49 +- .../MessageSenderGroupsSpec.swift | 53 +- .../_TestUtilities/MockOGMCache.swift | 2 +- .../_TestUtilities/MockPoller.swift | 2 +- .../_TestUtilities/MockSwarmPoller.swift | 2 +- .../Crypto/Crypto+SessionNetworkingKit.swift | 83 +- .../Models/SnodeReceivedMessageInfo.swift | 136 -- .../FileServer/AppVersionResponse.swift | 97 -- .../FileServer/Crypto/Crypto+FileServer.swift | 89 ++ .../FileServer/FileServer.swift | 40 +- .../FileServer/FileServerAPI.swift | 50 + .../FileServer/FileServerEndpoint.swift | 23 + .../Models/AppVersionResponse.swift | 162 +- .../LibSession/LibSession+Networking.swift | 10 +- .../Models/FileUploadResponse.swift | 2 + .../Crypto/Crypto+PushNotification.swift | 41 + .../Models/AuthenticatedRequest.swift | 4 +- .../Models/NotificationMetadata.swift | 22 +- .../Models/SubscribeRequest.swift | 11 +- .../Models/SubscribeResponse.swift | 16 +- .../Models/UnsubscribeRequest.swift | 3 +- .../Models/UnsubscribeResponse.swift | 16 +- .../PushNotification/PushNotification.swift | 29 + .../PushNotificationAPI.swift | 194 +++ .../PushNotificationEndpoint.swift | 4 +- .../Types/ProcessResult.swift | 2 +- .../Types/Request+PushNotificationAPI.swift | 9 +- .../PushNotification}/Types/Service.swift | 6 +- .../PushNotification}/Types/ServiceInfo.swift | 2 +- .../SOGS/Crypto/Crypto+SOGS.swift | 153 ++ .../SOGS/Models/CapabilitiesResponse.swift | 8 +- .../SOGS/Models/DeleteInboxResponse.swift | 9 + .../SOGS/Models/DirectMessage.swift | 34 + .../SOGS/Models/PinnedMessage.swift | 22 + .../SOGS/Models/ReactionResponse.swift | 44 + SessionNetworkingKit/SOGS/Models/Room.swift | 191 +++ .../SOGS/Models/RoomPollInfo.swift | 143 ++ .../SOGS/Models/SOGSMessage.swift | 11 +- .../Models/SendDirectMessageRequest.swift | 2 +- .../Models/SendDirectMessageResponse.swift | 4 +- .../SOGS/Models/SendSOGSMessageRequest.swift | 78 + .../SOGS/Models/UpdateMessageRequest.swift | 35 + .../SOGS/Models/UserBanRequest.swift | 2 +- .../SOGS/Models/UserModeratorRequest.swift | 2 +- .../SOGS/Models/UserUnbanRequest.swift | 2 +- SessionNetworkingKit/SOGS/SOGS.swift | 15 + SessionNetworkingKit/SOGS/SOGSAPI.swift | 229 ++- SessionNetworkingKit/SOGS/SOGSEndpoint.swift | 7 +- SessionNetworkingKit/SOGS/SOGSError.swift | 2 +- .../SOGS/Types/HTTPHeader+SOGS.swift | 1 - .../SOGS/Types/HTTPQueryParam+SOGS.swift | 1 - .../SOGS/Types/Personalization.swift | 14 + .../SOGS/Types/Request+SOGS.swift | 6 +- .../SOGS/Types/UpdateTypes.swift | 9 + .../SessionNetwork/SessionNetworkAPI.swift | 10 +- .../Database/SnodeReceivedMessageInfo.swift | 4 +- .../Models/AppVersionResponse.swift | 97 -- .../Models/DeleteAllBeforeRequest.swift | 10 +- .../Models/DeleteAllMessagesRequest.swift | 10 +- .../Models/DeleteMessagesRequest.swift | 6 +- .../Models/FileUploadResponse.swift | 29 - .../Models/GetExpiriesRequest.swift | 6 +- .../Models/GetMessagesRequest.swift | 10 +- .../Models/GetNetworkTimestampResponse.swift | 4 +- .../Models/LegacyGetMessagesRequest.swift | 6 +- .../Models/LegacySendMessageRequest.swift | 6 +- .../Models/ONSResolveRequest.swift | 4 +- .../Models/ONSResolveResponse.swift | 4 +- .../Models/OxenDaemonRPCRequest.swift | 32 +- .../Models/RevokeSubaccountRequest.swift | 6 +- .../Models/SendMessageRequest.swift | 10 +- .../SnodeAuthenticatedRequestBody.swift | 2 +- .../Models/SnodeBatchRequest.swift | 4 +- .../StorageServer/Models/SnodeMessage.swift | 62 - .../Models/SnodeReceivedMessage.swift | 68 - .../StorageServer/Models/SnodeRequest.swift | 4 +- .../Models/UnrevokeSubaccountRequest.swift | 6 +- .../Models/UpdateExpiryAllRequest.swift | 10 +- .../Models/UpdateExpiryRequest.swift | 6 +- .../StorageServer/SnodeAPI.swift | 799 +++++++++- .../StorageServer/SnodeAPIEndpoint.swift | 2 +- .../StorageServer/SnodeAPIError.swift | 2 +- .../StorageServer/SnodeAPINamespace.swift | 2 +- .../Types/Request+SnodeAPI.swift | 6 +- .../Types/SnodeReceivedMessage.swift | 4 +- SessionNetworkingKit/Types/Network.swift | 138 +- .../SOGS/Crypto/Authentication+SOGS.swift | 50 + .../SOGS/Crypto/CryptoSOGSAPISpec.swift | 180 +++ .../SOGS/Models/CapabilitiesResponse.swift | 38 + .../SOGS}/Models/RoomPollInfoSpec.swift | 10 +- .../SOGS}/Models/RoomSpec.swift | 6 +- .../SOGS}/Models/SOGSMessageSpec.swift | 29 +- .../Models/SendDirectMessageRequestSpec.swift | 4 +- .../Models/SendSOGSMessageRequestSpec.swift | 14 +- .../Models/UpdateMessageRequestSpec.swift | 6 +- .../SOGS/SOGSAPISpec.swift | 1211 +++++++------- .../SOGS}/Types/PersonalizationSpec.swift | 6 +- .../SOGS}/Types/SOGSEndpointSpec.swift | 60 +- .../SOGS/Types/SOGSErrorSpec.swift | 24 + .../CommonSSKMockExtensions.swift | 91 ++ .../_TestUtilities/MockNetwork.swift | 2 +- .../NotificationResolution.swift | 13 +- .../NotificationServiceExtension.swift | 14 +- SessionShareExtension/ThreadPickerVC.swift | 2 +- SessionTests/Onboarding/OnboardingSpec.swift | 2 +- 173 files changed, 4406 insertions(+), 4739 deletions(-) delete mode 100644 SessionMessagingKit/Open Groups/OpenGroupAPI.swift delete mode 100644 SessionMessagingKit/Open Groups/Types/Capabilities.swift delete mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupOnlyRequest.swift delete mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupRequest.swift delete mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyNotifyRequest.swift delete mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyPushServerResponse.swift delete mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI+SMK.swift delete mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift rename SessionMessagingKitTests/Open Groups/Crypto/{CryptoOpenGroupAPISpec.swift => CryptoOpenGroupSpec.swift} (61%) rename SessionMessagingKitTests/Open Groups/Models/{CapabilitiesSpec.swift => CapabilitySpec.swift} (73%) delete mode 100644 SessionMessagingKitTests/Open Groups/Types/SOGSErrorSpec.swift delete mode 100644 SessionNetworkingKit/Database/Models/SnodeReceivedMessageInfo.swift delete mode 100644 SessionNetworkingKit/FileServer/AppVersionResponse.swift create mode 100644 SessionNetworkingKit/PushNotification/Crypto/Crypto+PushNotification.swift rename {SessionMessagingKit/Sending & Receiving/Notifications => SessionNetworkingKit/PushNotification}/Models/AuthenticatedRequest.swift (97%) rename {SessionMessagingKit/Sending & Receiving/Notifications => SessionNetworkingKit/PushNotification}/Models/NotificationMetadata.swift (81%) rename {SessionMessagingKit/Sending & Receiving/Notifications => SessionNetworkingKit/PushNotification}/Models/SubscribeRequest.swift (96%) rename {SessionMessagingKit/Sending & Receiving/Notifications => SessionNetworkingKit/PushNotification}/Models/SubscribeResponse.swift (85%) rename {SessionMessagingKit/Sending & Receiving/Notifications => SessionNetworkingKit/PushNotification}/Models/UnsubscribeRequest.swift (98%) rename {SessionMessagingKit/Sending & Receiving/Notifications => SessionNetworkingKit/PushNotification}/Models/UnsubscribeResponse.swift (85%) create mode 100644 SessionNetworkingKit/PushNotification/PushNotification.swift create mode 100644 SessionNetworkingKit/PushNotification/PushNotificationAPI.swift rename SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift => SessionNetworkingKit/PushNotification/PushNotificationEndpoint.swift (80%) rename {SessionMessagingKit/Sending & Receiving/Notifications => SessionNetworkingKit/PushNotification}/Types/ProcessResult.swift (84%) rename {SessionMessagingKit/Sending & Receiving/Notifications => SessionNetworkingKit/PushNotification}/Types/Request+PushNotificationAPI.swift (68%) rename {SessionMessagingKit/Sending & Receiving/Notifications => SessionNetworkingKit/PushNotification}/Types/Service.swift (84%) rename {SessionMessagingKit/Sending & Receiving/Notifications => SessionNetworkingKit/PushNotification}/Types/ServiceInfo.swift (91%) create mode 100644 SessionNetworkingKit/SOGS/Models/DeleteInboxResponse.swift create mode 100644 SessionNetworkingKit/SOGS/Models/DirectMessage.swift create mode 100644 SessionNetworkingKit/SOGS/Models/PinnedMessage.swift create mode 100644 SessionNetworkingKit/SOGS/Models/ReactionResponse.swift create mode 100644 SessionNetworkingKit/SOGS/Models/Room.swift create mode 100644 SessionNetworkingKit/SOGS/Models/RoomPollInfo.swift create mode 100644 SessionNetworkingKit/SOGS/Models/SendSOGSMessageRequest.swift create mode 100644 SessionNetworkingKit/SOGS/Models/UpdateMessageRequest.swift create mode 100644 SessionNetworkingKit/SOGS/Types/Personalization.swift create mode 100644 SessionNetworkingKit/SOGS/Types/UpdateTypes.swift delete mode 100644 SessionNetworkingKit/StorageServer/Models/AppVersionResponse.swift delete mode 100644 SessionNetworkingKit/StorageServer/Models/FileUploadResponse.swift delete mode 100644 SessionNetworkingKit/StorageServer/Models/SnodeMessage.swift delete mode 100644 SessionNetworkingKit/StorageServer/Models/SnodeReceivedMessage.swift create mode 100644 SessionNetworkingKitTests/SOGS/Crypto/Authentication+SOGS.swift create mode 100644 SessionNetworkingKitTests/SOGS/Crypto/CryptoSOGSAPISpec.swift create mode 100644 SessionNetworkingKitTests/SOGS/Models/CapabilitiesResponse.swift rename {SessionMessagingKitTests/Open Groups => SessionNetworkingKitTests/SOGS}/Models/RoomPollInfoSpec.swift (92%) rename {SessionMessagingKitTests/Open Groups => SessionNetworkingKitTests/SOGS}/Models/RoomSpec.swift (93%) rename {SessionMessagingKitTests/Open Groups => SessionNetworkingKitTests/SOGS}/Models/SOGSMessageSpec.swift (92%) rename {SessionMessagingKitTests/Open Groups => SessionNetworkingKitTests/SOGS}/Models/SendDirectMessageRequestSpec.swift (86%) rename SessionMessagingKitTests/Open Groups/Models/SendMessageRequestSpec.swift => SessionNetworkingKitTests/SOGS/Models/SendSOGSMessageRequestSpec.swift (82%) rename {SessionMessagingKitTests/Open Groups => SessionNetworkingKitTests/SOGS}/Models/UpdateMessageRequestSpec.swift (87%) rename SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift => SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift (74%) rename {SessionMessagingKitTests/Open Groups => SessionNetworkingKitTests/SOGS}/Types/PersonalizationSpec.swift (78%) rename {SessionMessagingKitTests/Open Groups => SessionNetworkingKitTests/SOGS}/Types/SOGSEndpointSpec.swift (56%) create mode 100644 SessionNetworkingKitTests/SOGS/Types/SOGSErrorSpec.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6302fff458..582038180a 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -106,9 +106,7 @@ 7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */; }; 7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */; }; 7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */; }; - 7B81682328A4C1210069F315 /* UpdateTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682228A4C1210069F315 /* UpdateTypes.swift */; }; 7B81682828B310D50069F315 /* _015_HomeQueryOptimisationIndexes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682728B310D50069F315 /* _015_HomeQueryOptimisationIndexes.swift */; }; - 7B81682A28B6F1420069F315 /* ReactionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682928B6F1420069F315 /* ReactionResponse.swift */; }; 7B81682C28B72F480069F315 /* PendingChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682B28B72F480069F315 /* PendingChange.swift */; }; 7B81FB5A2AB01B17002FB267 /* LoadingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81FB582AB01AA8002FB267 /* LoadingIndicatorView.swift */; }; 7B8D5FC428332600008324D9 /* VisibleMessage+Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */; }; @@ -177,10 +175,9 @@ 946F5A732D5DA3AC00A5ADCE /* Punycode in Frameworks */ = {isa = PBXBuildFile; productRef = 946F5A722D5DA3AC00A5ADCE /* Punycode */; }; 9473386E2BDF5F3E00B9E169 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9473386D2BDF5F3E00B9E169 /* InfoPlist.xcstrings */; }; 9479981C2DD44ADC008F5CD5 /* ThreadNotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9479981B2DD44AC5008F5CD5 /* ThreadNotificationSettingsViewModel.swift */; }; - 947D7FD42D509FC900E8E413 /* SessionNetworkAPI+Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD12D509FC900E8E413 /* SessionNetworkAPI+Models.swift */; }; 947D7FD62D509FC900E8E413 /* SessionNetworkAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FCF2D509FC900E8E413 /* SessionNetworkAPI.swift */; }; - 947D7FD72D509FC900E8E413 /* SessionNetworkAPI+Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD22D509FC900E8E413 /* SessionNetworkAPI+Network.swift */; }; - 947D7FD82D509FC900E8E413 /* SessionNetworkAPI+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD02D509FC900E8E413 /* SessionNetworkAPI+Database.swift */; }; + 947D7FD72D509FC900E8E413 /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD22D509FC900E8E413 /* HTTPClient.swift */; }; + 947D7FD82D509FC900E8E413 /* KeyValueStore+SessionNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD02D509FC900E8E413 /* KeyValueStore+SessionNetwork.swift */; }; 947D7FDE2D5180F200E8E413 /* SessionNetworkScreen+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FDB2D5180F200E8E413 /* SessionNetworkScreen+ViewModel.swift */; }; 947D7FE72D51837200E8E413 /* ArrowCapsule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FE42D51837200E8E413 /* ArrowCapsule.swift */; }; 947D7FE82D51837200E8E413 /* PopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FE52D51837200E8E413 /* PopoverView.swift */; }; @@ -249,7 +246,6 @@ B8856D72256F1421001CE70E /* OWSWindowManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2FB255B6DBD007E1867 /* OWSWindowManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; B8856DE6256F15F2001CE70E /* String+SSK.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB3F255A580C00E217F9 /* String+SSK.swift */; }; B8856E09256F1676001CE70E /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF237255B6D65007E1867 /* UIDevice+featureSupport.swift */; }; - B886B4A72398B23E00211ABE /* (null) in Sources */ = {isa = PBXBuildFile; }; B886B4A92398BA1500211ABE /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A82398BA1500211ABE /* QRCode.swift */; }; B88FA7F2260C3EB10049422F /* OpenGroupSuggestionGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88FA7F1260C3EB10049422F /* OpenGroupSuggestionGrid.swift */; }; B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */; }; @@ -318,7 +314,6 @@ C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */; }; C3402FE52559036600EA6424 /* SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C331FF1B2558F9D300070591 /* SessionUIKit.framework */; }; C34C8F7423A7830B00D82669 /* SpaceMono-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */; }; - C352A2FF25574B6300338F3E /* (null) in Sources */ = {isa = PBXBuildFile; }; C3548F0824456AB6009433A8 /* UIView+Wrapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */; }; C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C354E75923FE2A7600CE22E3 /* BaseVC.swift */; }; C35D0DB525AE5F1200B6BF49 /* UIEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35D0DB425AE5F1200B6BF49 /* UIEdgeInsets.swift */; }; @@ -423,8 +418,6 @@ FD0150522CA2446D005B08A1 /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = FD0150512CA2446D005B08A1 /* Quick */; }; FD0150542CA24471005B08A1 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = FD0150532CA24471005B08A1 /* Nimble */; }; FD0150582CA27DF3005B08A1 /* ScrollableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0150572CA27DEE005B08A1 /* ScrollableLabel.swift */; }; - FD02CC122C367762009AB976 /* Request+PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD02CC112C367761009AB976 /* Request+PushNotificationAPI.swift */; }; - FD02CC142C3677E6009AB976 /* Request+OpenGroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD02CC132C3677E6009AB976 /* Request+OpenGroupAPI.swift */; }; FD02CC162C3681EF009AB976 /* RevokeSubaccountResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD02CC152C3681EF009AB976 /* RevokeSubaccountResponse.swift */; }; FD05593D2DFA3A2800DC48CE /* VoipPayloadKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05593C2DFA3A2200DC48CE /* VoipPayloadKey.swift */; }; FD05594E2E012D2700DC48CE /* _043_RenameAttachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05594D2E012D1A00DC48CE /* _043_RenameAttachments.swift */; }; @@ -580,7 +573,6 @@ FD245C5D2850660F00B966DD /* OWSAudioPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F7255B6DBC007E1867 /* OWSAudioPlayer.m */; }; FD245C5F2850662200B966DD /* OWSWindowManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF306255B6DBE007E1867 /* OWSWindowManager.m */; }; FD245C632850664600B966DD /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD245C612850664300B966DD /* Configuration.swift */; }; - FD245C662850665900B966DD /* OpenGroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88FA7B726045D100049422F /* OpenGroupAPI.swift */; }; FD245C682850666300B966DD /* Message+Destination.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A30825574D8400338F3E /* Message+Destination.swift */; }; FD245C692850666800B966DD /* ExpirationTimerUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5E62554B07300555489 /* ExpirationTimerUpdate.swift */; }; FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */; }; @@ -621,8 +613,6 @@ FD3765E72ADE1AA300DC1489 /* UnrevokeSubaccountRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765E62ADE1AA300DC1489 /* UnrevokeSubaccountRequest.swift */; }; FD3765E92ADE1AAE00DC1489 /* UnrevokeSubaccountResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765E82ADE1AAE00DC1489 /* UnrevokeSubaccountResponse.swift */; }; FD3765EA2ADE37B400DC1489 /* Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD47E0AA2AA68EEA00A55E41 /* Authentication.swift */; }; - FD3765F42ADE5A0800DC1489 /* AuthenticatedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765F32ADE5A0800DC1489 /* AuthenticatedRequest.swift */; }; - FD3765F62ADE5BA500DC1489 /* ServiceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765F52ADE5BA500DC1489 /* ServiceInfo.swift */; }; FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */; }; FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */; }; FD37E9C828A1D73F003AE748 /* Theme+Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */; }; @@ -670,7 +660,6 @@ FD42ECD22E3071DE002D03EA /* ThemeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD42ECD12E3071DC002D03EA /* ThemeText.swift */; }; FD42ECD42E32FF2E002D03EA /* StringUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD42ECD32E32FF2A002D03EA /* StringUtilitiesSpec.swift */; }; FD42ECD62E3308B5002D03EA /* ObservableKey+SessionUtilitiesKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD42ECD52E3308AC002D03EA /* ObservableKey+SessionUtilitiesKit.swift */; }; - FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; }; FD432434299C6985008A0213 /* PendingReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD432433299C6985008A0213 /* PendingReadReceipt.swift */; }; FD47E0B12AA6A05800A55E41 /* Authentication+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD47E0B02AA6A05800A55E41 /* Authentication+SessionMessagingKit.swift */; }; FD47E0B52AA6D7AA00A55E41 /* Request+SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD47E0B42AA6D7AA00A55E41 /* Request+SnodeAPI.swift */; }; @@ -691,7 +680,6 @@ FD49E2492B05C1D500FFBBB5 /* MockKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD49E2452B05C1D500FFBBB5 /* MockKeychain.swift */; }; FD4B200E283492210034334B /* InsetLockableTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4B200D283492210034334B /* InsetLockableTableView.swift */; }; FD4BB22B2D63F20700D0DC3D /* MigrationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4BB22A2D63F20600D0DC3D /* MigrationHelper.swift */; }; - FD4BB22C2D63FA8600D0DC3D /* (null) in Sources */ = {isa = PBXBuildFile; }; FD4C4E9C2B02E2A300C72199 /* DisplayPictureError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4C4E9B2B02E2A300C72199 /* DisplayPictureError.swift */; }; FD4C53AF2CC1D62E003B10F4 /* _035_ReworkRecipientState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4C53AE2CC1D61E003B10F4 /* _035_ReworkRecipientState.swift */; }; FD52090328B4680F006098F6 /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090228B4680F006098F6 /* RadioButton.swift */; }; @@ -740,11 +728,74 @@ FD6A393B2C2AD3A300762359 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A393A2C2AD3A300762359 /* Nimble */; }; FD6A393D2C2AD3AC00762359 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A393C2C2AD3AC00762359 /* Nimble */; }; FD6A39412C2AD3B600762359 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A39402C2AD3B600762359 /* Nimble */; }; + FD6B928C2E779DCC004463B5 /* FileServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B928B2E779DC8004463B5 /* FileServer.swift */; }; + FD6B928E2E779E99004463B5 /* FileServerEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B928D2E779E95004463B5 /* FileServerEndpoint.swift */; }; + FD6B92902E779EDD004463B5 /* FileServerAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B928F2E779EDA004463B5 /* FileServerAPI.swift */; }; + FD6B92922E779FC8004463B5 /* SessionNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92912E779FC6004463B5 /* SessionNetwork.swift */; }; + FD6B92942E77A003004463B5 /* SessionNetworkEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92932E779FFE004463B5 /* SessionNetworkEndpoint.swift */; }; + FD6B92972E77A047004463B5 /* Price.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92962E77A042004463B5 /* Price.swift */; }; + FD6B92992E77A06E004463B5 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92982E77A06C004463B5 /* Token.swift */; }; + FD6B929B2E77A084004463B5 /* NetworkInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B929A2E77A083004463B5 /* NetworkInfo.swift */; }; + FD6B929D2E77A096004463B5 /* Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B929C2E77A095004463B5 /* Info.swift */; }; + FD6B92A32E77A18B004463B5 /* SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92A22E77A189004463B5 /* SnodeAPI.swift */; }; + FD6B92AB2E77A920004463B5 /* SOGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92A92E77A8F8004463B5 /* SOGS.swift */; }; + FD6B92AC2E77A993004463B5 /* SOGSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */; }; + FD6B92AD2E77A9F1004463B5 /* SOGSError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4380827B31D4E00C60D73 /* SOGSError.swift */; }; + FD6B92AE2E77A9F7004463B5 /* SOGSAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88FA7B726045D100049422F /* SOGSAPI.swift */; }; + FD6B92AF2E77AA03004463B5 /* HTTPQueryParam+SOGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487E29405994007DCAE5 /* HTTPQueryParam+SOGS.swift */; }; + FD6B92B02E77AA03004463B5 /* Request+SOGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD02CC132C3677E6009AB976 /* Request+SOGS.swift */; }; + FD6B92B12E77AA03004463B5 /* HTTPHeader+SOGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487D29405993007DCAE5 /* HTTPHeader+SOGS.swift */; }; + FD6B92B22E77AA03004463B5 /* UpdateTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682228A4C1210069F315 /* UpdateTypes.swift */; }; + FD6B92B32E77AA03004463B5 /* Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381627B32EC700C60D73 /* Personalization.swift */; }; + FD6B92B42E77AA11004463B5 /* PinnedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */; }; + FD6B92B52E77AA11004463B5 /* SendDirectMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */; }; + FD6B92B62E77AA11004463B5 /* UserUnbanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */; }; + FD6B92B72E77AA11004463B5 /* UserModeratorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */; }; + FD6B92B82E77AA11004463B5 /* SendSOGSMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387727B5C35400C60D73 /* SendSOGSMessageRequest.swift */; }; + FD6B92B92E77AA11004463B5 /* Room.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385C27B4C18900C60D73 /* Room.swift */; }; + FD6B92BA2E77AA11004463B5 /* RoomPollInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */; }; + FD6B92BB2E77AA11004463B5 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */; }; + FD6B92BC2E77AA11004463B5 /* ReactionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682928B6F1420069F315 /* ReactionResponse.swift */; }; + FD6B92BD2E77AA11004463B5 /* UserBanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A327BB107F00C60D73 /* UserBanRequest.swift */; }; + FD6B92BE2E77AA11004463B5 /* DirectMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C627BB6DF000C60D73 /* DirectMessage.swift */; }; + FD6B92BF2E77AA11004463B5 /* DeleteInboxResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD20C192A0A03AC003898FB /* DeleteInboxResponse.swift */; }; + FD6B92C02E77AA11004463B5 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; }; + FD6B92C12E77AA11004463B5 /* CapabilitiesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386627B4E10E00C60D73 /* CapabilitiesResponse.swift */; }; + FD6B92C22E77AA11004463B5 /* SOGSMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */; }; + FD6B92C62E77AD0F004463B5 /* Crypto+FileServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92C52E77AD0B004463B5 /* Crypto+FileServer.swift */; }; + FD6B92C82E77AD39004463B5 /* Crypto+SOGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92C72E77AD35004463B5 /* Crypto+SOGS.swift */; }; + FD6B92CD2E77B22D004463B5 /* SOGSMessageSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909027D709CA005DAE71 /* SOGSMessageSpec.swift */; }; + FD6B92CE2E77B234004463B5 /* RoomPollInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */; }; + FD6B92CF2E77B234004463B5 /* RoomSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908627D7047F005DAE71 /* RoomSpec.swift */; }; + FD6B92D02E77B23B004463B5 /* CapabilitiesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92CB2E77B1E5004463B5 /* CapabilitiesResponse.swift */; }; + FD6B92D12E77B253004463B5 /* SendSOGSMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908A27D707F3005DAE71 /* SendSOGSMessageRequestSpec.swift */; }; + FD6B92D22E77B270004463B5 /* SendDirectMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908E27D70938005DAE71 /* SendDirectMessageRequestSpec.swift */; }; + FD6B92D32E77B270004463B5 /* UpdateMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908C27D70905005DAE71 /* UpdateMessageRequestSpec.swift */; }; + FD6B92D42E77B2C7004463B5 /* SOGSAPISpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4389927BA002500C60D73 /* SOGSAPISpec.swift */; }; + FD6B92D62E77B55D004463B5 /* SOGSEndpointSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909327D710B4005DAE71 /* SOGSEndpointSpec.swift */; }; + FD6B92D72E77B55D004463B5 /* SOGSErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */; }; + FD6B92D82E77B55D004463B5 /* PersonalizationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */; }; + FD6B92DB2E77B597004463B5 /* CryptoSOGSAPISpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92DA2E77B592004463B5 /* CryptoSOGSAPISpec.swift */; }; + FD6B92DE2E77BDE2004463B5 /* Authentication+SOGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92DC2E77BB7E004463B5 /* Authentication+SOGS.swift */; }; + FD6B92E12E77C1E1004463B5 /* PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92E02E77C1DC004463B5 /* PushNotification.swift */; }; + FD6B92E22E77C21D004463B5 /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; }; + FD6B92E42E77C256004463B5 /* PushNotificationAPI+SMK.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92E32E77C250004463B5 /* PushNotificationAPI+SMK.swift */; }; + FD6B92E62E77C5A2004463B5 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D482A16EC20007267C7 /* Service.swift */; }; + FD6B92E72E77C5A2004463B5 /* Request+PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD02CC112C367761009AB976 /* Request+PushNotificationAPI.swift */; }; + FD6B92E82E77C5B7004463B5 /* PushNotificationEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D4F2A16EE50007267C7 /* PushNotificationEndpoint.swift */; }; + FD6B92E92E77C5D1004463B5 /* SubscribeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D4A2A16ECBA007267C7 /* SubscribeResponse.swift */; }; + FD6B92EA2E77C5D1004463B5 /* NotificationMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */; }; + FD6B92EB2E77C5D1004463B5 /* AuthenticatedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765F32ADE5A0800DC1489 /* AuthenticatedRequest.swift */; }; + FD6B92EF2E77C5D1004463B5 /* UnsubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D552A171FE4007267C7 /* UnsubscribeRequest.swift */; }; + FD6B92F02E77C5D1004463B5 /* SubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D462A16E4CA007267C7 /* SubscribeRequest.swift */; }; + FD6B92F22E77C5D1004463B5 /* UnsubscribeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D572A17207D007267C7 /* UnsubscribeResponse.swift */; }; + FD6B92F42E77C61A004463B5 /* ServiceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765F52ADE5BA500DC1489 /* ServiceInfo.swift */; }; + FD6B92F72E77C6D7004463B5 /* Crypto+PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92F62E77C6D3004463B5 /* Crypto+PushNotification.swift */; }; + FD6B92F82E77C725004463B5 /* ProcessResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */; }; FD6C67242CF6E72E00B350A7 /* NoopSessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6C67232CF6E72900B350A7 /* NoopSessionCallManager.swift */; }; FD6D9CF92CA152B300F706A8 /* Session+SNUIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754BB2C9B9A8E002A2623 /* Session+SNUIKit.swift */; }; FD6DA9CF2D015B440092085A /* Lucide in Frameworks */ = {isa = PBXBuildFile; productRef = FD6DA9CE2D015B440092085A /* Lucide */; }; FD6DA9D22D0160F10092085A /* Lucide in Frameworks */ = {isa = PBXBuildFile; productRef = FD6DA9D12D0160F10092085A /* Lucide */; }; - FD6E4C8A2A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */; }; FD6F5B5E2E657A24009A8D01 /* CancellationAwareAsyncStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6F5B5D2E657A24009A8D01 /* CancellationAwareAsyncStream.swift */; }; FD6F5B602E657A33009A8D01 /* StreamLifecycleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6F5B5F2E657A32009A8D01 /* StreamLifecycleManager.swift */; }; FD705A92278D051200F16121 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A91278D051200F16121 /* ReusableView.swift */; }; @@ -790,7 +841,7 @@ FD716E722850647600C96BF4 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF127BF6BA200510D0C /* Data+Utilities.swift */; }; FD72BDA12BE368C800CF6CF6 /* UIWindowLevel+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD72BDA02BE368C800CF6CF6 /* UIWindowLevel+Utilities.swift */; }; FD72BDA42BE3690B00CF6CF6 /* CryptoSMKSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD72BDA32BE3690B00CF6CF6 /* CryptoSMKSpec.swift */; }; - FD72BDA72BE369DC00CF6CF6 /* CryptoOpenGroupAPISpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD72BDA62BE369DC00CF6CF6 /* CryptoOpenGroupAPISpec.swift */; }; + FD72BDA72BE369DC00CF6CF6 /* CryptoOpenGroupSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD72BDA62BE369DC00CF6CF6 /* CryptoOpenGroupSpec.swift */; }; FD7443402D07A25C00862443 /* PushRegistrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD74433F2D07A25C00862443 /* PushRegistrationManager.swift */; }; FD7443422D07A27E00862443 /* SyncPushTokensJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7443412D07A27E00862443 /* SyncPushTokensJob.swift */; }; FD74434A2D07CA9F00862443 /* Codable+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7443492D07CA9F00862443 /* Codable+Utilities.swift */; }; @@ -824,8 +875,7 @@ FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; }; FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; }; FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */; }; - FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */; }; - FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */; }; + FD83B9C727CF3F10005E1583 /* CapabilitySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C627CF3F10005E1583 /* CapabilitySpec.swift */; }; FD83B9D227D59495005E1583 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; }; FD848B8B283DC509000E298B /* PagedDatabaseObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8A283DC509000E298B /* PagedDatabaseObserver.swift */; }; FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8C283E0B26000E298B /* MessageInputTypes.swift */; }; @@ -941,56 +991,20 @@ FDBB25E72988BBBE00F1508E /* UIContextualAction+Theming.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */; }; FDBEE52E2B6A18B900C143A0 /* UserDefaultsConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBEE52D2B6A18B900C143A0 /* UserDefaultsConfig.swift */; }; FDC0F0042BFECE12002CBFB9 /* TimeUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC0F0032BFECE12002CBFB9 /* TimeUnit.swift */; }; - FDC13D472A16E4CA007267C7 /* SubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D462A16E4CA007267C7 /* SubscribeRequest.swift */; }; - FDC13D492A16EC20007267C7 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D482A16EC20007267C7 /* Service.swift */; }; - FDC13D4B2A16ECBA007267C7 /* SubscribeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D4A2A16ECBA007267C7 /* SubscribeResponse.swift */; }; - FDC13D502A16EE50007267C7 /* PushNotificationAPIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D4F2A16EE50007267C7 /* PushNotificationAPIEndpoint.swift */; }; - FDC13D542A16FF29007267C7 /* LegacyGroupRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D532A16FF29007267C7 /* LegacyGroupRequest.swift */; }; - FDC13D562A171FE4007267C7 /* UnsubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D552A171FE4007267C7 /* UnsubscribeRequest.swift */; }; - FDC13D582A17207D007267C7 /* UnsubscribeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D572A17207D007267C7 /* UnsubscribeResponse.swift */; }; - FDC13D5A2A1721C5007267C7 /* LegacyNotifyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D592A1721C5007267C7 /* LegacyNotifyRequest.swift */; }; FDC1BD662CFD6C4F002CDC71 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC1BD652CFD6C4E002CDC71 /* Config.swift */; }; FDC1BD682CFE6EEB002CDC71 /* DeveloperSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC1BD672CFE6EEA002CDC71 /* DeveloperSettingsViewModel.swift */; }; FDC1BD6A2CFE7B6B002CDC71 /* DirectoryArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC1BD692CFE7B67002CDC71 /* DirectoryArchiver.swift */; }; FDC289472C881A3800020BC2 /* MutableIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC289462C881A3800020BC2 /* MutableIdentifiable.swift */; }; - FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908627D7047F005DAE71 /* RoomSpec.swift */; }; - FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */; }; - FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908A27D707F3005DAE71 /* SendMessageRequestSpec.swift */; }; - FDC2908D27D70905005DAE71 /* UpdateMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908C27D70905005DAE71 /* UpdateMessageRequestSpec.swift */; }; - FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908E27D70938005DAE71 /* SendDirectMessageRequestSpec.swift */; }; - FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909027D709CA005DAE71 /* SOGSMessageSpec.swift */; }; - FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909327D710B4005DAE71 /* SOGSEndpointSpec.swift */; }; - FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */; }; - FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */; }; FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; }; FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; }; - FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */; }; - FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381627B32EC700C60D73 /* Personalization.swift */; }; - FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */; }; - FDC4382F27B383AF00C60D73 /* LegacyPushServerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */; }; - FDC4385D27B4C18900C60D73 /* Room.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385C27B4C18900C60D73 /* Room.swift */; }; - FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */; }; - FDC4386327B4D94E00C60D73 /* SOGSMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */; }; - FDC4386527B4DE7600C60D73 /* RoomPollInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */; }; - FDC4386727B4E10E00C60D73 /* Capabilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386627B4E10E00C60D73 /* Capabilities.swift */; }; FDC4386C27B4E90300C60D73 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; - FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */; }; FDC4389227B9FFC700C60D73 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; platformFilter = ios; }; - FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */; }; - FDC438A427BB107F00C60D73 /* UserBanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A327BB107F00C60D73 /* UserBanRequest.swift */; }; - FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */; }; - FDC438AA27BB12BB00C60D73 /* UserModeratorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */; }; - FDC438C727BB6DF000C60D73 /* DirectMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C627BB6DF000C60D73 /* DirectMessage.swift */; }; - FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; }; - FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */; }; FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CC27BC641200C60D73 /* Set+Utilities.swift */; }; FDC498B92AC15FE300EDD897 /* AppNotificationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC498B82AC15FE300EDD897 /* AppNotificationAction.swift */; }; FDC6D7602862B3F600B04575 /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC6D75F2862B3F600B04575 /* Dependencies.swift */; }; - FDCD2E032A41294E00964D6A /* LegacyGroupOnlyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */; }; FDCDB8E02811007F00352A0C /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCDB8DF2811007F00352A0C /* HomeViewModel.swift */; }; FDD20C162A09E64A003898FB /* GetExpiriesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD20C152A09E64A003898FB /* GetExpiriesRequest.swift */; }; FDD20C182A09E7D3003898FB /* GetExpiriesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD20C172A09E7D3003898FB /* GetExpiriesResponse.swift */; }; - FDD20C1A2A0A03AC003898FB /* DeleteInboxResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD20C192A0A03AC003898FB /* DeleteInboxResponse.swift */; }; FDD23ADF2E457CAA0057E853 /* _016_ThemePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9F828A5F14A003AE748 /* _016_ThemePreferences.swift */; }; FDD23AE02E457CD40057E853 /* _004_SNK_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79F27F40CC800122BE0 /* _004_SNK_InitialSetupMigration.swift */; }; FDD23AE12E457CDE0057E853 /* _005_SNK_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A6C2818C61500035AC1 /* _005_SNK_SetupStandardJobs.swift */; }; @@ -1011,7 +1025,6 @@ FDD23AF02E459EDD0057E853 /* _020_AddJobUniqueHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945D9C572D6FDBE7003C4C0C /* _020_AddJobUniqueHash.swift */; }; FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; }; FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; }; - FDD82C3F2A205D0A00425F05 /* ProcessResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */; }; FDDD554E2C1FCB77006CBF03 /* _033_ScheduleAppUpdateCheckJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDD554D2C1FCB77006CBF03 /* _033_ScheduleAppUpdateCheckJob.swift */; }; FDDF074429C3E3D000E5E8B5 /* FetchRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */; }; FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */; }; @@ -1030,7 +1043,7 @@ FDE6E99829F8E63A00F93C5D /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE6E99729F8E63A00F93C5D /* Accessibility.swift */; }; FDE7549B2C940108002A2623 /* MessageViewModel+DeletionActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7549A2C940108002A2623 /* MessageViewModel+DeletionActions.swift */; }; FDE7549D2C9961A4002A2623 /* CommunityPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7549C2C9961A4002A2623 /* CommunityPoller.swift */; }; - FDE754A12C9A60A6002A2623 /* Crypto+OpenGroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A02C9A60A6002A2623 /* Crypto+OpenGroupAPI.swift */; }; + FDE754A12C9A60A6002A2623 /* Crypto+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A02C9A60A6002A2623 /* Crypto+OpenGroup.swift */; }; FDE754A32C9A8FD1002A2623 /* SwarmPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A22C9A8FD1002A2623 /* SwarmPoller.swift */; }; FDE754A82C9B964D002A2623 /* MessageReceiverGroupsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A42C9B964D002A2623 /* MessageReceiverGroupsSpec.swift */; }; FDE754A92C9B964D002A2623 /* MessageSenderGroupsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A52C9B964D002A2623 /* MessageSenderGroupsSpec.swift */; }; @@ -1079,12 +1092,6 @@ FDE755202C9BC1A6002A2623 /* CacheConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7551F2C9BC1A6002A2623 /* CacheConfig.swift */; }; FDE755222C9BC1BA002A2623 /* LibSessionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755212C9BC1BA002A2623 /* LibSessionError.swift */; }; FDE755242C9BC1D1002A2623 /* Publisher+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755232C9BC1D1002A2623 /* Publisher+Utilities.swift */; }; - FDEF57212C3CF03A00131302 /* (null) in Sources */ = {isa = PBXBuildFile; }; - FDEF57222C3CF03D00131302 /* (null) in Sources */ = {isa = PBXBuildFile; }; - FDEF57232C3CF04300131302 /* (null) in Sources */ = {isa = PBXBuildFile; }; - FDEF57242C3CF04700131302 /* (null) in Sources */ = {isa = PBXBuildFile; }; - FDEF57252C3CF04C00131302 /* (null) in Sources */ = {isa = PBXBuildFile; }; - FDEF57262C3CF05F00131302 /* (null) in Sources */ = {isa = PBXBuildFile; }; FDEF573E2C40F2A100131302 /* GroupUpdateMemberLeftNotificationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDEF573D2C40F2A100131302 /* GroupUpdateMemberLeftNotificationMessage.swift */; }; FDEF57712C44D2D300131302 /* GeoLite2-Country-Blocks-IPv4 in Resources */ = {isa = PBXBuildFile; fileRef = FDEF57702C44D2D300131302 /* GeoLite2-Country-Blocks-IPv4 */; }; FDF01FAD2A9ECC4200CAF969 /* SingletonConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF01FAC2A9ECC4200CAF969 /* SingletonConfig.swift */; }; @@ -1106,11 +1113,8 @@ FDF40CDE2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF40CDD2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift */; }; FDF71EA32B072C2800A8D6B5 /* LibSessionMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF71EA22B072C2800A8D6B5 /* LibSessionMessage.swift */; }; FDF71EA52B07363500A8D6B5 /* MessageReceiver+LibSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF71EA42B07363500A8D6B5 /* MessageReceiver+LibSession.swift */; }; - FDF8487F29405994007DCAE5 /* HTTPHeader+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487D29405993007DCAE5 /* HTTPHeader+OpenGroup.swift */; }; - FDF8488029405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487E29405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift */; }; FDF8488929405B27007DCAE5 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */; }; FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */; }; - FDF8489429405C1B007DCAE5 /* SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */; }; FDF848BC29405C5A007DCAE5 /* SnodeRecursiveResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489A29405C5A007DCAE5 /* SnodeRecursiveResponse.swift */; }; FDF848BD29405C5A007DCAE5 /* GetMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489B29405C5A007DCAE5 /* GetMessagesRequest.swift */; }; FDF848BF29405C5A007DCAE5 /* SnodeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489D29405C5A007DCAE5 /* SnodeResponse.swift */; }; @@ -1146,7 +1150,6 @@ FDF848F329413DB0007DCAE5 /* ImagePickerHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */; }; FDF848F529413EEC007DCAE5 /* SessionCell+Styling.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */; }; FDF848F729414477007DCAE5 /* CurrentUserPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */; }; - FDFBB74D2A1F3C4E00CA7350 /* NotificationMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */; }; FDFC4D9A29F0C51500992FB6 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D22553860900C340D1 /* String+Trimming.swift */; }; FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FDFDE124282D04F20098B17F /* MediaDismissAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE123282D04F20098B17F /* MediaDismissAnimationController.swift */; }; @@ -1551,9 +1554,8 @@ 9479981B2DD44AC5008F5CD5 /* ThreadNotificationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadNotificationSettingsViewModel.swift; sourceTree = ""; }; 947AD68F2C8968FF000B2730 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 947D7FCF2D509FC900E8E413 /* SessionNetworkAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionNetworkAPI.swift; sourceTree = ""; }; - 947D7FD02D509FC900E8E413 /* SessionNetworkAPI+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkAPI+Database.swift"; sourceTree = ""; }; - 947D7FD12D509FC900E8E413 /* SessionNetworkAPI+Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkAPI+Models.swift"; sourceTree = ""; }; - 947D7FD22D509FC900E8E413 /* SessionNetworkAPI+Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkAPI+Network.swift"; sourceTree = ""; }; + 947D7FD02D509FC900E8E413 /* KeyValueStore+SessionNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyValueStore+SessionNetwork.swift"; sourceTree = ""; }; + 947D7FD22D509FC900E8E413 /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; 947D7FD92D5180F200E8E413 /* SessionNetworkScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionNetworkScreen.swift; sourceTree = ""; }; 947D7FDA2D5180F200E8E413 /* SessionNetworkScreen+Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkScreen+Models.swift"; sourceTree = ""; }; 947D7FDB2D5180F200E8E413 /* SessionNetworkScreen+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkScreen+ViewModel.swift"; sourceTree = ""; }; @@ -1622,7 +1624,7 @@ B879D44A247E1D9200DB3608 /* PathStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathStatusView.swift; sourceTree = ""; }; B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraints.swift"; sourceTree = ""; }; B886B4A82398BA1500211ABE /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = ""; }; - B88FA7B726045D100049422F /* OpenGroupAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPI.swift; sourceTree = ""; }; + B88FA7B726045D100049422F /* SOGSAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSAPI.swift; sourceTree = ""; }; B88FA7F1260C3EB10049422F /* OpenGroupSuggestionGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupSuggestionGrid.swift; sourceTree = ""; }; B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanQRCodeWrapperVC.swift; sourceTree = ""; }; B894D0742339EDCF00B4D94D /* NukeDataModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NukeDataModal.swift; sourceTree = ""; }; @@ -1801,7 +1803,7 @@ FD01504D2CA243E7005B08A1 /* TypeConversionUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeConversionUtilitiesSpec.swift; sourceTree = ""; }; FD0150572CA27DEE005B08A1 /* ScrollableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableLabel.swift; sourceTree = ""; }; FD02CC112C367761009AB976 /* Request+PushNotificationAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Request+PushNotificationAPI.swift"; sourceTree = ""; }; - FD02CC132C3677E6009AB976 /* Request+OpenGroupAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Request+OpenGroupAPI.swift"; sourceTree = ""; }; + FD02CC132C3677E6009AB976 /* Request+SOGS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Request+SOGS.swift"; sourceTree = ""; }; FD02CC152C3681EF009AB976 /* RevokeSubaccountResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RevokeSubaccountResponse.swift; sourceTree = ""; }; FD05593C2DFA3A2200DC48CE /* VoipPayloadKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipPayloadKey.swift; sourceTree = ""; }; FD05594D2E012D1A00DC48CE /* _043_RenameAttachments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _043_RenameAttachments.swift; sourceTree = ""; }; @@ -2047,9 +2049,27 @@ FD66CB2B2BF344C600268FAB /* SessionMessageKit.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = SessionMessageKit.xctestplan; sourceTree = ""; }; FD6A38F02C2A66B100762359 /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = ""; }; FD6A7A6C2818C61500035AC1 /* _005_SNK_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _005_SNK_SetupStandardJobs.swift; sourceTree = ""; }; + FD6B928B2E779DC8004463B5 /* FileServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileServer.swift; sourceTree = ""; }; + FD6B928D2E779E95004463B5 /* FileServerEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileServerEndpoint.swift; sourceTree = ""; }; + FD6B928F2E779EDA004463B5 /* FileServerAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileServerAPI.swift; sourceTree = ""; }; + FD6B92912E779FC6004463B5 /* SessionNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionNetwork.swift; sourceTree = ""; }; + FD6B92932E779FFE004463B5 /* SessionNetworkEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionNetworkEndpoint.swift; sourceTree = ""; }; + FD6B92962E77A042004463B5 /* Price.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Price.swift; sourceTree = ""; }; + FD6B92982E77A06C004463B5 /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; + FD6B929A2E77A083004463B5 /* NetworkInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInfo.swift; sourceTree = ""; }; + FD6B929C2E77A095004463B5 /* Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Info.swift; sourceTree = ""; }; + FD6B92A22E77A189004463B5 /* SnodeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeAPI.swift; sourceTree = ""; }; + FD6B92A92E77A8F8004463B5 /* SOGS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGS.swift; sourceTree = ""; }; + FD6B92C52E77AD0B004463B5 /* Crypto+FileServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+FileServer.swift"; sourceTree = ""; }; + FD6B92C72E77AD35004463B5 /* Crypto+SOGS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+SOGS.swift"; sourceTree = ""; }; + FD6B92CB2E77B1E5004463B5 /* CapabilitiesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilitiesResponse.swift; sourceTree = ""; }; + FD6B92DA2E77B592004463B5 /* CryptoSOGSAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSOGSAPISpec.swift; sourceTree = ""; }; + FD6B92DC2E77BB7E004463B5 /* Authentication+SOGS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Authentication+SOGS.swift"; sourceTree = ""; }; + FD6B92E02E77C1DC004463B5 /* PushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotification.swift; sourceTree = ""; }; + FD6B92E32E77C250004463B5 /* PushNotificationAPI+SMK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PushNotificationAPI+SMK.swift"; sourceTree = ""; }; + FD6B92F62E77C6D3004463B5 /* Crypto+PushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+PushNotification.swift"; sourceTree = ""; }; FD6C67232CF6E72900B350A7 /* NoopSessionCallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopSessionCallManager.swift; sourceTree = ""; }; FD6DF00A2ACFE40D0084BA4C /* _021_AddSnodeReveivedMessageInfoPrimaryKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _021_AddSnodeReveivedMessageInfoPrimaryKey.swift; sourceTree = ""; }; - FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyUnsubscribeRequest.swift; sourceTree = ""; }; FD6F5B5D2E657A24009A8D01 /* CancellationAwareAsyncStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancellationAwareAsyncStream.swift; sourceTree = ""; }; FD6F5B5F2E657A32009A8D01 /* StreamLifecycleManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamLifecycleManager.swift; sourceTree = ""; }; FD705A91278D051200F16121 /* ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = ""; }; @@ -2090,7 +2110,7 @@ FD716E7028505E5100C96BF4 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = ""; }; FD72BDA02BE368C800CF6CF6 /* UIWindowLevel+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIWindowLevel+Utilities.swift"; sourceTree = ""; }; FD72BDA32BE3690B00CF6CF6 /* CryptoSMKSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSMKSpec.swift; sourceTree = ""; }; - FD72BDA62BE369DC00CF6CF6 /* CryptoOpenGroupAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoOpenGroupAPISpec.swift; sourceTree = ""; }; + FD72BDA62BE369DC00CF6CF6 /* CryptoOpenGroupSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoOpenGroupSpec.swift; sourceTree = ""; }; FD74433F2D07A25C00862443 /* PushRegistrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRegistrationManager.swift; sourceTree = ""; }; FD7443412D07A27E00862443 /* SyncPushTokensJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPushTokensJob.swift; sourceTree = ""; }; FD7443452D07CA9F00862443 /* CGFloat+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Utilities.swift"; sourceTree = ""; }; @@ -2120,7 +2140,7 @@ FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionIdSpec.swift; sourceTree = ""; }; FD83B9BD27CF2243005E1583 /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupSpec.swift; sourceTree = ""; }; - FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilitiesSpec.swift; sourceTree = ""; }; + FD83B9C627CF3F10005E1583 /* CapabilitySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilitySpec.swift; sourceTree = ""; }; FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageResponse.swift; sourceTree = ""; }; FD83B9D127D59495005E1583 /* MockUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserDefaults.swift; sourceTree = ""; }; FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryWithDependencies.swift; sourceTree = ""; }; @@ -2222,18 +2242,16 @@ FDC13D462A16E4CA007267C7 /* SubscribeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribeRequest.swift; sourceTree = ""; }; FDC13D482A16EC20007267C7 /* Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = ""; }; FDC13D4A2A16ECBA007267C7 /* SubscribeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribeResponse.swift; sourceTree = ""; }; - FDC13D4F2A16EE50007267C7 /* PushNotificationAPIEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationAPIEndpoint.swift; sourceTree = ""; }; - FDC13D532A16FF29007267C7 /* LegacyGroupRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGroupRequest.swift; sourceTree = ""; }; + FDC13D4F2A16EE50007267C7 /* PushNotificationEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationEndpoint.swift; sourceTree = ""; }; FDC13D552A171FE4007267C7 /* UnsubscribeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsubscribeRequest.swift; sourceTree = ""; }; FDC13D572A17207D007267C7 /* UnsubscribeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsubscribeResponse.swift; sourceTree = ""; }; - FDC13D592A1721C5007267C7 /* LegacyNotifyRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyNotifyRequest.swift; sourceTree = ""; }; FDC1BD652CFD6C4E002CDC71 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; FDC1BD672CFE6EEA002CDC71 /* DeveloperSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsViewModel.swift; sourceTree = ""; }; FDC1BD692CFE7B67002CDC71 /* DirectoryArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryArchiver.swift; sourceTree = ""; }; FDC289462C881A3800020BC2 /* MutableIdentifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutableIdentifiable.swift; sourceTree = ""; }; FDC2908627D7047F005DAE71 /* RoomSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSpec.swift; sourceTree = ""; }; FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollInfoSpec.swift; sourceTree = ""; }; - FDC2908A27D707F3005DAE71 /* SendMessageRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequestSpec.swift; sourceTree = ""; }; + FDC2908A27D707F3005DAE71 /* SendSOGSMessageRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendSOGSMessageRequestSpec.swift; sourceTree = ""; }; FDC2908C27D70905005DAE71 /* UpdateMessageRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMessageRequestSpec.swift; sourceTree = ""; }; FDC2908E27D70938005DAE71 /* SendDirectMessageRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageRequestSpec.swift; sourceTree = ""; }; FDC2909027D709CA005DAE71 /* SOGSMessageSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSMessageSpec.swift; sourceTree = ""; }; @@ -2242,20 +2260,19 @@ FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalizationSpec.swift; sourceTree = ""; }; FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupManagerSpec.swift; sourceTree = ""; }; FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NimbleExtensions.swift; sourceTree = ""; }; - FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPIError.swift; sourceTree = ""; }; + FDC4380827B31D4E00C60D73 /* SOGSError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSError.swift; sourceTree = ""; }; FDC4381627B32EC700C60D73 /* Personalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Personalization.swift; sourceTree = ""; }; FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSEndpoint.swift; sourceTree = ""; }; - FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyPushServerResponse.swift; sourceTree = ""; }; FDC4383727B3863200C60D73 /* AppVersionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionResponse.swift; sourceTree = ""; }; FDC4385C27B4C18900C60D73 /* Room.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Room.swift; sourceTree = ""; }; FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedMessage.swift; sourceTree = ""; }; FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSMessage.swift; sourceTree = ""; }; FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollInfo.swift; sourceTree = ""; }; - FDC4386627B4E10E00C60D73 /* Capabilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Capabilities.swift; sourceTree = ""; }; + FDC4386627B4E10E00C60D73 /* CapabilitiesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilitiesResponse.swift; sourceTree = ""; }; FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadResponse.swift; sourceTree = ""; }; - FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequest.swift; sourceTree = ""; }; + FDC4387727B5C35400C60D73 /* SendSOGSMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendSOGSMessageRequest.swift; sourceTree = ""; }; FDC4388E27B9FFC700C60D73 /* SessionMessagingKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionMessagingKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPISpec.swift; sourceTree = ""; }; + FDC4389927BA002500C60D73 /* SOGSAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSAPISpec.swift; sourceTree = ""; }; FDC438A327BB107F00C60D73 /* UserBanRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserBanRequest.swift; sourceTree = ""; }; FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserUnbanRequest.swift; sourceTree = ""; }; FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserModeratorRequest.swift; sourceTree = ""; }; @@ -2266,7 +2283,6 @@ FDC498B82AC15FE300EDD897 /* AppNotificationAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNotificationAction.swift; sourceTree = ""; }; FDC6D75F2862B3F600B04575 /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = ""; }; FDCCC6E82ABA7402002BBEF5 /* EmojiGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiGenerator.swift; sourceTree = ""; }; - FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGroupOnlyRequest.swift; sourceTree = ""; }; FDCDB8DF2811007F00352A0C /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; FDD20C152A09E64A003898FB /* GetExpiriesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetExpiriesRequest.swift; sourceTree = ""; }; FDD20C172A09E7D3003898FB /* GetExpiriesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetExpiriesResponse.swift; sourceTree = ""; }; @@ -2295,7 +2311,7 @@ FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintLocalizableStrings.swift; sourceTree = ""; }; FDE7549A2C940108002A2623 /* MessageViewModel+DeletionActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageViewModel+DeletionActions.swift"; sourceTree = ""; }; FDE7549C2C9961A4002A2623 /* CommunityPoller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommunityPoller.swift; sourceTree = ""; }; - FDE754A02C9A60A6002A2623 /* Crypto+OpenGroupAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Crypto+OpenGroupAPI.swift"; sourceTree = ""; }; + FDE754A02C9A60A6002A2623 /* Crypto+OpenGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Crypto+OpenGroup.swift"; sourceTree = ""; }; FDE754A22C9A8FD1002A2623 /* SwarmPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwarmPoller.swift; sourceTree = ""; }; FDE754A42C9B964D002A2623 /* MessageReceiverGroupsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageReceiverGroupsSpec.swift; sourceTree = ""; }; FDE754A52C9B964D002A2623 /* MessageSenderGroupsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageSenderGroupsSpec.swift; sourceTree = ""; }; @@ -2379,10 +2395,9 @@ FDF40CDD2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _011_RemoveLegacyYDB.swift; sourceTree = ""; }; FDF71EA22B072C2800A8D6B5 /* LibSessionMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionMessage.swift; sourceTree = ""; }; FDF71EA42B07363500A8D6B5 /* MessageReceiver+LibSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+LibSession.swift"; sourceTree = ""; }; - FDF8487D29405993007DCAE5 /* HTTPHeader+OpenGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPHeader+OpenGroup.swift"; sourceTree = ""; }; - FDF8487E29405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPQueryParam+OpenGroup.swift"; sourceTree = ""; }; + FDF8487D29405993007DCAE5 /* HTTPHeader+SOGS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPHeader+SOGS.swift"; sourceTree = ""; }; + FDF8487E29405994007DCAE5 /* HTTPQueryParam+SOGS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPQueryParam+SOGS.swift"; sourceTree = ""; }; FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPINamespace.swift; sourceTree = ""; }; - FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPI.swift; sourceTree = ""; }; FDF8489A29405C5A007DCAE5 /* SnodeRecursiveResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeRecursiveResponse.swift; sourceTree = ""; }; FDF8489B29405C5A007DCAE5 /* GetMessagesRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetMessagesRequest.swift; sourceTree = ""; }; FDF8489D29405C5A007DCAE5 /* SnodeResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeResponse.swift; sourceTree = ""; }; @@ -2850,16 +2865,16 @@ path = SwiftUI; sourceTree = ""; }; - 947D7FD32D509FC900E8E413 /* SessionNetworkAPI */ = { + 947D7FD32D509FC900E8E413 /* SessionNetwork */ = { isa = PBXGroup; children = ( - 941375BA2D5184B60058F244 /* HTTPHeader+SessionNetwork.swift */, + FD6B92952E77A036004463B5 /* Models */, + FD6B929E2E77A0E8004463B5 /* Types */, + FD6B92912E779FC6004463B5 /* SessionNetwork.swift */, 947D7FCF2D509FC900E8E413 /* SessionNetworkAPI.swift */, - 947D7FD02D509FC900E8E413 /* SessionNetworkAPI+Database.swift */, - 947D7FD12D509FC900E8E413 /* SessionNetworkAPI+Models.swift */, - 947D7FD22D509FC900E8E413 /* SessionNetworkAPI+Network.swift */, + FD6B92932E779FFE004463B5 /* SessionNetworkEndpoint.swift */, ); - path = SessionNetworkAPI; + path = SessionNetwork; sourceTree = ""; }; 947D7FDC2D5180F200E8E413 /* SessionNetworkScreen */ = { @@ -3565,9 +3580,8 @@ isa = PBXGroup; children = ( FDC13D4E2A16EE41007267C7 /* Types */, - FDC4382D27B383A600C60D73 /* Models */, FDF0B7502807BA56004C14C5 /* NotificationsManagerType.swift */, - C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */, + FD6B92E32E77C250004463B5 /* PushNotificationAPI+SMK.swift */, ); path = Notifications; sourceTree = ""; @@ -3623,9 +3637,7 @@ isa = PBXGroup; children = ( FD23CE202A661CE80000B97C /* Crypto */, - FDC4381827B34EAD00C60D73 /* Models */, - FDC4380727B31D3A00C60D73 /* Types */, - B88FA7B726045D100049422F /* OpenGroupAPI.swift */, + FDC4381827B34EAD00C60D73 /* Types */, C3DB66AB260ACA42001EFC55 /* OpenGroupManager.swift */, ); path = "Open Groups"; @@ -3673,11 +3685,13 @@ children = ( C3C2A5B0255385C700C340D1 /* Meta */, FDE754E22C9BAFF4002A2623 /* Crypto */, - FD17D79D27F40CAA00122BE0 /* Database */, + FD6B928A2E779DB6004463B5 /* FileServer */, FD7F74682BAB8A5D006DDFD8 /* LibSession */, - FDF8489929405C5A007DCAE5 /* Models */, - 947D7FD32D509FC900E8E413 /* SessionNetworkAPI */, - FD2272842C33E28D004D8A6C /* SnodeAPI */, + FD6B92DF2E77C1CB004463B5 /* PushNotification */, + 947D7FD32D509FC900E8E413 /* SessionNetwork */, + FD6B92892E779D8D004463B5 /* SOGS */, + FD2272842C33E28D004D8A6C /* StorageServer */, + FD6B92A52E77A3BD004463B5 /* Models */, FDF8488F29405C13007DCAE5 /* Types */, C3C2A5CD255385F300C340D1 /* Utilities */, ); @@ -4063,19 +4077,11 @@ sourceTree = ""; }; FD17D79D27F40CAA00122BE0 /* Database */ = { - isa = PBXGroup; - children = ( - FD17D7A827F41BE300122BE0 /* Models */, - ); - path = Database; - sourceTree = ""; - }; - FD17D7A827F41BE300122BE0 /* Models */ = { isa = PBXGroup; children = ( FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */, ); - path = Models; + path = Database; sourceTree = ""; }; FD17D7B427F51E6700122BE0 /* Types */ = { @@ -4123,17 +4129,18 @@ path = Database; sourceTree = ""; }; - FD2272842C33E28D004D8A6C /* SnodeAPI */ = { + FD2272842C33E28D004D8A6C /* StorageServer */ = { isa = PBXGroup; children = ( - FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */, + FD17D79D27F40CAA00122BE0 /* Database */, + FDF8489929405C5A007DCAE5 /* Models */, + FD6B92A12E77A153004463B5 /* Types */, + FD6B92A22E77A189004463B5 /* SnodeAPI.swift */, FD2272D72C34EDE6004D8A6C /* SnodeAPIEndpoint.swift */, FDF848E029405D6E007DCAE5 /* SnodeAPIError.swift */, FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */, - FD47E0B42AA6D7AA00A55E41 /* Request+SnodeAPI.swift */, - FD19363B2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift */, ); - path = SnodeAPI; + path = StorageServer; sourceTree = ""; }; FD2272C52C34E9D1004D8A6C /* Types */ = { @@ -4175,7 +4182,7 @@ FD23CE202A661CE80000B97C /* Crypto */ = { isa = PBXGroup; children = ( - FDE754A02C9A60A6002A2623 /* Crypto+OpenGroupAPI.swift */, + FDE754A02C9A60A6002A2623 /* Crypto+OpenGroup.swift */, ); path = Crypto; sourceTree = ""; @@ -4335,6 +4342,206 @@ path = Models; sourceTree = ""; }; + FD6B92892E779D8D004463B5 /* SOGS */ = { + isa = PBXGroup; + children = ( + FD6B92C32E77ACF2004463B5 /* Crypto */, + FD6B92A82E77A8B2004463B5 /* Models */, + FD6B92A72E77A875004463B5 /* Types */, + FD6B92A92E77A8F8004463B5 /* SOGS.swift */, + B88FA7B726045D100049422F /* SOGSAPI.swift */, + FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */, + FDC4380827B31D4E00C60D73 /* SOGSError.swift */, + ); + path = SOGS; + sourceTree = ""; + }; + FD6B928A2E779DB6004463B5 /* FileServer */ = { + isa = PBXGroup; + children = ( + FD6B92C42E77AD01004463B5 /* Crypto */, + FD6B92A42E77A37A004463B5 /* Models */, + FD6B928B2E779DC8004463B5 /* FileServer.swift */, + FD6B928F2E779EDA004463B5 /* FileServerAPI.swift */, + FD6B928D2E779E95004463B5 /* FileServerEndpoint.swift */, + ); + path = FileServer; + sourceTree = ""; + }; + FD6B92952E77A036004463B5 /* Models */ = { + isa = PBXGroup; + children = ( + FD6B929C2E77A095004463B5 /* Info.swift */, + FD6B929A2E77A083004463B5 /* NetworkInfo.swift */, + FD6B92962E77A042004463B5 /* Price.swift */, + FD6B92982E77A06C004463B5 /* Token.swift */, + ); + path = Models; + sourceTree = ""; + }; + FD6B929E2E77A0E8004463B5 /* Types */ = { + isa = PBXGroup; + children = ( + 941375BA2D5184B60058F244 /* HTTPHeader+SessionNetwork.swift */, + 947D7FD22D509FC900E8E413 /* HTTPClient.swift */, + 947D7FD02D509FC900E8E413 /* KeyValueStore+SessionNetwork.swift */, + ); + path = Types; + sourceTree = ""; + }; + FD6B92A12E77A153004463B5 /* Types */ = { + isa = PBXGroup; + children = ( + FD19363B2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift */, + FD47E0B42AA6D7AA00A55E41 /* Request+SnodeAPI.swift */, + FDF848B429405C5A007DCAE5 /* SnodeMessage.swift */, + FDF848AA29405C5A007DCAE5 /* SnodeReceivedMessage.swift */, + ); + path = Types; + sourceTree = ""; + }; + FD6B92A42E77A37A004463B5 /* Models */ = { + isa = PBXGroup; + children = ( + FDC4383727B3863200C60D73 /* AppVersionResponse.swift */, + ); + path = Models; + sourceTree = ""; + }; + FD6B92A52E77A3BD004463B5 /* Models */ = { + isa = PBXGroup; + children = ( + FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */, + ); + path = Models; + sourceTree = ""; + }; + FD6B92A72E77A875004463B5 /* Types */ = { + isa = PBXGroup; + children = ( + FDF8487D29405993007DCAE5 /* HTTPHeader+SOGS.swift */, + FDF8487E29405994007DCAE5 /* HTTPQueryParam+SOGS.swift */, + FDC4381627B32EC700C60D73 /* Personalization.swift */, + FD02CC132C3677E6009AB976 /* Request+SOGS.swift */, + 7B81682228A4C1210069F315 /* UpdateTypes.swift */, + ); + path = Types; + sourceTree = ""; + }; + FD6B92A82E77A8B2004463B5 /* Models */ = { + isa = PBXGroup; + children = ( + FDC4386627B4E10E00C60D73 /* CapabilitiesResponse.swift */, + FDC4385C27B4C18900C60D73 /* Room.swift */, + FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */, + FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */, + FDC4387727B5C35400C60D73 /* SendSOGSMessageRequest.swift */, + FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */, + FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */, + FDC438C627BB6DF000C60D73 /* DirectMessage.swift */, + FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */, + FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */, + FDD20C192A0A03AC003898FB /* DeleteInboxResponse.swift */, + FDC438A327BB107F00C60D73 /* UserBanRequest.swift */, + FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */, + FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */, + 7B81682928B6F1420069F315 /* ReactionResponse.swift */, + ); + path = Models; + sourceTree = ""; + }; + FD6B92C32E77ACF2004463B5 /* Crypto */ = { + isa = PBXGroup; + children = ( + FD6B92C72E77AD35004463B5 /* Crypto+SOGS.swift */, + ); + path = Crypto; + sourceTree = ""; + }; + FD6B92C42E77AD01004463B5 /* Crypto */ = { + isa = PBXGroup; + children = ( + FD6B92C52E77AD0B004463B5 /* Crypto+FileServer.swift */, + ); + path = Crypto; + sourceTree = ""; + }; + FD6B92C92E77B1A7004463B5 /* SOGS */ = { + isa = PBXGroup; + children = ( + FD6B92D92E77B58B004463B5 /* Crypto */, + FD6B92CA2E77B1AE004463B5 /* Models */, + FD6B92D52E77B54B004463B5 /* Types */, + FDC4389927BA002500C60D73 /* SOGSAPISpec.swift */, + ); + path = SOGS; + sourceTree = ""; + }; + FD6B92CA2E77B1AE004463B5 /* Models */ = { + isa = PBXGroup; + children = ( + FD6B92CB2E77B1E5004463B5 /* CapabilitiesResponse.swift */, + FDC2908627D7047F005DAE71 /* RoomSpec.swift */, + FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */, + FDC2909027D709CA005DAE71 /* SOGSMessageSpec.swift */, + FDC2908A27D707F3005DAE71 /* SendSOGSMessageRequestSpec.swift */, + FDC2908E27D70938005DAE71 /* SendDirectMessageRequestSpec.swift */, + FDC2908C27D70905005DAE71 /* UpdateMessageRequestSpec.swift */, + ); + path = Models; + sourceTree = ""; + }; + FD6B92D52E77B54B004463B5 /* Types */ = { + isa = PBXGroup; + children = ( + FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */, + FDC2909327D710B4005DAE71 /* SOGSEndpointSpec.swift */, + FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */, + ); + path = Types; + sourceTree = ""; + }; + FD6B92D92E77B58B004463B5 /* Crypto */ = { + isa = PBXGroup; + children = ( + FD6B92DC2E77BB7E004463B5 /* Authentication+SOGS.swift */, + FD6B92DA2E77B592004463B5 /* CryptoSOGSAPISpec.swift */, + ); + path = Crypto; + sourceTree = ""; + }; + FD6B92DF2E77C1CB004463B5 /* PushNotification */ = { + isa = PBXGroup; + children = ( + FD6B92F52E77C6AF004463B5 /* Crypto */, + FDC4382D27B383A600C60D73 /* Models */, + FD6B92E52E77C33B004463B5 /* Types */, + FD6B92E02E77C1DC004463B5 /* PushNotification.swift */, + C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */, + FDC13D4F2A16EE50007267C7 /* PushNotificationEndpoint.swift */, + ); + path = PushNotification; + sourceTree = ""; + }; + FD6B92E52E77C33B004463B5 /* Types */ = { + isa = PBXGroup; + children = ( + FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */, + FD02CC112C367761009AB976 /* Request+PushNotificationAPI.swift */, + FDC13D482A16EC20007267C7 /* Service.swift */, + FD3765F52ADE5BA500DC1489 /* ServiceInfo.swift */, + ); + path = Types; + sourceTree = ""; + }; + FD6B92F52E77C6AF004463B5 /* Crypto */ = { + isa = PBXGroup; + children = ( + FD6B92F62E77C6D3004463B5 /* Crypto+PushNotification.swift */, + ); + path = Crypto; + sourceTree = ""; + }; FD7115F528C8150600B47552 /* Combine */ = { isa = PBXGroup; children = ( @@ -4459,7 +4666,7 @@ FD72BDA52BE369B600CF6CF6 /* Crypto */ = { isa = PBXGroup; children = ( - FD72BDA62BE369DC00CF6CF6 /* CryptoOpenGroupAPISpec.swift */, + FD72BDA62BE369DC00CF6CF6 /* CryptoOpenGroupSpec.swift */, ); path = Crypto; sourceTree = ""; @@ -4586,14 +4793,8 @@ FD83B9C127CF33EE005E1583 /* Models */ = { isa = PBXGroup; children = ( - FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */, + FD83B9C627CF3F10005E1583 /* CapabilitySpec.swift */, FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */, - FDC2908627D7047F005DAE71 /* RoomSpec.swift */, - FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */, - FDC2909027D709CA005DAE71 /* SOGSMessageSpec.swift */, - FDC2908A27D707F3005DAE71 /* SendMessageRequestSpec.swift */, - FDC2908E27D70938005DAE71 /* SendDirectMessageRequestSpec.swift */, - FDC2908C27D70905005DAE71 /* UpdateMessageRequestSpec.swift */, ); path = Models; sourceTree = ""; @@ -4728,6 +4929,7 @@ children = ( FD66CB272BF3449B00268FAB /* SessionNetworkingKit.xctestplan */, FD3765DD2AD8F02300DC1489 /* _TestUtilities */, + FD6B92C92E77B1A7004463B5 /* SOGS */, FDAA16792AC28E2200DDBF77 /* Models */, FD2272C52C34E9D1004D8A6C /* Types */, ); @@ -4741,11 +4943,6 @@ FD981BD62DC9A61600564172 /* NotificationCategory.swift */, FDB11A4B2DCC527900BEF49F /* NotificationContent.swift */, FD981BD82DC9A69000564172 /* NotificationUserInfoKey.swift */, - FDC13D482A16EC20007267C7 /* Service.swift */, - FD3765F52ADE5BA500DC1489 /* ServiceInfo.swift */, - FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */, - FDC13D4F2A16EE50007267C7 /* PushNotificationAPIEndpoint.swift */, - FD02CC112C367761009AB976 /* Request+PushNotificationAPI.swift */, ); path = Types; sourceTree = ""; @@ -4769,51 +4966,12 @@ path = LibSession; sourceTree = ""; }; - FDC2909227D710A9005DAE71 /* Types */ = { + FDC4381827B34EAD00C60D73 /* Types */ = { isa = PBXGroup; children = ( - FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */, - FDC2909327D710B4005DAE71 /* SOGSEndpointSpec.swift */, - FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */, - ); - path = Types; - sourceTree = ""; - }; - FDC4380727B31D3A00C60D73 /* Types */ = { - isa = PBXGroup; - children = ( - FDF8487D29405993007DCAE5 /* HTTPHeader+OpenGroup.swift */, - FDF8487E29405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift */, - FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */, - FDC4381627B32EC700C60D73 /* Personalization.swift */, - FD02CC132C3677E6009AB976 /* Request+OpenGroupAPI.swift */, - FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */, - 7B81682228A4C1210069F315 /* UpdateTypes.swift */, - ); - path = Types; - sourceTree = ""; - }; - FDC4381827B34EAD00C60D73 /* Models */ = { - isa = PBXGroup; - children = ( - FDC4386627B4E10E00C60D73 /* Capabilities.swift */, - FDC4385C27B4C18900C60D73 /* Room.swift */, - FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */, - FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */, - FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */, - FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */, - FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */, - FDC438C627BB6DF000C60D73 /* DirectMessage.swift */, - FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */, - FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */, - FDD20C192A0A03AC003898FB /* DeleteInboxResponse.swift */, - FDC438A327BB107F00C60D73 /* UserBanRequest.swift */, - FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */, - FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */, - 7B81682928B6F1420069F315 /* ReactionResponse.swift */, 7B81682B28B72F480069F315 /* PendingChange.swift */, ); - path = Models; + path = Types; sourceTree = ""; }; FDC4382D27B383A600C60D73 /* Models */ = { @@ -4824,11 +4982,6 @@ FDC13D4A2A16ECBA007267C7 /* SubscribeResponse.swift */, FDC13D552A171FE4007267C7 /* UnsubscribeRequest.swift */, FDC13D572A17207D007267C7 /* UnsubscribeResponse.swift */, - FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */, - FDC13D532A16FF29007267C7 /* LegacyGroupRequest.swift */, - FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */, - FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */, - FDC13D592A1721C5007267C7 /* LegacyNotifyRequest.swift */, FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */, ); path = Models; @@ -4857,8 +5010,6 @@ children = ( FD72BDA52BE369B600CF6CF6 /* Crypto */, FD83B9C127CF33EE005E1583 /* Models */, - FDC2909227D710A9005DAE71 /* Types */, - FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */, FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */, ); path = "Open Groups"; @@ -5064,14 +5215,10 @@ FD3765E82ADE1AAE00DC1489 /* UnrevokeSubaccountResponse.swift */, FDF848BA29405C5A007DCAE5 /* RevokeSubaccountRequest.swift */, FD02CC152C3681EF009AB976 /* RevokeSubaccountResponse.swift */, - FDF848AA29405C5A007DCAE5 /* SnodeReceivedMessage.swift */, - FDF848B429405C5A007DCAE5 /* SnodeMessage.swift */, FDF848AB29405C5A007DCAE5 /* GetNetworkTimestampResponse.swift */, FDF848A029405C5A007DCAE5 /* OxenDaemonRPCRequest.swift */, FDF848A629405C5A007DCAE5 /* ONSResolveRequest.swift */, FDF8489E29405C5A007DCAE5 /* ONSResolveResponse.swift */, - FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */, - FDC4383727B3863200C60D73 /* AppVersionResponse.swift */, ); path = Models; sourceTree = ""; @@ -6187,17 +6334,24 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FD6B92E12E77C1E1004463B5 /* PushNotification.swift in Sources */, FD2272B12C33E337004D8A6C /* ProxiedContentDownloader.swift in Sources */, FDF848C329405C5A007DCAE5 /* DeleteMessagesRequest.swift in Sources */, + FD6B928E2E779E99004463B5 /* FileServerEndpoint.swift in Sources */, FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */, FD2272AB2C33E337004D8A6C /* ValidatableResponse.swift in Sources */, FD2272B72C33E337004D8A6C /* Request.swift in Sources */, FD2272B92C33E337004D8A6C /* ResponseInfo.swift in Sources */, + FD6B92F82E77C725004463B5 /* ProcessResult.swift in Sources */, + FD6B92902E779EDD004463B5 /* FileServerAPI.swift in Sources */, FDF848D729405C5B007DCAE5 /* SnodeBatchRequest.swift in Sources */, FDF848CE29405C5B007DCAE5 /* UpdateExpiryAllRequest.swift in Sources */, FDF848C229405C5A007DCAE5 /* OxenDaemonRPCRequest.swift in Sources */, FDF848DC29405C5B007DCAE5 /* RevokeSubaccountRequest.swift in Sources */, FDF848D029405C5B007DCAE5 /* UpdateExpiryResponse.swift in Sources */, + FD6B92E82E77C5B7004463B5 /* PushNotificationEndpoint.swift in Sources */, + FD6B92AC2E77A993004463B5 /* SOGSEndpoint.swift in Sources */, + FD6B92922E779FC8004463B5 /* SessionNetwork.swift in Sources */, FDF848D329405C5B007DCAE5 /* UpdateExpiryAllResponse.swift in Sources */, FDD20C162A09E64A003898FB /* GetExpiriesRequest.swift in Sources */, FDF848BC29405C5A007DCAE5 /* SnodeRecursiveResponse.swift in Sources */, @@ -6205,9 +6359,17 @@ FD2272AC2C33E337004D8A6C /* IPv4.swift in Sources */, FD2272D82C34EDE7004D8A6C /* SnodeAPIEndpoint.swift in Sources */, FDFC4D9A29F0C51500992FB6 /* String+Trimming.swift in Sources */, + FD6B92992E77A06E004463B5 /* Token.swift in Sources */, FDB5DAF32A96DD4F002C8721 /* PreparedRequest+Sending.swift in Sources */, FDF848C629405C5B007DCAE5 /* DeleteAllMessagesRequest.swift in Sources */, FDF848D429405C5B007DCAE5 /* DeleteAllBeforeResponse.swift in Sources */, + FD6B92AF2E77AA03004463B5 /* HTTPQueryParam+SOGS.swift in Sources */, + FD6B92B02E77AA03004463B5 /* Request+SOGS.swift in Sources */, + FD6B92B12E77AA03004463B5 /* HTTPHeader+SOGS.swift in Sources */, + FD6B92F72E77C6D7004463B5 /* Crypto+PushNotification.swift in Sources */, + FD6B92B22E77AA03004463B5 /* UpdateTypes.swift in Sources */, + FD6B92B32E77AA03004463B5 /* Personalization.swift in Sources */, + FD6B929B2E77A084004463B5 /* NetworkInfo.swift in Sources */, FD47E0B52AA6D7AA00A55E41 /* Request+SnodeAPI.swift in Sources */, FD5E93D12C100FD70038C25A /* FileUploadResponse.swift in Sources */, FDF848D629405C5B007DCAE5 /* SnodeMessage.swift in Sources */, @@ -6219,35 +6381,69 @@ FDF848DD29405C5B007DCAE5 /* LegacySendMessageRequest.swift in Sources */, FDF848BD29405C5A007DCAE5 /* GetMessagesRequest.swift in Sources */, FD2272B02C33E337004D8A6C /* NetworkError.swift in Sources */, + FD6B92AB2E77A920004463B5 /* SOGS.swift in Sources */, FD19363C2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift in Sources */, - 947D7FD42D509FC900E8E413 /* SessionNetworkAPI+Models.swift in Sources */, + FD6B92E92E77C5D1004463B5 /* SubscribeResponse.swift in Sources */, + FD6B92EA2E77C5D1004463B5 /* NotificationMetadata.swift in Sources */, + FD6B92EB2E77C5D1004463B5 /* AuthenticatedRequest.swift in Sources */, + FD6B92EF2E77C5D1004463B5 /* UnsubscribeRequest.swift in Sources */, + FD6B92F02E77C5D1004463B5 /* SubscribeRequest.swift in Sources */, + FD6B92F22E77C5D1004463B5 /* UnsubscribeResponse.swift in Sources */, 947D7FD62D509FC900E8E413 /* SessionNetworkAPI.swift in Sources */, - 947D7FD72D509FC900E8E413 /* SessionNetworkAPI+Network.swift in Sources */, - 947D7FD82D509FC900E8E413 /* SessionNetworkAPI+Database.swift in Sources */, + 947D7FD72D509FC900E8E413 /* HTTPClient.swift in Sources */, + FD6B92B42E77AA11004463B5 /* PinnedMessage.swift in Sources */, + FD6B92B52E77AA11004463B5 /* SendDirectMessageResponse.swift in Sources */, + FD6B92B62E77AA11004463B5 /* UserUnbanRequest.swift in Sources */, + FD6B92B72E77AA11004463B5 /* UserModeratorRequest.swift in Sources */, + FD6B92B82E77AA11004463B5 /* SendSOGSMessageRequest.swift in Sources */, + FD6B92B92E77AA11004463B5 /* Room.swift in Sources */, + FD6B92BA2E77AA11004463B5 /* RoomPollInfo.swift in Sources */, + FD6B92BB2E77AA11004463B5 /* UpdateMessageRequest.swift in Sources */, + FD6B92BC2E77AA11004463B5 /* ReactionResponse.swift in Sources */, + FD6B92BD2E77AA11004463B5 /* UserBanRequest.swift in Sources */, + FD6B92BE2E77AA11004463B5 /* DirectMessage.swift in Sources */, + FD6B92BF2E77AA11004463B5 /* DeleteInboxResponse.swift in Sources */, + FD6B92C02E77AA11004463B5 /* SendDirectMessageRequest.swift in Sources */, + FD6B92C12E77AA11004463B5 /* CapabilitiesResponse.swift in Sources */, + FD6B92C22E77AA11004463B5 /* SOGSMessage.swift in Sources */, + 947D7FD82D509FC900E8E413 /* KeyValueStore+SessionNetwork.swift in Sources */, FDF848DB29405C5B007DCAE5 /* DeleteMessagesResponse.swift in Sources */, + FD6B92F42E77C61A004463B5 /* ServiceInfo.swift in Sources */, FDF848E629405D6E007DCAE5 /* Destination.swift in Sources */, + FD6B92A32E77A18B004463B5 /* SnodeAPI.swift in Sources */, FDF848CC29405C5B007DCAE5 /* SnodeReceivedMessage.swift in Sources */, FDF848C129405C5A007DCAE5 /* UpdateExpiryRequest.swift in Sources */, + FD6B92E22E77C21D004463B5 /* PushNotificationAPI.swift in Sources */, FDF848C729405C5B007DCAE5 /* SendMessageResponse.swift in Sources */, FDF848CA29405C5B007DCAE5 /* DeleteAllBeforeRequest.swift in Sources */, FD2272AF2C33E337004D8A6C /* JSON.swift in Sources */, FD2272D62C34ED6A004D8A6C /* RetryWithDependencies.swift in Sources */, FDF848D229405C5B007DCAE5 /* LegacyGetMessagesRequest.swift in Sources */, FDF848E529405D6E007DCAE5 /* SnodeAPIError.swift in Sources */, + FD6B928C2E779DCC004463B5 /* FileServer.swift in Sources */, FDF848D529405C5B007DCAE5 /* DeleteAllMessagesResponse.swift in Sources */, FD2272B22C33E337004D8A6C /* PreparedRequest.swift in Sources */, FDF848BF29405C5A007DCAE5 /* SnodeResponse.swift in Sources */, + FD6B92C82E77AD39004463B5 /* Crypto+SOGS.swift in Sources */, + FD6B92942E77A003004463B5 /* SessionNetworkEndpoint.swift in Sources */, FDD20C182A09E7D3003898FB /* GetExpiriesResponse.swift in Sources */, FD2272BB2C33E337004D8A6C /* HTTPMethod.swift in Sources */, + FD6B92AE2E77A9F7004463B5 /* SOGSAPI.swift in Sources */, + FD6B92972E77A047004463B5 /* Price.swift in Sources */, C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */, FDF848D929405C5B007DCAE5 /* SnodeAuthenticatedRequestBody.swift in Sources */, + FD6B92AD2E77A9F1004463B5 /* SOGSError.swift in Sources */, FD2272BA2C33E337004D8A6C /* HTTPHeader.swift in Sources */, FDF848CD29405C5B007DCAE5 /* GetNetworkTimestampResponse.swift in Sources */, FDF848DA29405C5B007DCAE5 /* GetMessagesResponse.swift in Sources */, - FDF8489429405C1B007DCAE5 /* SnodeAPI.swift in Sources */, + FD6B92C62E77AD0F004463B5 /* Crypto+FileServer.swift in Sources */, FD2286682C37DA3B00BC06F7 /* LibSession+Networking.swift in Sources */, FD2272A92C33E337004D8A6C /* ContentProxy.swift in Sources */, + FD6B92E62E77C5A2004463B5 /* Service.swift in Sources */, + FD6B92E72E77C5A2004463B5 /* Request+PushNotificationAPI.swift in Sources */, + FD6B92DE2E77BDE2004463B5 /* Authentication+SOGS.swift in Sources */, FDF848C829405C5B007DCAE5 /* ONSResolveRequest.swift in Sources */, + FD6B929D2E77A096004463B5 /* Info.swift in Sources */, FDF848C929405C5B007DCAE5 /* SnodeRequest.swift in Sources */, FDF848CF29405C5B007DCAE5 /* SendMessageRequest.swift in Sources */, FD2272BE2C34B710004D8A6C /* Publisher+Utilities.swift in Sources */, @@ -6310,7 +6506,6 @@ FD3765EA2ADE37B400DC1489 /* Authentication.swift in Sources */, FDE755202C9BC1A6002A2623 /* CacheConfig.swift in Sources */, FD1A553E2E14BE11003761E4 /* PagedData.swift in Sources */, - FD4BB22C2D63FA8600D0DC3D /* (null) in Sources */, FDE755192C9BC169002A2623 /* UIImage+Utilities.swift in Sources */, C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Utilities.swift in Sources */, FD97B2402A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift in Sources */, @@ -6418,7 +6613,6 @@ FD981BD72DC9A61A00564172 /* NotificationCategory.swift in Sources */, C300A5D32554B05A00555489 /* TypingIndicator.swift in Sources */, FDF71EA52B07363500A8D6B5 /* MessageReceiver+LibSession.swift in Sources */, - FDC13D582A17207D007267C7 /* UnsubscribeResponse.swift in Sources */, FD09799927FFC1A300936362 /* Attachment.swift in Sources */, FD245C5F2850662200B966DD /* OWSWindowManager.m in Sources */, FDF40CDE2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift in Sources */, @@ -6442,11 +6636,9 @@ FD22727E2C32911C004D8A6C /* GarbageCollectionJob.swift in Sources */, FD09B7E7288670FD00ED0B66 /* Reaction.swift in Sources */, FD245C5A2850660100B966DD /* LinkPreviewDraft.swift in Sources */, - FDD82C3F2A205D0A00425F05 /* ProcessResult.swift in Sources */, FDE5219E2E0D0B9B00061B8E /* AsyncAccessible.swift in Sources */, FDF0B75C2807F41D004C14C5 /* MessageSender+Convenience.swift in Sources */, FD22726D2C32911C004D8A6C /* CheckForAppUpdatesJob.swift in Sources */, - 7B81682A28B6F1420069F315 /* ReactionResponse.swift in Sources */, FD2273082C353109004D8A6C /* DisplayPictureManager.swift in Sources */, FDE521A22E0D23AB00061B8E /* ObservableKey+SessionMessagingKit.swift in Sources */, FD2273022C352D8E004D8A6C /* LibSession+GroupInfo.swift in Sources */, @@ -6460,49 +6652,36 @@ FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */, FD428B232B4B9969006D0888 /* _031_RebuildFTSIfNeeded_2_4_5.swift in Sources */, FD2272742C32911C004D8A6C /* ConfigMessageReceiveJob.swift in Sources */, - FDFBB74D2A1F3C4E00CA7350 /* NotificationMetadata.swift in Sources */, FD716E6628502EE200C96BF4 /* CurrentCallProtocol.swift in Sources */, FD22727A2C32911C004D8A6C /* GroupInviteMemberJob.swift in Sources */, FDB5DAC72A9447E7002C8721 /* _036_GroupsRebuildChanges.swift in Sources */, FD09B7E5288670BB00ED0B66 /* _017_EmojiReacts.swift in Sources */, - FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */, - FDD20C1A2A0A03AC003898FB /* DeleteInboxResponse.swift in Sources */, 7B8D5FC428332600008324D9 /* VisibleMessage+Reaction.swift in Sources */, - FDC4386527B4DE7600C60D73 /* RoomPollInfo.swift in Sources */, FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */, FD2272FA2C352D8E004D8A6C /* LibSession+SharedGroup.swift in Sources */, FD37EA0F28AB3330003AE748 /* _014_FixHiddenModAdminSupport.swift in Sources */, FD2272772C32911C004D8A6C /* AttachmentUploadJob.swift in Sources */, - 7B81682328A4C1210069F315 /* UpdateTypes.swift in Sources */, - FDC13D472A16E4CA007267C7 /* SubscribeRequest.swift in Sources */, - FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */, FD22727C2C32911C004D8A6C /* GroupPromoteMemberJob.swift in Sources */, FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */, - FDC4386727B4E10E00C60D73 /* Capabilities.swift in Sources */, - FDC438A427BB107F00C60D73 /* UserBanRequest.swift in Sources */, FDE754F22C9BB08B002A2623 /* Crypto+SessionMessagingKit.swift in Sources */, FD4C53AF2CC1D62E003B10F4 /* _035_ReworkRecipientState.swift in Sources */, C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */, FD6C67242CF6E72E00B350A7 /* NoopSessionCallManager.swift in Sources */, FDB4BBC72838B91E00B7C95D /* LinkPreviewError.swift in Sources */, FD09798327FD1A1500936362 /* ClosedGroup.swift in Sources */, - FDC13D542A16FF29007267C7 /* LegacyGroupRequest.swift in Sources */, B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */, FD09798727FD1B7800936362 /* GroupMember.swift in Sources */, FD78EA0D2DDFEDE200D55B50 /* LibSession+Local.swift in Sources */, - FDCD2E032A41294E00964D6A /* LegacyGroupOnlyRequest.swift in Sources */, FDD23AE12E457CDE0057E853 /* _005_SNK_SetupStandardJobs.swift in Sources */, FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */, FD09C5EC282B8F18000CE219 /* AttachmentError.swift in Sources */, FDE754F02C9BB08B002A2623 /* Crypto+Attachments.swift in Sources */, FD17D79927F40AB800122BE0 /* _009_SMK_YDBToGRDBMigration.swift in Sources */, - FDE754A12C9A60A6002A2623 /* Crypto+OpenGroupAPI.swift in Sources */, + FDE754A12C9A60A6002A2623 /* Crypto+OpenGroup.swift in Sources */, FDF0B7512807BA56004C14C5 /* NotificationsManagerType.swift in Sources */, FD2272722C32911C004D8A6C /* FailedAttachmentDownloadsJob.swift in Sources */, - FDC13D5A2A1721C5007267C7 /* LegacyNotifyRequest.swift in Sources */, FDD23AEA2E458EB00057E853 /* _012_AddJobPriority.swift in Sources */, FD245C59285065FC00B966DD /* ControlMessage.swift in Sources */, - FD6E4C8A2A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift in Sources */, B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */, FDD23AEC2E458F980057E853 /* _024_ResetUserConfigLastHashes.swift in Sources */, FD245C50285065C700B966DD /* VisibleMessage+Quote.swift in Sources */, @@ -6523,7 +6702,6 @@ FD8FD7622C37B7BD001E38C7 /* Position.swift in Sources */, 7B93D07127CF194000811CB6 /* MessageRequestResponse.swift in Sources */, FDB5DADE2A95D847002C8721 /* GroupUpdatePromoteMessage.swift in Sources */, - FD245C662850665900B966DD /* OpenGroupAPI.swift in Sources */, FD245C5B2850660500B966DD /* ReadReceipt.swift in Sources */, FD428B1F2B4B758B006D0888 /* AppReadiness.swift in Sources */, FD22726B2C32911C004D8A6C /* SendReadReceiptsJob.swift in Sources */, @@ -6532,7 +6710,6 @@ C3C2A7852553AAF300C340D1 /* SessionProtos.pb.swift in Sources */, FDF0B7422804EA4F004C14C5 /* _007_SMK_SetupStandardJobs.swift in Sources */, B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, - FDF8487F29405994007DCAE5 /* HTTPHeader+OpenGroup.swift in Sources */, FD8ECF7D2934293A00C0D1BB /* _027_SessionUtilChanges.swift in Sources */, FD17D7A227F40F0500122BE0 /* _006_SMK_InitialSetupMigration.swift in Sources */, FD245C5D2850660F00B966DD /* OWSAudioPlayer.m in Sources */, @@ -6541,28 +6718,19 @@ FD09798D27FD1D8900936362 /* DisappearingMessageConfiguration.swift in Sources */, FD2272732C32911C004D8A6C /* ConfigurationSyncJob.swift in Sources */, FDF0B75A2807F3A3004C14C5 /* MessageSenderError.swift in Sources */, - FDC4382F27B383AF00C60D73 /* LegacyPushServerResponse.swift in Sources */, 94B6BAFA2E38454F00E718BB /* SessionProState.swift in Sources */, - FDC4386327B4D94E00C60D73 /* SOGSMessage.swift in Sources */, FD245C692850666800B966DD /* ExpirationTimerUpdate.swift in Sources */, - FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */, FD2272752C32911C004D8A6C /* RetrieveDefaultOpenGroupRoomsJob.swift in Sources */, FD2272712C32911C004D8A6C /* MessageReceiveJob.swift in Sources */, FDB5DAC12A9443A5002C8721 /* MessageSender+Groups.swift in Sources */, - FDC13D4B2A16ECBA007267C7 /* SubscribeResponse.swift in Sources */, FD2272702C32911C004D8A6C /* DisappearingMessagesJob.swift in Sources */, FD7115F228C6CB3900B47552 /* _019_AddThreadIdToFTS.swift in Sources */, FD716E6428502DDD00C96BF4 /* CallManagerProtocol.swift in Sources */, 943C6D822B75E061004ACE64 /* Message+DisappearingMessages.swift in Sources */, - FDC438C727BB6DF000C60D73 /* DirectMessage.swift in Sources */, - FD3765F42ADE5A0800DC1489 /* AuthenticatedRequest.swift in Sources */, - FDC13D502A16EE50007267C7 /* PushNotificationAPIEndpoint.swift in Sources */, FD432434299C6985008A0213 /* PendingReadReceipt.swift in Sources */, - FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */, FD78E9F62DDD43AD00D55B50 /* Mutation.swift in Sources */, FDB11A5F2DD5B77800BEF49F /* Message+Origin.swift in Sources */, FD245C51285065CC00B966DD /* MessageReceiver.swift in Sources */, - FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */, FDB5DAE62A95D8B0002C8721 /* GroupUpdateDeleteMemberContentMessage.swift in Sources */, 7B5233C6290636D700F8F375 /* _032_DisappearingMessagesConfiguration.swift in Sources */, FD5C72FD284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift in Sources */, @@ -6578,34 +6746,26 @@ FD37EA0D28AB2A45003AE748 /* _013_FixDeletedMessageReadState.swift in Sources */, FDD23AE32E457CFE0057E853 /* _010_FlagMessageHashAsDeletedOrInvalid.swift in Sources */, 7BAA7B6628D2DE4700AE1489 /* _018_OpenGroupPermission.swift in Sources */, - FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */, FD2286692C37DA5500BC06F7 /* PollerType.swift in Sources */, FD860CBC2D6E7A9F00BBE29C /* _038_FixBustedInteractionVariant.swift in Sources */, FDD23AE72E458DBC0057E853 /* _001_SUK_InitialSetupMigration.swift in Sources */, FDD23AE42E458C810057E853 /* _021_AddSnodeReveivedMessageInfoPrimaryKey.swift in Sources */, FD22727B2C32911C004D8A6C /* MessageSendJob.swift in Sources */, FD78E9EE2DD6D32500D55B50 /* ImageDataManager+Singleton.swift in Sources */, - FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */, - FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */, C3A71D1F25589AC30043A11F /* WebSocketResources.pb.swift in Sources */, FD981BC42DC304E600564172 /* MessageDeduplication.swift in Sources */, FDF0B74B28061F7A004C14C5 /* InteractionAttachment.swift in Sources */, FD09796E27FA6D0000936362 /* Contact.swift in Sources */, - FD02CC142C3677E6009AB976 /* Request+OpenGroupAPI.swift in Sources */, FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */, - FDC13D492A16EC20007267C7 /* Service.swift in Sources */, FDBA8A842D597975007C19C0 /* FailedGroupInvitesAndPromotionsJob.swift in Sources */, FD778B6429B189FF001BAC6B /* _028_GenerateInitialUserConfigDumps.swift in Sources */, - FDC13D562A171FE4007267C7 /* UnsubscribeRequest.swift in Sources */, FD1A55432E179AED003761E4 /* ObservableKeyEvent+Utilities.swift in Sources */, C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, FDD23AE92E458E020057E853 /* _003_SUK_YDBToGRDBMigration.swift in Sources */, - FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */, FD22727F2C32911C004D8A6C /* GetExpirationJob.swift in Sources */, FD2272792C32911C004D8A6C /* DisplayPictureDownloadJob.swift in Sources */, FD981BD92DC9A69600564172 /* NotificationUserInfoKey.swift in Sources */, - C352A2FF25574B6300338F3E /* (null) in Sources */, FDD23AE52E458C940057E853 /* _022_DropSnodeCache.swift in Sources */, FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */, B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */, @@ -6620,7 +6780,6 @@ FD2273012C352D8E004D8A6C /* LibSession+Shared.swift in Sources */, C3C2A74425539EB700C340D1 /* Message.swift in Sources */, FD245C682850666300B966DD /* Message+Destination.swift in Sources */, - FDF8488029405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift in Sources */, FD8A5B322DC191B4004C689B /* _039_DropLegacyClosedGroupKeyPairTable.swift in Sources */, FD245C632850664600B966DD /* Configuration.swift in Sources */, FD981BC62DC3310B00564172 /* ExtensionHelper.swift in Sources */, @@ -6639,7 +6798,6 @@ FD09798B27FD1CFE00936362 /* Capability.swift in Sources */, C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */, FD09798127FCFEE800936362 /* SessionThread.swift in Sources */, - FD3765F62ADE5BA500DC1489 /* ServiceInfo.swift in Sources */, FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */, FDB5DADA2A95D839002C8721 /* GroupUpdateInfoChangeMessage.swift in Sources */, FDF71EA32B072C2800A8D6B5 /* LibSessionMessage.swift in Sources */, @@ -6648,9 +6806,9 @@ FDE754FE2C9BB0D0002A2623 /* Threading+SMK.swift in Sources */, FDF0B75E280AAF35004C14C5 /* Preferences.swift in Sources */, FDF2F0222DAE1AF500491E8A /* MessageReceiver+LegacyClosedGroups.swift in Sources */, - FD02CC122C367762009AB976 /* Request+PushNotificationAPI.swift in Sources */, FD05594E2E012D2700DC48CE /* _043_RenameAttachments.swift in Sources */, FD22726E2C32911C004D8A6C /* FailedMessageSendsJob.swift in Sources */, + FD6B92E42E77C256004463B5 /* PushNotificationAPI+SMK.swift in Sources */, FDB5DAD42A9483F3002C8721 /* GroupUpdateInviteMessage.swift in Sources */, FDB5DAE22A95D8A0002C8721 /* GroupUpdateInviteResponseMessage.swift in Sources */, FDB11A522DCC6B0000BEF49F /* OpenGroupUrlInfo.swift in Sources */, @@ -6658,9 +6816,6 @@ FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */, FDD23AE02E457CD40057E853 /* _004_SNK_InitialSetupMigration.swift in Sources */, FD5C7303284F0FA50029977D /* MessageReceiver+Calls.swift in Sources */, - FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */, - FDC438AA27BB12BB00C60D73 /* UserModeratorRequest.swift in Sources */, - FDC4385D27B4C18900C60D73 /* Room.swift in Sources */, FDE755022C9BB122002A2623 /* _025_AddPendingReadReceipts.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -6692,7 +6847,6 @@ B877E24626CA13BA0007970A /* CallVC+Camera.swift in Sources */, 454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */, FD3FAB5F2AE9BC2200DC5421 /* EditGroupViewModel.swift in Sources */, - FDEF57222C3CF03D00131302 /* (null) in Sources */, 7BA37AFB2AEB64CA002438F8 /* DisappearingMessageTimerView.swift in Sources */, FD12A8492AD63C4700EEBA0D /* SessionNavItem.swift in Sources */, FD12A83D2AD63BCC00EEBA0D /* EditableState.swift in Sources */, @@ -6722,7 +6876,6 @@ B886B4A92398BA1500211ABE /* QRCode.swift in Sources */, 34A8B3512190A40E00218A25 /* MediaAlbumView.swift in Sources */, FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */, - FDEF57262C3CF05F00131302 /* (null) in Sources */, 3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */, 7BA37AFD2AEF7C3D002438F8 /* VoiceMessageView_SwiftUI.swift in Sources */, 7B1B52E028580D51006069F2 /* EmojiSkinTonePicker.swift in Sources */, @@ -6766,7 +6919,6 @@ 7BBBDC462875600700747E59 /* DocumentTitleViewController.swift in Sources */, FD71163F28E2C82C00B47552 /* SessionHeaderView.swift in Sources */, B877E24226CA12910007970A /* CallVC.swift in Sources */, - FDEF57232C3CF04300131302 /* (null) in Sources */, FDC498B92AC15FE300EDD897 /* AppNotificationAction.swift in Sources */, FD7443402D07A25C00862443 /* PushRegistrationManager.swift in Sources */, 7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */, @@ -6800,7 +6952,6 @@ 7BAF54CF27ACCEEC003D12F8 /* GlobalSearchViewController.swift in Sources */, FD37EA1728AC5605003AE748 /* NotificationContentViewModel.swift in Sources */, FD3FAB612AEA194E00DC5421 /* UserListViewModel.swift in Sources */, - B886B4A72398B23E00211ABE /* (null) in Sources */, 94CD96322E1B88C20097754D /* ExpandingAttachmentsButton.swift in Sources */, 94B3DC172AF8592200C88531 /* QuoteView_SwiftUI.swift in Sources */, 4C4AE6A1224AF35700D4AF6F /* SendMediaNavigationController.swift in Sources */, @@ -6840,13 +6991,11 @@ 7B9F71D42852EEE2006DFE7B /* Emoji+Name.swift in Sources */, 4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */, C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */, - FDEF57242C3CF04700131302 /* (null) in Sources */, 9479981C2DD44ADC008F5CD5 /* ThreadNotificationSettingsViewModel.swift in Sources */, 34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */, 9422568C2C23F8C800C0FDBF /* DisplayNameScreen.swift in Sources */, 7B9F71D72853100A006DFE7B /* Emoji+Available.swift in Sources */, FD09C5E628260FF9000CE219 /* MediaGalleryViewModel.swift in Sources */, - FDEF57212C3CF03A00131302 /* (null) in Sources */, 7B9F71D32852EEE2006DFE7B /* Emoji.swift in Sources */, C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */, 3488F9362191CC4000E524CC /* MediaView.swift in Sources */, @@ -6886,7 +7035,6 @@ 940943402C7ED62300D9D2E0 /* StartupError.swift in Sources */, FD368A6A29DE9E30000DBF1E /* UIContextualAction+Utilities.swift in Sources */, 947D7FDE2D5180F200E8E413 /* SessionNetworkScreen+ViewModel.swift in Sources */, - FDEF57252C3CF04C00131302 /* (null) in Sources */, 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */, FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */, FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */, @@ -6995,6 +7143,8 @@ FDB5DB062A981C67002C8721 /* PreparedRequestSendingSpec.swift in Sources */, FD49E2482B05C1D500FFBBB5 /* MockKeychain.swift in Sources */, FDB5DB082A981F8B002C8721 /* Mocked.swift in Sources */, + FD6B92CD2E77B22D004463B5 /* SOGSMessageSpec.swift in Sources */, + FD6B92DB2E77B597004463B5 /* CryptoSOGSAPISpec.swift in Sources */, FD336F702CABB96C00C0B51B /* BatchRequestSpec.swift in Sources */, FD3765E22AD8F53B00DC1489 /* CommonSSKMockExtensions.swift in Sources */, FDB5DB142A981FAE002C8721 /* CombineExtensions.swift in Sources */, @@ -7003,11 +7153,19 @@ FD3765DF2AD8F03100DC1489 /* MockSnodeAPICache.swift in Sources */, FDB11A582DD17D0600BEF49F /* MockLogger.swift in Sources */, FDB5DB112A981FA6002C8721 /* TestExtensions.swift in Sources */, + FD6B92D22E77B270004463B5 /* SendDirectMessageRequestSpec.swift in Sources */, + FD6B92D32E77B270004463B5 /* UpdateMessageRequestSpec.swift in Sources */, FDB5DB092A981F8D002C8721 /* MockCrypto.swift in Sources */, FDAA167B2AC28E2F00DDBF77 /* SnodeRequestSpec.swift in Sources */, + FD6B92D02E77B23B004463B5 /* CapabilitiesResponse.swift in Sources */, + FD6B92D42E77B2C7004463B5 /* SOGSAPISpec.swift in Sources */, FD65318C2AA025C500DFEEAA /* TestDependencies.swift in Sources */, FDB5DB102A981FA3002C8721 /* TestConstants.swift in Sources */, FDB5DB0C2A981F96002C8721 /* MockNetwork.swift in Sources */, + FD6B92D12E77B253004463B5 /* SendSOGSMessageRequestSpec.swift in Sources */, + FD6B92D62E77B55D004463B5 /* SOGSEndpointSpec.swift in Sources */, + FD6B92D72E77B55D004463B5 /* SOGSErrorSpec.swift in Sources */, + FD6B92D82E77B55D004463B5 /* PersonalizationSpec.swift in Sources */, FD336F712CABB97800C0B51B /* DestinationSpec.swift in Sources */, FD336F722CABB97800C0B51B /* BatchResponseSpec.swift in Sources */, FD336F732CABB97800C0B51B /* HeaderSpec.swift in Sources */, @@ -7016,6 +7174,8 @@ FD336F762CABB97800C0B51B /* BencodeResponseSpec.swift in Sources */, FDB5DB0B2A981F92002C8721 /* MockGeneralCache.swift in Sources */, FD0150492CA243CB005B08A1 /* Mock.swift in Sources */, + FD6B92CE2E77B234004463B5 /* RoomPollInfoSpec.swift in Sources */, + FD6B92CF2E77B234004463B5 /* RoomSpec.swift in Sources */, FDB5DB122A981FA8002C8721 /* NimbleExtensions.swift in Sources */, FD0150282CA23DB7005B08A1 /* GRDBExtensions.swift in Sources */, FDB5DB152A981FB0002C8721 /* SynchronousStorage.swift in Sources */, @@ -7027,21 +7187,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - FD72BDA72BE369DC00CF6CF6 /* CryptoOpenGroupAPISpec.swift in Sources */, - FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */, + FD72BDA72BE369DC00CF6CF6 /* CryptoOpenGroupSpec.swift in Sources */, FD96F3A529DBC3DC00401309 /* MessageSendJobSpec.swift in Sources */, - FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */, - FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */, - FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */, FDE754A82C9B964D002A2623 /* MessageReceiverGroupsSpec.swift in Sources */, FD01504C2CA243CB005B08A1 /* Mock.swift in Sources */, FD981BC92DC4641100564172 /* ExtensionHelperSpec.swift in Sources */, FD981BCB2DC4A21C00564172 /* MessageDeduplicationSpec.swift in Sources */, - FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */, + FD83B9C727CF3F10005E1583 /* CapabilitySpec.swift in Sources */, FD2AAAF128ED57B500A49611 /* SynchronousStorage.swift in Sources */, FD23CE2A2A6775660000B97C /* MockCrypto.swift in Sources */, FD336F6F2CAA37CB00C0B51B /* MockCommunityPoller.swift in Sources */, - FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */, FD481A962CAE0AE000ECC4CF /* MockAppContext.swift in Sources */, FDB11A562DD17C3300BEF49F /* MockLogger.swift in Sources */, FD0150452CA243BB005B08A1 /* LibSessionUtilSpec.swift in Sources */, @@ -7065,26 +7220,21 @@ FD65318B2AA025C500DFEEAA /* TestDependencies.swift in Sources */, FD49E2472B05C1D500FFBBB5 /* MockKeychain.swift in Sources */, FD3765E32AD8F56200DC1489 /* CommonSSKMockExtensions.swift in Sources */, - FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */, FD23EA6228ED0B260058676E /* CombineExtensions.swift in Sources */, FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */, FD23CE342A67C4D90000B97C /* MockNetwork.swift in Sources */, FD61FCF92D308CC9005752DE /* GroupMemberSpec.swift in Sources */, - FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */, FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */, FD0969F92A69FFE700C5C365 /* Mocked.swift in Sources */, FD481A902CAD16F100ECC4CF /* LibSessionGroupInfoSpec.swift in Sources */, FDE754A92C9B964D002A2623 /* MessageSenderGroupsSpec.swift in Sources */, - FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */, FD3FAB672AF0C47000DC5421 /* DisplayPictureDownloadJobSpec.swift in Sources */, FD72BDA42BE3690B00CF6CF6 /* CryptoSMKSpec.swift in Sources */, - FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */, FD336F6C2CAA29C600C0B51B /* CommunityPollerSpec.swift in Sources */, FD481AA32CB889AE00ECC4CF /* RetrieveDefaultOpenGroupRoomsJobSpec.swift in Sources */, FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */, FD01502A2CA23DB7005B08A1 /* GRDBExtensions.swift in Sources */, - FDC2908D27D70905005DAE71 /* UpdateMessageRequestSpec.swift in Sources */, FD01503B2CA24328005B08A1 /* MockJobRunner.swift in Sources */, FD3F2EE72DE6CC4100FD6849 /* NotificationsManagerSpec.swift in Sources */, FD078E5427E197CA000769AF /* OpenGroupManagerSpec.swift in Sources */, diff --git a/Session/Closed Groups/EditGroupViewModel.swift b/Session/Closed Groups/EditGroupViewModel.swift index 8309d70282..b7fe384e4d 100644 --- a/Session/Closed Groups/EditGroupViewModel.swift +++ b/Session/Closed Groups/EditGroupViewModel.swift @@ -534,7 +534,7 @@ class EditGroupViewModel: SessionTableViewModel, NavigatableStateHolder, Editabl case (.some(let inviteByIdValue), _): // This could be an ONS name let viewController = ModalActivityIndicatorViewController() { modalActivityIndicator in - SnodeAPI + Network.SnodeAPI .getSessionID(for: inviteByIdValue, using: dependencies) .subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies) .receive(on: DispatchQueue.main, using: dependencies) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 814a8591e1..70558f64e7 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1746,7 +1746,7 @@ extension ConversationVC: let openGroupServerMessageId: Int64 = cellViewModel.openGroupServerMessageId else { return } - let pendingChange: OpenGroupAPI.PendingChange = viewModel.dependencies[singleton: .openGroupManager] + let pendingChange: OpenGroupManager.PendingChange = viewModel.dependencies[singleton: .openGroupManager] .addPendingReaction( emoji: emoji, id: openGroupServerMessageId, @@ -1756,7 +1756,7 @@ extension ConversationVC: ) Result { - try OpenGroupAPI.preparedReactionDeleteAll( + try Network.SOGS.preparedReactionDeleteAll( emoji: emoji, id: openGroupServerMessageId, roomToken: roomToken, @@ -1831,14 +1831,14 @@ extension ConversationVC: typealias OpenGroupInfo = ( pendingReaction: Reaction?, - pendingChange: OpenGroupAPI.PendingChange, + pendingChange: OpenGroupManager.PendingChange, preparedRequest: Network.PreparedRequest ) /// Perform the sending logic, we generate the pending reaction first in a deferred future closure to prevent the OpenGroup /// cache from blocking either the main thread or the database write thread Deferred { [dependencies = viewModel.dependencies] in - Future { resolver in + Future { resolver in guard threadVariant == .community, let serverMessageId: Int64 = cellViewModel.openGroupServerMessageId, @@ -1859,7 +1859,7 @@ extension ConversationVC: } } .subscribe(on: DispatchQueue.global(qos: .userInitiated), using: viewModel.dependencies) - .flatMapStorageWritePublisher(using: viewModel.dependencies) { [weak self, dependencies = viewModel.dependencies] db, pendingChange -> (OpenGroupAPI.PendingChange?, Reaction?, Message.Destination, AuthenticationMethod) in + .flatMapStorageWritePublisher(using: viewModel.dependencies) { [weak self, dependencies = viewModel.dependencies] db, pendingChange -> (OpenGroupManager.PendingChange?, Reaction?, Message.Destination, AuthenticationMethod) in // Update the thread to be visible (if it isn't already) if self?.viewModel.threadData.threadShouldBeVisible == false { try SessionThread.updateVisibility( @@ -1937,12 +1937,12 @@ extension ConversationVC: let serverMessageId: Int64 = cellViewModel.openGroupServerMessageId, let openGroupServer: String = cellViewModel.threadOpenGroupServer, let openGroupRoom: String = openGroupRoom, - let pendingChange: OpenGroupAPI.PendingChange = pendingChange + let pendingChange: OpenGroupManager.PendingChange = pendingChange else { throw MessageSenderError.invalidMessage } let preparedRequest: Network.PreparedRequest = try { guard !remove else { - return try OpenGroupAPI + return try Network.SOGS .preparedReactionDelete( emoji: emoji, id: serverMessageId, @@ -1953,7 +1953,7 @@ extension ConversationVC: .map { _, response in response.seqNo } } - return try OpenGroupAPI + return try Network.SOGS .preparedReactionAdd( emoji: emoji, id: serverMessageId, @@ -2579,7 +2579,7 @@ extension ConversationVC: } .publisher .tryFlatMap { (roomToken: String, authMethod: AuthenticationMethod) in - try OpenGroupAPI.preparedUserBan( + try Network.SOGS.preparedUserBan( sessionId: cellViewModel.authorId, from: [roomToken], authMethod: authMethod, @@ -2657,7 +2657,7 @@ extension ConversationVC: } .publisher .tryFlatMap { (roomToken: String, authMethod: AuthenticationMethod) in - try OpenGroupAPI.preparedUserBanAndDeleteAllMessages( + try Network.SOGS.preparedUserBanAndDeleteAllMessages( sessionId: cellViewModel.authorId, roomToken: roomToken, authMethod: authMethod, diff --git a/Session/Home/New Conversation/NewMessageScreen.swift b/Session/Home/New Conversation/NewMessageScreen.swift index 725fa06ed8..4eb0dbfa9f 100644 --- a/Session/Home/New Conversation/NewMessageScreen.swift +++ b/Session/Home/New Conversation/NewMessageScreen.swift @@ -87,7 +87,7 @@ struct NewMessageScreen: View { // This could be an ONS name ModalActivityIndicatorViewController .present(fromViewController: self.host.controller?.navigationController!, canCancel: false) { modalActivityIndicator in - SnodeAPI + Network.SnodeAPI .getSessionID(for: accountIdOrONS, using: dependencies) .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) diff --git a/Session/Media Viewing & Editing/MessageInfoScreen.swift b/Session/Media Viewing & Editing/MessageInfoScreen.swift index f4c447dd46..9456f8dc42 100644 --- a/Session/Media Viewing & Editing/MessageInfoScreen.swift +++ b/Session/Media Viewing & Editing/MessageInfoScreen.swift @@ -183,7 +183,7 @@ struct MessageInfoScreen: View { spacing: Values.mediumSpacing ) { InfoBlock(title: "attachmentsFileId".localized()) { - Text(attachment.downloadUrl.map { Attachment.fileId(for: $0) } ?? "") + Text(attachment.downloadUrl.map { Network.FileServer.fileId(for: $0) } ?? "") .font(.system(size: Values.mediumFontSize)) .foregroundColor(themeColor: .textPrimary) } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index cb0c65534e..e70361c687 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -704,7 +704,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD self?.startPollersIfNeeded() - SessionNetworkAPI.client.initialize(using: dependencies) + Network.SessionNetwork.client.initialize(using: dependencies) if dependencies[singleton: .appContext].isMainApp { DispatchQueue.main.async { diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 7cf9efb5a4..5d32a61072 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -82,7 +82,7 @@ public enum SyncPushTokensJob: JobExecutor { // Unregister from our server if let existingToken: String = lastRecordedPushToken { Log.info(.syncPushTokensJob, "Unregister using last recorded push token: \(redact(existingToken))") - return PushNotificationAPI + return Network.PushNotification .unsubscribeAll(token: Data(hex: existingToken), using: dependencies) .map { _ in () } .eraseToAnyPublisher() @@ -177,7 +177,7 @@ public enum SyncPushTokensJob: JobExecutor { } Log.info(.syncPushTokensJob, "Sending push token to PN server") - return PushNotificationAPI + return Network.PushNotification .subscribeAll( token: Data(hex: pushToken), isForcedUpdate: true, diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index 5b3f4e4177..0c5fa3c2cc 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -6,6 +6,7 @@ import AVFoundation import GRDB import SessionUIKit import SessionMessagingKit +import SessionNetworkingKit import SessionUtilitiesKit import SignalUtilitiesKit @@ -485,11 +486,11 @@ private final class EnterURLVC: UIViewController, UIGestureRecognizerDelegate, O ) } - func join(_ room: OpenGroupAPI.Room) { + func join(_ room: Network.SOGS.Room) { joinOpenGroupVC?.joinOpenGroup( roomToken: room.token, - server: OpenGroupAPI.defaultServer, - publicKey: OpenGroupAPI.defaultServerPublicKey, + server: Network.SOGS.defaultServer, + publicKey: Network.SOGS.defaultServerPublicKey, shouldOpenCommunity: true, onError: nil ) diff --git a/Session/Open Groups/OpenGroupSuggestionGrid.swift b/Session/Open Groups/OpenGroupSuggestionGrid.swift index 28bbe31ee6..ddc4c72ba9 100644 --- a/Session/Open Groups/OpenGroupSuggestionGrid.swift +++ b/Session/Open Groups/OpenGroupSuggestionGrid.swift @@ -4,6 +4,7 @@ import Foundation import GRDB import Combine import NVActivityIndicatorView +import SessionNetworkingKit import SessionMessagingKit import SessionUIKit import SessionUtilitiesKit @@ -354,7 +355,7 @@ extension OpenGroupSuggestionGrid { snContentView.pin(to: self) } - fileprivate func update(with room: OpenGroupAPI.Room, openGroup: OpenGroup, using dependencies: Dependencies) { + fileprivate func update(with room: Network.SOGS.Room, openGroup: OpenGroup, using dependencies: Dependencies) { label.text = room.name let maybePath: String? = openGroup.displayPictureOriginalUrl @@ -380,7 +381,7 @@ extension OpenGroupSuggestionGrid { // MARK: - Delegate protocol OpenGroupSuggestionGridDelegate { - func join(_ room: OpenGroupAPI.Room) + func join(_ room: Network.SOGS.Room) } // MARK: - LastRowCenteredLayout diff --git a/Session/Settings/DeveloperSettingsViewModel.swift b/Session/Settings/DeveloperSettingsViewModel.swift index 263ae04ace..78ea7b150c 100644 --- a/Session/Settings/DeveloperSettingsViewModel.swift +++ b/Session/Settings/DeveloperSettingsViewModel.swift @@ -239,7 +239,7 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, let serviceNetwork: ServiceNetwork let forceOffline: Bool - let pushNotificationService: PushNotificationAPI.Service + let pushNotificationService: Network.PushNotification.Service let debugDisappearingMessageDurations: Bool @@ -594,9 +594,9 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, onTap: { [weak self, dependencies] in self?.transitionToScreen( SessionTableViewController( - viewModel: SessionListViewModel( + viewModel: SessionListViewModel( title: "Push Notification Service", - options: PushNotificationAPI.Service.allCases, + options: Network.PushNotification.Service.allCases, behaviour: .autoDismiss( initialSelection: current.pushNotificationService, onOptionSelected: self?.updatePushNotificationService @@ -1181,7 +1181,7 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, forceRefresh(type: .databaseQuery) } - private func updatePushNotificationService(to updatedService: PushNotificationAPI.Service?) { + private func updatePushNotificationService(to updatedService: Network.PushNotification.Service?) { guard dependencies[defaults: .standard, key: .isUsingFullAPNs], updatedService != dependencies[feature: .pushNotificationService] @@ -1270,7 +1270,7 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, /// Unsubscribe from push notifications (do this after resetting the network as they are server requests so aren't dependant on a service /// layer and we don't want these to be cancelled) if let existingToken: String = dependencies[singleton: .storage].read({ db in db[.lastRecordedPushToken] }) { - PushNotificationAPI + Network.PushNotification .unsubscribeAll(token: Data(hex: existingToken), using: dependencies) .sinkUntilComplete() } @@ -2104,9 +2104,9 @@ final class PollLimitInputView: UIView, UITextFieldDelegate, SessionCell.Accesso extension ServiceNetwork: @retroactive ContentIdentifiable {} extension ServiceNetwork: @retroactive ContentEquatable {} extension ServiceNetwork: Listable {} -extension PushNotificationAPI.Service: @retroactive ContentIdentifiable {} -extension PushNotificationAPI.Service: @retroactive ContentEquatable {} -extension PushNotificationAPI.Service: Listable {} +extension Network.PushNotification.Service: @retroactive ContentIdentifiable {} +extension Network.PushNotification.Service: @retroactive ContentEquatable {} +extension Network.PushNotification.Service: Listable {} extension Log.Level: @retroactive ContentIdentifiable {} extension Log.Level: @retroactive ContentEquatable {} extension Log.Level: Listable {} diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift index d4162bb02c..ffc90bcd3d 100644 --- a/Session/Settings/NukeDataModal.swift +++ b/Session/Settings/NukeDataModal.swift @@ -201,7 +201,7 @@ final class NukeDataModal: Modal { try communityAuth.compactMap { authMethod in switch authMethod.info { case .community(let server, _, _, _, _): - return try OpenGroupAPI.preparedClearInbox( + return try Network.SOGS.preparedClearInbox( requestAndPathBuildTimeout: Network.defaultTimeout, authMethod: authMethod, using: dependencies @@ -218,7 +218,7 @@ final class NukeDataModal: Modal { .eraseToAnyPublisher() } .tryFlatMap { authMethod, clearedServers in - try SnodeAPI + try Network.SnodeAPI .preparedDeleteAllMessages( namespace: .all, requestAndPathBuildTimeout: Network.defaultTimeout, @@ -296,7 +296,7 @@ final class NukeDataModal: Modal { UIApplication.shared.unregisterForRemoteNotifications() if let deviceToken: String = maybeDeviceToken, dependencies[singleton: .storage].isValid { - PushNotificationAPI + Network.PushNotification .unsubscribeAll(token: Data(hex: deviceToken), using: dependencies) .sinkUntilComplete() } diff --git a/Session/Settings/SessionNetworkScreen/SessionNetworkScreen+ViewModel.swift b/Session/Settings/SessionNetworkScreen/SessionNetworkScreen+ViewModel.swift index d7795ea74f..dfefd70e69 100644 --- a/Session/Settings/SessionNetworkScreen/SessionNetworkScreen+ViewModel.swift +++ b/Session/Settings/SessionNetworkScreen/SessionNetworkScreen+ViewModel.swift @@ -78,8 +78,8 @@ extension SessionNetworkScreenContent { self.isRefreshing.toggle() self.lastRefreshWasSuccessful = false - SessionNetworkAPI.client.getInfo(using: dependencies) - .subscribe(on: SessionNetworkAPI.workQueue, using: dependencies) + Network.SessionNetwork.client.getInfo(using: dependencies) + .subscribe(on: Network.SessionNetwork.workQueue, using: dependencies) .receive(on: DispatchQueue.main) .sink( receiveCompletion: { _ in }, diff --git a/SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift b/SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift index 8621e3a2fa..ff92a72277 100644 --- a/SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift +++ b/SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift @@ -164,38 +164,6 @@ public extension Crypto.Generator { } } - static func plaintextWithPushNotificationPayload( - payload: Data, - encKey: Data - ) -> Crypto.Generator { - return Crypto.Generator( - id: "plaintextWithPushNotificationPayload", - args: [payload, encKey] - ) { - var cPayload: [UInt8] = Array(payload) - var cEncKey: [UInt8] = Array(encKey) - var maybePlaintext: UnsafeMutablePointer? = nil - var plaintextLen: Int = 0 - - guard - cEncKey.count == 32, - session_decrypt_push_notification( - &cPayload, - cPayload.count, - &cEncKey, - &maybePlaintext, - &plaintextLen - ), - plaintextLen > 0, - let plaintext: Data = maybePlaintext.map({ Data(bytes: $0, count: plaintextLen) }) - else { throw MessageReceiverError.decryptionFailed } - - free(UnsafeMutableRawPointer(mutating: maybePlaintext)) - - return plaintext - } - } - static func plaintextWithMultiEncrypt( ciphertext: Data, senderSessionId: SessionId, @@ -231,7 +199,7 @@ public extension Crypto.Generator { static func messageServerHash( swarmPubkey: String, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, data: Data ) -> Crypto.Generator { return Crypto.Generator( diff --git a/SessionMessagingKit/Database/Migrations/_023_SplitSnodeReceivedMessageInfo.swift b/SessionMessagingKit/Database/Migrations/_023_SplitSnodeReceivedMessageInfo.swift index e77ead364d..91746c8cef 100644 --- a/SessionMessagingKit/Database/Migrations/_023_SplitSnodeReceivedMessageInfo.swift +++ b/SessionMessagingKit/Database/Migrations/_023_SplitSnodeReceivedMessageInfo.swift @@ -91,10 +91,10 @@ enum _023_SplitSnodeReceivedMessageInfo: Migration { let targetNamespace: Int = { guard swarmPublicKeySplitComponents.count == 2 else { - return SnodeAPI.Namespace.default.rawValue + return Network.SnodeAPI.Namespace.default.rawValue } - return (Int(swarmPublicKeySplitComponents[1]) ?? SnodeAPI.Namespace.default.rawValue) + return (Int(swarmPublicKeySplitComponents[1]) ?? Network.SnodeAPI.Namespace.default.rawValue) }() let wasDeletedOrInvalid: Bool? = info["wasDeletedOrInvalid"] diff --git a/SessionMessagingKit/Database/Migrations/_024_ResetUserConfigLastHashes.swift b/SessionMessagingKit/Database/Migrations/_024_ResetUserConfigLastHashes.swift index 2fad3edb4e..60052a5eda 100644 --- a/SessionMessagingKit/Database/Migrations/_024_ResetUserConfigLastHashes.swift +++ b/SessionMessagingKit/Database/Migrations/_024_ResetUserConfigLastHashes.swift @@ -15,7 +15,7 @@ enum _024_ResetUserConfigLastHashes: Migration { static func migrate(_ db: ObservingDatabase, using dependencies: Dependencies) throws { try db.execute(literal: """ DELETE FROM snodeReceivedMessageInfo - WHERE namespace IN (\(SnodeAPI.Namespace.configContacts.rawValue), \(SnodeAPI.Namespace.configUserProfile.rawValue), \(SnodeAPI.Namespace.configUserGroups.rawValue), \(SnodeAPI.Namespace.configConvoInfoVolatile.rawValue)) + WHERE namespace IN (\(Network.SnodeAPI.Namespace.configContacts.rawValue), \(Network.SnodeAPI.Namespace.configUserProfile.rawValue), \(Network.SnodeAPI.Namespace.configUserGroups.rawValue), \(Network.SnodeAPI.Namespace.configConvoInfoVolatile.rawValue)) """) MigrationExecution.updateProgress(1) diff --git a/SessionMessagingKit/Database/Migrations/_036_GroupsRebuildChanges.swift b/SessionMessagingKit/Database/Migrations/_036_GroupsRebuildChanges.swift index 87081585e3..55cb03346f 100644 --- a/SessionMessagingKit/Database/Migrations/_036_GroupsRebuildChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_036_GroupsRebuildChanges.swift @@ -144,19 +144,24 @@ enum _036_GroupsRebuildChanges: Migration { /// If the group isn't in the invited state then make sure to subscribe for PNs once the migrations are done if !group.invited, let token: String = dependencies[defaults: .standard, key: .deviceToken] { - db.afterCommit { - dependencies[singleton: .storage] - .readPublisher { db in - try PushNotificationAPI.preparedSubscribe( - db, + let maybeAuthMethod: AuthenticationMethod? = try? Authentication.with( + db, + swarmPublicKey: group.groupSessionId, + using: dependencies + ) + + if let authMethod: AuthenticationMethod = maybeAuthMethod { + db.afterCommit { + try? Network.PushNotification + .preparedSubscribe( token: Data(hex: token), - sessionIds: [SessionId(.group, hex: group.groupSessionId)], + swarms: [(SessionId(.group, hex: group.groupSessionId), authMethod)], using: dependencies ) - } - .flatMap { $0.send(using: dependencies) } - .subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies) - .sinkUntilComplete() + .send(using: dependencies) + .subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies) + .sinkUntilComplete() + } } } } diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index 9419f7b1d9..072306a4d3 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -489,7 +489,7 @@ extension Attachment { /// **Note:** We need to continue to send this because it seems that the Desktop client _does_ in fact still use this /// id for downloading attachments. Desktop will be updated to remove it's use but in order to fix attachments for old /// versions we set this value again - let legacyId: UInt64 = (Attachment.fileId(for: self.downloadUrl).map { UInt64($0) } ?? 0) + let legacyId: UInt64 = (Network.FileServer.fileId(for: self.downloadUrl).map { UInt64($0) } ?? 0) let builder = SNProtoAttachmentPointer.builder(id: legacyId) builder.setContentType(contentType) @@ -689,14 +689,4 @@ extension Attachment { return true } - - public static func fileId(for downloadUrl: String?) -> String? { - return downloadUrl - .map { urlString -> String? in - urlString - .split(separator: "/") // stringlint:ignore - .last - .map { String($0) } - } - } } diff --git a/SessionMessagingKit/Database/Models/ClosedGroup.swift b/SessionMessagingKit/Database/Models/ClosedGroup.swift index f4a9b5cb6a..da2655eb59 100644 --- a/SessionMessagingKit/Database/Models/ClosedGroup.swift +++ b/SessionMessagingKit/Database/Models/ClosedGroup.swift @@ -226,16 +226,22 @@ public extension ClosedGroup { /// Subscribe for group push notifications if let token: String = dependencies[defaults: .standard, key: .deviceToken] { - try? PushNotificationAPI - .preparedSubscribe( - db, - token: Data(hex: token), - sessionIds: [SessionId(.group, hex: group.id)], - using: dependencies - ) - .send(using: dependencies) - .subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies) - .sinkUntilComplete() + let maybeAuthMethod: AuthenticationMethod? = try? Authentication.with( + db, + swarmPublicKey: group.id, + using: dependencies + ) + + if let authMethod: AuthenticationMethod = maybeAuthMethod { + try? Network.PushNotification + .preparedSubscribe( + token: Data(hex: token), + swarms: [(SessionId(.group, hex: group.id), authMethod)], + using: dependencies + ) + .send(using: dependencies) + .sinkUntilComplete() + } } } @@ -305,13 +311,20 @@ public extension ClosedGroup { /// Bulk unsubscripe from updated groups being removed if dataToRemove.contains(.pushNotifications) && threadVariants.contains(where: { $0.variant == .group }) { if let token: String = dependencies[defaults: .standard, key: .deviceToken] { - try? PushNotificationAPI + try? Network.PushNotification .preparedUnsubscribe( - db, token: Data(hex: token), - sessionIds: threadVariants + swarms: threadVariants .filter { $0.variant == .group } - .map { SessionId(.group, hex: $0.id) }, + .compactMap { info in + let authMethod: AuthenticationMethod? = try? Authentication.with( + db, + swarmPublicKey: info.id, + using: dependencies + ) + + return authMethod.map { (SessionId(.group, hex: info.id), $0) } + }, using: dependencies ) .send(using: dependencies) diff --git a/SessionMessagingKit/Database/Models/ConfigDump.swift b/SessionMessagingKit/Database/Models/ConfigDump.swift index 0b429793ad..eed9528aa9 100644 --- a/SessionMessagingKit/Database/Models/ConfigDump.swift +++ b/SessionMessagingKit/Database/Models/ConfigDump.swift @@ -86,7 +86,7 @@ public extension ConfigDump.Variant { .groupInfo, .groupMembers, .groupKeys ] - init(namespace: SnodeAPI.Namespace) { + init(namespace: Network.SnodeAPI.Namespace) { switch namespace { case .configUserProfile: self = .userProfile case .configContacts: self = .contacts @@ -104,19 +104,19 @@ public extension ConfigDump.Variant { /// Config messages should last for 30 days rather than the standard 14 var ttl: UInt64 { 30 * 24 * 60 * 60 * 1000 } - var namespace: SnodeAPI.Namespace { + var namespace: Network.SnodeAPI.Namespace { switch self { - case .userProfile: return SnodeAPI.Namespace.configUserProfile - case .contacts: return SnodeAPI.Namespace.configContacts - case .convoInfoVolatile: return SnodeAPI.Namespace.configConvoInfoVolatile - case .userGroups: return SnodeAPI.Namespace.configUserGroups - case .local: return SnodeAPI.Namespace.configLocal + case .userProfile: return Network.SnodeAPI.Namespace.configUserProfile + case .contacts: return Network.SnodeAPI.Namespace.configContacts + case .convoInfoVolatile: return Network.SnodeAPI.Namespace.configConvoInfoVolatile + case .userGroups: return Network.SnodeAPI.Namespace.configUserGroups + case .local: return Network.SnodeAPI.Namespace.configLocal - case .groupInfo: return SnodeAPI.Namespace.configGroupInfo - case .groupMembers: return SnodeAPI.Namespace.configGroupMembers - case .groupKeys: return SnodeAPI.Namespace.configGroupKeys + case .groupInfo: return Network.SnodeAPI.Namespace.configGroupInfo + case .groupMembers: return Network.SnodeAPI.Namespace.configGroupMembers + case .groupKeys: return Network.SnodeAPI.Namespace.configGroupKeys - case .invalid: return SnodeAPI.Namespace.unknown + case .invalid: return Network.SnodeAPI.Namespace.unknown } } diff --git a/SessionMessagingKit/Database/Models/OpenGroup.swift b/SessionMessagingKit/Database/Models/OpenGroup.swift index f6ac676fea..977887c1a1 100644 --- a/SessionMessagingKit/Database/Models/OpenGroup.swift +++ b/SessionMessagingKit/Database/Models/OpenGroup.swift @@ -4,6 +4,7 @@ import Foundation import GRDB +import SessionNetworkingKit import SessionUtilitiesKit public struct OpenGroup: Codable, Equatable, Hashable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { @@ -40,7 +41,7 @@ public struct OpenGroup: Codable, Equatable, Hashable, Identifiable, FetchableRe self.rawValue = rawValue } - public init(roomInfo: OpenGroupAPI.RoomPollInfo) { + public init(roomInfo: Network.SOGS.RoomPollInfo) { var permissions: Permissions = [] if roomInfo.read { permissions.insert(.read) } diff --git a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift index b52dbe95d5..210d629967 100644 --- a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift @@ -112,7 +112,7 @@ public enum AttachmentDownloadJob: JobExecutor { switch maybeRoomToken { case .some(let roomToken): - return try OpenGroupAPI + return try Network.SOGS .preparedDownload( url: info.downloadUrl, roomToken: roomToken, diff --git a/SessionMessagingKit/Jobs/ConfigMessageReceiveJob.swift b/SessionMessagingKit/Jobs/ConfigMessageReceiveJob.swift index f0ba83de12..53cd2a73a5 100644 --- a/SessionMessagingKit/Jobs/ConfigMessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/ConfigMessageReceiveJob.swift @@ -92,13 +92,13 @@ extension ConfigMessageReceiveJob { case data } - public let namespace: SnodeAPI.Namespace + public let namespace: Network.SnodeAPI.Namespace public let serverHash: String public let serverTimestampMs: Int64 public let data: Data public init( - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, serverHash: String, serverTimestampMs: Int64, data: Data diff --git a/SessionMessagingKit/Jobs/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/ConfigurationSyncJob.swift index 136b736815..f1266f72a1 100644 --- a/SessionMessagingKit/Jobs/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/ConfigurationSyncJob.swift @@ -96,14 +96,14 @@ public enum ConfigurationSyncJob: JobExecutor { try Authentication.with(db, swarmPublicKey: swarmPublicKey, using: dependencies) } .tryFlatMap { authMethod -> AnyPublisher<(ResponseInfoType, Network.BatchResponse), Error> in - try SnodeAPI.preparedSequence( + try Network.SnodeAPI.preparedSequence( requests: [] .appending(contentsOf: additionalTransientData?.beforeSequenceRequests) .appending( contentsOf: try pendingPushes.pushData .flatMap { pushData -> [ErasedPreparedRequest] in try pushData.data.map { data -> ErasedPreparedRequest in - try SnodeAPI + try Network.SnodeAPI .preparedSendMessage( message: SnodeMessage( recipient: swarmPublicKey, @@ -121,7 +121,7 @@ public enum ConfigurationSyncJob: JobExecutor { .appending(try { guard !pendingPushes.obsoleteHashes.isEmpty else { return nil } - return try SnodeAPI.preparedDeleteMessages( + return try Network.SnodeAPI.preparedDeleteMessages( serverHashes: Array(pendingPushes.obsoleteHashes), requireSuccessfulDeletion: false, authMethod: authMethod, diff --git a/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift b/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift index c5257be82b..dac761d872 100644 --- a/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift +++ b/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift @@ -39,7 +39,7 @@ public enum DisplayPictureDownloadJob: JobExecutor { switch details.target { case .profile(_, let url, _), .group(_, let url, _): guard - let fileId: String = Attachment.fileId(for: url), + let fileId: String = Network.FileServer.fileId(for: url), let downloadUrl: URL = URL(string: Network.FileServer.downloadUrlString(for: url, fileId: fileId)) else { throw NetworkError.invalidURL } @@ -54,7 +54,7 @@ public enum DisplayPictureDownloadJob: JobExecutor { .fetchOne(db, id: OpenGroup.idFor(roomToken: roomToken, server: server)) else { throw JobRunnerError.missingRequiredDetails } - return try OpenGroupAPI.preparedDownload( + return try Network.SOGS.preparedDownload( fileId: fileId, roomToken: roomToken, authMethod: Authentication.community(info: info), @@ -235,7 +235,7 @@ extension DisplayPictureDownloadJob { case .profile(_, let url, let encryptionKey), .group(_, let url, let encryptionKey): return ( !url.isEmpty && - Attachment.fileId(for: url) != nil && + Network.FileServer.fileId(for: url) != nil && encryptionKey.count == DisplayPictureManager.aes256KeyByteLength ) diff --git a/SessionMessagingKit/Jobs/ExpirationUpdateJob.swift b/SessionMessagingKit/Jobs/ExpirationUpdateJob.swift index 03e602be04..ef3cdb9d7a 100644 --- a/SessionMessagingKit/Jobs/ExpirationUpdateJob.swift +++ b/SessionMessagingKit/Jobs/ExpirationUpdateJob.swift @@ -26,7 +26,7 @@ public enum ExpirationUpdateJob: JobExecutor { dependencies[singleton: .storage] .readPublisher { db in - try SnodeAPI + try Network.SnodeAPI .preparedUpdateExpiry( serverHashes: details.serverHashes, updatedExpiryMs: details.expirationTimestampMs, diff --git a/SessionMessagingKit/Jobs/GarbageCollectionJob.swift b/SessionMessagingKit/Jobs/GarbageCollectionJob.swift index 907d245582..aacaa48e5c 100644 --- a/SessionMessagingKit/Jobs/GarbageCollectionJob.swift +++ b/SessionMessagingKit/Jobs/GarbageCollectionJob.swift @@ -182,7 +182,7 @@ public enum GarbageCollectionJob: JobExecutor { LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(openGroup[.threadId]) WHERE ( \(thread[.id]) IS NULL AND - \(SQL("\(openGroup[.server]) != \(OpenGroupAPI.defaultServer.lowercased())")) + \(SQL("\(openGroup[.server]) != \(Network.SOGS.defaultServer.lowercased())")) ) ) """) diff --git a/SessionMessagingKit/Jobs/GetExpirationJob.swift b/SessionMessagingKit/Jobs/GetExpirationJob.swift index 477268c35f..00be1730b7 100644 --- a/SessionMessagingKit/Jobs/GetExpirationJob.swift +++ b/SessionMessagingKit/Jobs/GetExpirationJob.swift @@ -39,7 +39,7 @@ public enum GetExpirationJob: JobExecutor { dependencies[singleton: .storage] .readPublisher { db -> Network.PreparedRequest in - try SnodeAPI.preparedGetExpiries( + try Network.SnodeAPI.preparedGetExpiries( of: expirationInfo.map { $0.key }, authMethod: try Authentication.with( db, diff --git a/SessionMessagingKit/Jobs/GroupLeavingJob.swift b/SessionMessagingKit/Jobs/GroupLeavingJob.swift index 92448ad51b..208072da67 100644 --- a/SessionMessagingKit/Jobs/GroupLeavingJob.swift +++ b/SessionMessagingKit/Jobs/GroupLeavingJob.swift @@ -92,7 +92,7 @@ public enum GroupLeavingJob: JobExecutor { .tryFlatMap { requestType -> AnyPublisher in switch requestType { case .sendLeaveMessage(let authMethod, let disappearingConfig): - return try SnodeAPI + return try Network.SnodeAPI .preparedBatch( requests: [ /// Don't expire the `GroupUpdateMemberLeftMessage` as that's not a UI-based diff --git a/SessionMessagingKit/Jobs/MessageSendJob.swift b/SessionMessagingKit/Jobs/MessageSendJob.swift index 49948e7bac..bbb2b1c7fe 100644 --- a/SessionMessagingKit/Jobs/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/MessageSendJob.swift @@ -352,7 +352,7 @@ public extension MessageSendJob { .compactMap { info in guard let attachment: Attachment = attachments[info.attachmentId], - let fileId: String = Attachment.fileId(for: info.downloadUrl) + let fileId: String = Network.FileServer.fileId(for: info.downloadUrl) else { return nil } return (attachment, fileId) diff --git a/SessionMessagingKit/Jobs/ProcessPendingGroupMemberRemovalsJob.swift b/SessionMessagingKit/Jobs/ProcessPendingGroupMemberRemovalsJob.swift index 096d6729ff..0253242bc7 100644 --- a/SessionMessagingKit/Jobs/ProcessPendingGroupMemberRemovalsJob.swift +++ b/SessionMessagingKit/Jobs/ProcessPendingGroupMemberRemovalsJob.swift @@ -97,7 +97,7 @@ public enum ProcessPendingGroupMemberRemovalsJob: JobExecutor { .tryMap { _ -> Network.PreparedRequest in /// Revoke the members authData from the group so the server rejects API calls from the ex-members (fire-and-forget /// this request, we don't want it to be blocking) - let preparedRevokeSubaccounts: Network.PreparedRequest = try SnodeAPI.preparedRevokeSubaccounts( + let preparedRevokeSubaccounts: Network.PreparedRequest = try Network.SnodeAPI.preparedRevokeSubaccounts( subaccountsToRevoke: try dependencies.mutate(cache: .libSession) { cache in try Array(pendingRemovals.keys).map { memberId in try dependencies[singleton: .crypto].tryGenerate( @@ -131,7 +131,7 @@ public enum ProcessPendingGroupMemberRemovalsJob: JobExecutor { domain: .kickedMessage ) ) - let preparedGroupDeleteMessage: Network.PreparedRequest = try SnodeAPI + let preparedGroupDeleteMessage: Network.PreparedRequest = try Network.SnodeAPI .preparedSendMessage( message: SnodeMessage( recipient: groupSessionId.hexString, @@ -179,7 +179,7 @@ public enum ProcessPendingGroupMemberRemovalsJob: JobExecutor { }() /// Combine the two requests to be sent at the same time - return try SnodeAPI.preparedSequence( + return try Network.SnodeAPI.preparedSequence( requests: [preparedRevokeSubaccounts, preparedGroupDeleteMessage, preparedMemberContentRemovalMessage] .compactMap { $0 }, requireAllBatchResponses: true, @@ -262,7 +262,7 @@ public enum ProcessPendingGroupMemberRemovalsJob: JobExecutor { ) /// Delete the messages from the swarm so users won't download them again - try? SnodeAPI + try? Network.SnodeAPI .preparedDeleteMessages( serverHashes: Array(hashes), requireSuccessfulDeletion: false, diff --git a/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift b/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift index 06d2b8e3b9..1dac5e6dd7 100644 --- a/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift +++ b/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift @@ -40,17 +40,17 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { .isEmpty else { return deferred(job) } - // The OpenGroupAPI won't make any API calls if there is no entry for an OpenGroup + // The Network.SOGS won't make any API calls if there is no entry for an OpenGroup // in the database so we need to create a dummy one to retrieve the default room data - let defaultGroupId: String = OpenGroup.idFor(roomToken: "", server: OpenGroupAPI.defaultServer) + let defaultGroupId: String = OpenGroup.idFor(roomToken: "", server: Network.SOGS.defaultServer) dependencies[singleton: .storage].write { db in guard try OpenGroup.exists(db, id: defaultGroupId) == false else { return } try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "", userCount: 0, @@ -64,13 +64,13 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { .readPublisher { [dependencies] db -> AuthenticationMethod in try Authentication.with( db, - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, activeOnly: false, /// The record for the default rooms is inactive using: dependencies ) } - .tryFlatMap { [dependencies] authMethod -> AnyPublisher<(ResponseInfoType, OpenGroupAPI.CapabilitiesAndRoomsResponse), Error> in - try OpenGroupAPI.preparedCapabilitiesAndRooms( + .tryFlatMap { [dependencies] authMethod -> AnyPublisher<(ResponseInfoType, Network.SOGS.CapabilitiesAndRoomsResponse), Error> in + try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: authMethod, using: dependencies ).send(using: dependencies) @@ -96,11 +96,11 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { OpenGroupManager.handleCapabilities( db, capabilities: response.capabilities.data, - on: OpenGroupAPI.defaultServer + on: Network.SOGS.defaultServer ) let existingImageIds: [String: String] = try OpenGroup - .filter(OpenGroup.Columns.server == OpenGroupAPI.defaultServer) + .filter(OpenGroup.Columns.server == Network.SOGS.defaultServer) .filter(OpenGroup.Columns.imageId != nil) .fetchAll(db) .reduce(into: [:]) { result, next in result[next.id] = next.imageId } @@ -112,9 +112,9 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { return ( room, try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: room.token, - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: room.name, roomDescription: room.roomDescription, @@ -131,7 +131,7 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { db, id: OpenGroup.idFor( roomToken: room.token, - server: OpenGroupAPI.defaultServer + server: Network.SOGS.defaultServer ) ) .map { (room, $0) } @@ -140,7 +140,7 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { /// Schedule the room image download (if it doesn't match out current one) result.forEach { room, openGroup in - let openGroupId: String = OpenGroup.idFor(roomToken: room.token, server: OpenGroupAPI.defaultServer) + let openGroupId: String = OpenGroup.idFor(roomToken: room.token, server: Network.SOGS.defaultServer) guard let imageId: String = room.imageId, @@ -157,7 +157,7 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { target: .community( imageId: imageId, roomToken: room.token, - server: OpenGroupAPI.defaultServer + server: Network.SOGS.defaultServer ), timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000) ) diff --git a/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupInfo.swift b/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupInfo.swift index 5abc9e32d3..82cdc5060c 100644 --- a/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupInfo.swift +++ b/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupInfo.swift @@ -293,7 +293,7 @@ internal extension LibSessionCacheType { swarmPublicKey: groupSessionId.hexString, using: dependencies )).map { authMethod in - try? SnodeAPI + try? Network.SnodeAPI .preparedDeleteMessages( serverHashes: Array(messageHashesToDelete), requireSuccessfulDeletion: false, diff --git a/SessionMessagingKit/LibSession/Types/Config.swift b/SessionMessagingKit/LibSession/Types/Config.swift index 7d503d57f0..7eb71d798f 100644 --- a/SessionMessagingKit/LibSession/Types/Config.swift +++ b/SessionMessagingKit/LibSession/Types/Config.swift @@ -324,7 +324,7 @@ public extension LibSession { .sorted() if successfulMergeTimestamps.count != messages.count { - Log.warn(.libSession, "Unable to merge \(SnodeAPI.Namespace.configGroupKeys) messages (\(successfulMergeTimestamps.count)/\(messages.count))") + Log.warn(.libSession, "Unable to merge \(Network.SnodeAPI.Namespace.configGroupKeys) messages (\(successfulMergeTimestamps.count)/\(messages.count))") } return successfulMergeTimestamps.last diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index e6e7a98934..a61a43ec3d 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -39,7 +39,7 @@ public extension Message { } } - public var defaultNamespace: SnodeAPI.Namespace? { + public var defaultNamespace: Network.SnodeAPI.Namespace? { switch self { case .contact, .syncMessage: return .`default` case .closedGroup(let groupId) where (try? SessionId.Prefix(from: groupId)) == .group: @@ -61,7 +61,7 @@ public extension Message { if prefix == .blinded15 || prefix == .blinded25 { guard let lookup: BlindedIdLookup = try? BlindedIdLookup.fetchOne(db, id: threadId) else { - throw OpenGroupAPIError.blindedLookupMissingCommunityInfo + throw SOGSError.blindedLookupMissingCommunityInfo } return .openGroupInbox( diff --git a/SessionMessagingKit/Messages/Message+Origin.swift b/SessionMessagingKit/Messages/Message+Origin.swift index e64ebd18bb..1c0d5f330a 100644 --- a/SessionMessagingKit/Messages/Message+Origin.swift +++ b/SessionMessagingKit/Messages/Message+Origin.swift @@ -8,7 +8,7 @@ public extension Message { enum Origin: Codable, Hashable { case swarm( publicKey: String, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, serverHash: String, serverTimestampMs: Int64, serverExpirationTimestamp: TimeInterval diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index 1f775614c2..2583d84d7c 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -83,7 +83,7 @@ public class Message: Codable { case (false, .some(let sigTimestampMs), .some): let delta: TimeInterval = (TimeInterval(max(sigTimestampMs, sentTimestampMs) - min(sigTimestampMs, sentTimestampMs)) / 1000) - return delta < OpenGroupAPI.validTimestampVarianceThreshold + return delta < Network.SOGS.validTimestampVarianceThreshold // FIXME: We want to remove support for this case in a future release case (_, .none, _): return true @@ -174,7 +174,7 @@ public enum ProcessedMessage { ) case config( publicKey: String, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, serverHash: String, serverTimestampMs: Int64, data: Data, @@ -190,7 +190,7 @@ public enum ProcessedMessage { } } - var namespace: SnodeAPI.Namespace { + var namespace: Network.SnodeAPI.Namespace { switch self { case .standard(_, let threadVariant, _, _, _): switch threadVariant { @@ -432,12 +432,12 @@ public extension Message { static func processRawReceivedReactions( _ db: ObservingDatabase, openGroupId: String, - message: OpenGroupAPI.Message, - associatedPendingChanges: [OpenGroupAPI.PendingChange], + message: Network.SOGS.Message, + associatedPendingChanges: [OpenGroupManager.PendingChange], using dependencies: Dependencies ) -> [Reaction] { guard - let reactions: [String: OpenGroupAPI.Message.Reaction] = message.reactions, + let reactions: [String: Network.SOGS.Message.Reaction] = message.reactions, let openGroupCapabilityInfo: LibSession.OpenGroupCapabilityInfo = try? LibSession.OpenGroupCapabilityInfo .fetchOne(db, id: openGroupId) else { return [] } @@ -483,7 +483,7 @@ public extension Message { let pendingChangeSelfReaction: Bool? = { // Find the newest 'PendingChange' entry with a matching emoji, if one exists, and // set the "self reaction" value based on it's action - let maybePendingChange: OpenGroupAPI.PendingChange? = associatedPendingChanges + let maybePendingChange: OpenGroupManager.PendingChange? = associatedPendingChanges .sorted(by: { lhs, rhs -> Bool in (lhs.seqNo ?? Int64.max) >= (rhs.seqNo ?? Int64.max) }) .first { pendingChange in if case .reaction(_, let emoji, _) = pendingChange.metadata { @@ -495,7 +495,7 @@ public extension Message { // If there is no pending change for this reaction then return nil guard - let pendingChange: OpenGroupAPI.PendingChange = maybePendingChange, + let pendingChange: OpenGroupManager.PendingChange = maybePendingChange, case .reaction(_, _, let action) = pendingChange.metadata else { return nil } diff --git a/SessionMessagingKit/Open Groups/Crypto/Crypto+OpenGroup.swift b/SessionMessagingKit/Open Groups/Crypto/Crypto+OpenGroup.swift index 9f476ce00e..2ff8f76fc6 100644 --- a/SessionMessagingKit/Open Groups/Crypto/Crypto+OpenGroup.swift +++ b/SessionMessagingKit/Open Groups/Crypto/Crypto+OpenGroup.swift @@ -3,96 +3,9 @@ // stringlint:disable import Foundation -import CryptoKit import SessionUtil import SessionUtilitiesKit -public extension Crypto.Generator { - /// Constructs a "blinded" key pair (`ka, kA`) based on an open group server `publicKey` and an ed25519 `keyPair` - static func blinded15KeyPair( - serverPublicKey: String, - ed25519SecretKey: [UInt8] - ) -> Crypto.Generator { - return Crypto.Generator( - id: "blinded15KeyPair", - args: [serverPublicKey, ed25519SecretKey] - ) { - var cEd25519SecretKey: [UInt8] = Array(ed25519SecretKey) - var cServerPublicKey: [UInt8] = Array(Data(hex: serverPublicKey)) - var cBlindedPubkey: [UInt8] = [UInt8](repeating: 0, count: 32) - var cBlindedSeckey: [UInt8] = [UInt8](repeating: 0, count: 32) - - guard - cEd25519SecretKey.count == 64, - cServerPublicKey.count == 32, - session_blind15_key_pair( - &cEd25519SecretKey, - &cServerPublicKey, - &cBlindedPubkey, - &cBlindedSeckey - ) - else { throw CryptoError.keyGenerationFailed } - - return KeyPair(publicKey: cBlindedPubkey, secretKey: cBlindedSeckey) - } - } - - /// Constructs a "blinded" key pair (`ka, kA`) based on an open group server `publicKey` and an ed25519 `keyPair` - static func blinded25KeyPair( - serverPublicKey: String, - ed25519SecretKey: [UInt8] - ) -> Crypto.Generator { - return Crypto.Generator( - id: "blinded25KeyPair", - args: [serverPublicKey, ed25519SecretKey] - ) { - var cEd25519SecretKey: [UInt8] = Array(ed25519SecretKey) - var cServerPublicKey: [UInt8] = Array(Data(hex: serverPublicKey)) - var cBlindedPubkey: [UInt8] = [UInt8](repeating: 0, count: 32) - var cBlindedSeckey: [UInt8] = [UInt8](repeating: 0, count: 32) - - guard - cEd25519SecretKey.count == 64, - cServerPublicKey.count == 32, - session_blind25_key_pair( - &cEd25519SecretKey, - &cServerPublicKey, - &cBlindedPubkey, - &cBlindedSeckey - ) - else { throw CryptoError.keyGenerationFailed } - - return KeyPair(publicKey: cBlindedPubkey, secretKey: cBlindedSeckey) - } - } -} - -public extension Crypto.Verification { - /// This method should be used to check if a users standard sessionId matches a blinded one - static func sessionId( - _ standardSessionId: String, - matchesBlindedId blindedSessionId: String, - serverPublicKey: String - ) -> Crypto.Verification { - return Crypto.Verification( - id: "sessionId", - args: [standardSessionId, blindedSessionId, serverPublicKey] - ) { - guard - var cStandardSessionId: [CChar] = standardSessionId.cString(using: .utf8), - var cBlindedSessionId: [CChar] = blindedSessionId.cString(using: .utf8), - var cServerPublicKey: [CChar] = serverPublicKey.cString(using: .utf8) - else { return false } - - return session_id_matches_blinded_id( - &cStandardSessionId, - &cBlindedSessionId, - &cServerPublicKey - ) - } - } -} - // MARK: - Messages public extension Crypto.Generator { diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift deleted file mode 100644 index efe47a4c8c..0000000000 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ /dev/null @@ -1,1396 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -// -// stringlint:disable - -import Foundation -import SessionNetworkingKit -import SessionUtilitiesKit - -public enum OpenGroupAPI { - public struct RoomInfo: Codable { - let roomToken: String - let infoUpdates: Int64 - let sequenceNumber: Int64 - } - - // MARK: - Settings - - public static let legacyDefaultServerIP = "116.203.70.33" - public static let defaultServer = "https://open.getsession.org" - public static let defaultServerPublicKey = "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238" - public static let validTimestampVarianceThreshold: TimeInterval = (6 * 60 * 60) - - public static let workQueue = DispatchQueue(label: "OpenGroupAPI.workQueue", qos: .userInitiated) // It's important that this is a serial queue - - // MARK: - Batching & Polling - - /// This is a convenience method which calls `/batch` with a pre-defined set of requests used to update an Open - /// Group, currently this will retrieve: - /// - Capabilities for the server - /// - For each room: - /// - Poll Info - /// - Messages (includes additions and deletions) - /// - Inbox for the server - /// - Outbox for the server - public static func preparedPoll( - roomInfo: [RoomInfo], - lastInboxMessageId: Int64, - lastOutboxMessageId: Int64, - hasPerformedInitialPoll: Bool, - timeSinceLastPoll: TimeInterval, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest> { - guard case .community(_, _, _, let supportsBlinding, _) = authMethod.info else { - throw NetworkError.invalidPreparedRequest - } - - let preparedRequests: [any ErasedPreparedRequest] = [ - try preparedCapabilities( - authMethod: authMethod, - using: dependencies - ) - ].appending( - // Per-room requests - contentsOf: try roomInfo - .flatMap { roomInfo -> [any ErasedPreparedRequest] in - let shouldRetrieveRecentMessages: Bool = ( - roomInfo.sequenceNumber == 0 || ( - // If it's the first poll for this launch and it's been longer than - // 'maxInactivityPeriod' then just retrieve recent messages instead - // of trying to get all messages since the last one retrieved - !hasPerformedInitialPoll && - timeSinceLastPoll > CommunityPoller.maxInactivityPeriod - ) - ) - - return [ - try preparedRoomPollInfo( - lastUpdated: roomInfo.infoUpdates, - roomToken: roomInfo.roomToken, - authMethod: authMethod, - using: dependencies - ), - (shouldRetrieveRecentMessages ? - try preparedRecentMessages( - roomToken: roomInfo.roomToken, - authMethod: authMethod, - using: dependencies - ) : - try preparedMessagesSince( - seqNo: roomInfo.sequenceNumber, - roomToken: roomInfo.roomToken, - authMethod: authMethod, - using: dependencies - ) - ) - ] - } - ) - .appending( - contentsOf: ( - // The 'inbox' and 'outbox' only work with blinded keys so don't bother polling them if not blinded - !supportsBlinding ? [] : - [ - // Inbox (only check the inbox if the user want's community message requests) - (!dependencies.mutate(cache: .libSession) { $0.get(.checkForCommunityMessageRequests) } ? nil : - (lastInboxMessageId == 0 ? - try preparedInbox(authMethod: authMethod, using: dependencies) : - try preparedInboxSince( - id: lastInboxMessageId, - authMethod: authMethod, - using: dependencies - ) - ) - ), - - // Outbox - (lastOutboxMessageId == 0 ? - try preparedOutbox(authMethod: authMethod, using: dependencies) : - try preparedOutboxSince( - id: lastOutboxMessageId, - authMethod: authMethod, - using: dependencies - ) - ), - ].compactMap { $0 } - ) - ) - - return try OpenGroupAPI - .preparedBatch( - requests: preparedRequests, - authMethod: authMethod, - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Submits multiple requests wrapped up in a single request, runs them all, then returns the result of each one - /// - /// Requests are performed independently, that is, if one fails the others will still be attempted - there is no guarantee on the order in which - /// requests will be carried out (for sequential, related requests invoke via `/sequence` instead) - /// - /// For contained subrequests that specify a body (i.e. POST or PUT requests) exactly one of `json`, `b64`, or `bytes` must be provided - /// with the request body. - public static func preparedBatch( - requests: [any ErasedPreparedRequest], - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest> { - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: Endpoint.batch, - body: Network.BatchRequest(requests: requests), - authMethod: authMethod - ), - responseType: Network.BatchResponseMap.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// This is like `/batch`, except that it guarantees to perform requests sequentially in the order provided and will stop processing requests - /// if the previous request returned a non-`2xx` response - /// - /// For example, this can be used to ban and delete all of a user's messages by sequencing the ban followed by the `delete_all`: if the - /// ban fails (e.g. because permission is denied) then the `delete_all` will not occur. The batch body and response are identical to the - /// `/batch` endpoint; requests that are not carried out because of an earlier failure will have a response code of `412` (Precondition Failed)." - /// - /// Like `/batch`, responses are returned in the same order as requests, but unlike `/batch` there may be fewer elements in the response - /// list (if requests were stopped because of a non-2xx response) - In such a case, the final, non-2xx response is still included as the final - /// response value - private static func preparedSequence( - requests: [any ErasedPreparedRequest], - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest> { - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: Endpoint.sequence, - body: Network.BatchRequest(requests: requests), - authMethod: authMethod - ), - responseType: Network.BatchResponseMap.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - // MARK: - Capabilities - - /// Return the list of server features/capabilities - /// - /// Optionally takes a `required` parameter containing a comma-separated list of capabilites; if any are not satisfied a 412 (Precondition Failed) - /// response will be returned with missing requested capabilities in the `missing` key - /// - /// Eg. `GET /capabilities` could return `{"capabilities": ["sogs", "batch"]}` `GET /capabilities?required=magic,batch` - /// could return: `{"capabilities": ["sogs", "batch"], "missing": ["magic"]}` - public static func preparedCapabilities( - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - endpoint: .capabilities, - authMethod: authMethod - ), - responseType: Capabilities.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - // MARK: - Room - - /// Returns a list of available rooms on the server - /// - /// Rooms to which the user does not have access (e.g. because they are banned, or the room has restricted access permissions) are not included - public static func preparedRooms( - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest<[Room]> { - return try Network.PreparedRequest( - request: Request( - endpoint: .rooms, - authMethod: authMethod - ), - responseType: [Room].self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Returns the details of a single room - public static func preparedRoom( - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - endpoint: .room(roomToken), - authMethod: authMethod - ), - responseType: Room.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Polls a room for metadata updates - /// - /// The endpoint polls room metadata for this room, always including the instantaneous room details (such as the user's permission and current - /// number of active users), and including the full room metadata if the room's info_updated counter has changed from the provided value - public static func preparedRoomPollInfo( - lastUpdated: Int64, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - endpoint: .roomPollInfo(roomToken, lastUpdated), - authMethod: authMethod - ), - responseType: RoomPollInfo.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - public typealias CapabilitiesAndRoomResponse = ( - capabilities: (info: ResponseInfoType, data: Capabilities), - room: (info: ResponseInfoType, data: Room) - ) - - /// This is a convenience method which constructs a `/sequence` of the `capabilities` and `room` requests, refer to those - /// methods for the documented behaviour of each method - public static func preparedCapabilitiesAndRoom( - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try OpenGroupAPI - .preparedSequence( - requests: [ - // Get the latest capabilities for the server (in case it's a new server or the - // cached ones are stale) - preparedCapabilities(authMethod: authMethod, using: dependencies), - preparedRoom(roomToken: roomToken, authMethod: authMethod, using: dependencies) - ], - authMethod: authMethod, - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - .tryMap { (info: ResponseInfoType, response: Network.BatchResponseMap) -> CapabilitiesAndRoomResponse in - let maybeCapabilities: Network.BatchSubResponse? = (response[.capabilities] as? Network.BatchSubResponse) - let maybeRoomResponse: Any? = response.data - .first(where: { key, _ in - switch key { - case .room: return true - default: return false - } - }) - .map { _, value in value } - let maybeRoom: Network.BatchSubResponse? = (maybeRoomResponse as? Network.BatchSubResponse) - - guard - let capabilitiesInfo: ResponseInfoType = maybeCapabilities, - let capabilities: Capabilities = maybeCapabilities?.body, - let roomInfo: ResponseInfoType = maybeRoom, - let room: Room = maybeRoom?.body - else { throw NetworkError.parsingFailed } - - return ( - capabilities: (info: capabilitiesInfo, data: capabilities), - room: (info: roomInfo, data: room) - ) - } - } - - public typealias CapabilitiesAndRoomsResponse = ( - capabilities: (info: ResponseInfoType, data: Capabilities), - rooms: (info: ResponseInfoType, data: [Room]) - ) - - /// This is a convenience method which constructs a `/sequence` of the `capabilities` and `rooms` requests, refer to those - /// methods for the documented behaviour of each method - public static func preparedCapabilitiesAndRooms( - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try OpenGroupAPI - .preparedSequence( - requests: [ - // Get the latest capabilities for the server (in case it's a new server or the - // cached ones are stale) - preparedCapabilities(authMethod: authMethod, using: dependencies), - preparedRooms(authMethod: authMethod, using: dependencies) - ], - authMethod: authMethod, - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - .tryMap { (info: ResponseInfoType, response: Network.BatchResponseMap) -> CapabilitiesAndRoomsResponse in - let maybeCapabilities: Network.BatchSubResponse? = (response[.capabilities] as? Network.BatchSubResponse) - let maybeRooms: Network.BatchSubResponse<[Room]>? = response.data - .first(where: { key, _ in - switch key { - case .rooms: return true - default: return false - } - }) - .map { _, value in value as? Network.BatchSubResponse<[Room]> } - - guard - let capabilitiesInfo: ResponseInfoType = maybeCapabilities, - let capabilities: Capabilities = maybeCapabilities?.body, - let roomsInfo: ResponseInfoType = maybeRooms, - let roomsResponse: Network.BatchSubResponse<[Room]> = maybeRooms, - !roomsResponse.failedToParseBody - else { throw NetworkError.parsingFailed } - - // We might want to remove all default rooms for some reason so support that case - return ( - capabilities: (info: capabilitiesInfo, data: capabilities), - rooms: (info: roomsInfo, data: (roomsResponse.body ?? [])) - ) - } - } - - // MARK: - Messages - - /// Posts a new message to a room - public static func preparedSend( - plaintext: Data, - roomToken: String, - whisperTo: String?, - whisperMods: Bool, - fileIds: [String]?, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - let signResult: (publicKey: String, signature: [UInt8]) = try sign( - messageBytes: plaintext.bytes, - authMethod: authMethod, - fallbackSigningType: .standard, - using: dependencies - ) - - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: Endpoint.roomMessage(roomToken), - body: SendMessageRequest( - data: plaintext, - signature: Data(signResult.signature), - whisperTo: whisperTo, - whisperMods: whisperMods, - fileIds: fileIds - ), - authMethod: authMethod - ), - responseType: Message.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Returns a single message by ID - public static func preparedMessage( - id: Int64, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - endpoint: .roomMessageIndividual(roomToken, id: id), - authMethod: authMethod - ), - responseType: Message.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Edits a message, replacing its existing content with new content and a new signature - /// - /// **Note:** This edit may only be initiated by the creator of the post, and the poster must currently have write permissions in the room - public static func preparedMessageUpdate( - id: Int64, - plaintext: Data, - fileIds: [Int64]?, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - let signResult: (publicKey: String, signature: [UInt8]) = try sign( - messageBytes: plaintext.bytes, - authMethod: authMethod, - fallbackSigningType: .standard, - using: dependencies - ) - - return try Network.PreparedRequest( - request: Request( - method: .put, - endpoint: Endpoint.roomMessageIndividual(roomToken, id: id), - body: UpdateMessageRequest( - data: plaintext, - signature: Data(signResult.signature), - fileIds: fileIds - ), - authMethod: authMethod - ), - responseType: NoResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Remove a message by its message id - public static func preparedMessageDelete( - id: Int64, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - method: .delete, - endpoint: .roomMessageIndividual(roomToken, id: id), - authMethod: authMethod - ), - responseType: NoResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Retrieves recent messages posted to this room - /// - /// Returns the most recent limit messages (100 if no limit is given). This only returns extant messages, and always returns the latest - /// versions: that is, deleted message indicators and pre-editing versions of messages are not returned. Messages are returned in order - /// from most recent to least recent - public static func preparedRecentMessages( - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest<[Failable]> { - return try Network.PreparedRequest( - request: Request( - endpoint: .roomMessagesRecent(roomToken), - queryParameters: [ - .updateTypes: UpdateTypes.reaction.rawValue, - .reactors: "5", - .limit: "\(dependencies[feature: .communityPollLimit])" - ], - authMethod: authMethod - ), - responseType: [Failable].self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Retrieves messages from the room preceding a given id. - /// - /// This endpoint is intended to be used with .../recent to allow a client to retrieve the most recent messages and then walk backwards - /// through batches of ever-older messages. As with .../recent, messages are returned in order from most recent to least recent. - /// - /// As with .../recent, this endpoint does not include deleted messages and always returns the current version, for edited messages. - public static func preparedMessagesBefore( - messageId: Int64, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest<[Failable]> { - return try Network.PreparedRequest( - request: Request( - endpoint: .roomMessagesBefore(roomToken, id: messageId), - queryParameters: [ - .updateTypes: UpdateTypes.reaction.rawValue, - .reactors: "5", - .limit: "\(dependencies[feature: .communityPollLimit])" - ], - authMethod: authMethod - ), - responseType: [Failable].self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Retrieves message updates from a room. This is the main message polling endpoint in SOGS. - /// - /// This endpoint retrieves new, edited, and deleted messages or message reactions posted to this room since the given message - /// sequence counter. Returns limit messages at a time (100 if no limit is given). Returned messages include any new messages, updates - /// to existing messages (i.e. edits), and message deletions made to the room since the given update id. Messages are returned in "update" - /// order, that is, in the order in which the change was applied to the room, from oldest the newest. - public static func preparedMessagesSince( - seqNo: Int64, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest<[Failable]> { - return try Network.PreparedRequest( - request: Request( - endpoint: .roomMessagesSince(roomToken, seqNo: seqNo), - queryParameters: [ - .updateTypes: UpdateTypes.reaction.rawValue, - .reactors: "5", - .limit: "\(dependencies[feature: .communityPollLimit])" - ], - authMethod: authMethod - ), - responseType: [Failable].self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Deletes all messages from a given sessionId within the provided rooms (or globally) on a server - /// - /// - Parameters: - /// - sessionId: The sessionId (either standard or blinded) of the user whose messages should be deleted - /// - /// - roomToken: The room token from which the messages should be deleted - /// - /// The invoking user **must** be a moderator of the given room or an admin if trying to delete the messages - /// of another admin. - /// - /// - server: The server to delete messages from - /// - /// - dependencies: Injected dependencies (used for unit testing) - public static func preparedMessagesDeleteAll( - sessionId: String, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - method: .delete, - endpoint: Endpoint.roomDeleteMessages(roomToken, sessionId: sessionId), - authMethod: authMethod - ), - responseType: NoResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - // MARK: - Reactions - - /// Returns the list of all reactors who have added a particular reaction to a particular message. - public static func preparedReactors( - emoji: String, - id: Int64, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - /// URL(String:) won't convert raw emojis, so need to do a little encoding here. - /// The raw emoji will come back when calling url.path - guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - throw OpenGroupAPIError.invalidEmoji - } - - return try Network.PreparedRequest( - request: Request( - method: .get, - endpoint: .reactors(roomToken, id: id, emoji: encodedEmoji), - authMethod: authMethod - ), - responseType: NoResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Adds a reaction to the given message in this room. The user must have read access in the room. - /// - /// Reactions are short strings of 1-12 unicode codepoints, typically emoji (or character sequences to produce an emoji variant, - /// such as 👨🏿‍🦰, which is composed of 4 unicode "characters" but usually renders as a single emoji "Man: Dark Skin Tone, Red Hair"). - public static func preparedReactionAdd( - emoji: String, - id: Int64, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - /// URL(String:) won't convert raw emojis, so need to do a little encoding here. - /// The raw emoji will come back when calling url.path - guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - throw OpenGroupAPIError.invalidEmoji - } - - return try Network.PreparedRequest( - request: Request( - method: .put, - endpoint: .reaction(roomToken, id: id, emoji: encodedEmoji), - authMethod: authMethod - ), - responseType: ReactionAddResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Removes a reaction from a post this room. The user must have read access in the room. This only removes the user's own reaction - /// but does not affect the reactions of other users. - public static func preparedReactionDelete( - emoji: String, - id: Int64, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - /// URL(String:) won't convert raw emojis, so need to do a little encoding here. - /// The raw emoji will come back when calling url.path - guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - throw OpenGroupAPIError.invalidEmoji - } - - return try Network.PreparedRequest( - request: Request( - method: .delete, - endpoint: .reaction(roomToken, id: id, emoji: encodedEmoji), - authMethod: authMethod - ), - responseType: ReactionRemoveResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Removes all reactions of all users from a post in this room. The calling must have moderator permissions in the room. This endpoint - /// can either remove a single reaction (e.g. remove all 🍆 reactions) by specifying it after the message id (following a /), or remove all - /// reactions from the post by not including the / suffix of the URL. - public static func preparedReactionDeleteAll( - emoji: String, - id: Int64, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - /// URL(String:) won't convert raw emojis, so need to do a little encoding here. - /// The raw emoji will come back when calling url.path - guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - throw OpenGroupAPIError.invalidEmoji - } - - return try Network.PreparedRequest( - request: Request( - method: .delete, - endpoint: .reactionDelete(roomToken, id: id, emoji: encodedEmoji), - authMethod: authMethod - ), - responseType: ReactionRemoveAllResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - // MARK: - Pinning - - /// Adds a pinned message to this room - /// - /// **Note:** Existing pinned messages are not removed: the new message is added to the pinned message list (If you want to remove existing - /// pins then build a sequence request that first calls .../unpin/all) - /// - /// The user must have admin (not just moderator) permissions in the room in order to pin messages - /// - /// Pinned messages that are already pinned will be re-pinned (that is, their pin timestamp and pinning admin user will be updated) - because pinned - /// messages are returned in pinning-order this allows admins to order multiple pinned messages in a room by re-pinning (via this endpoint) in the - /// order in which pinned messages should be displayed - public static func preparedPinMessage( - id: Int64, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: .roomPinMessage(roomToken, id: id), - authMethod: authMethod - ), - responseType: NoResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Remove a message from this room's pinned message list - /// - /// The user must have `admin` (not just `moderator`) permissions in the room - public static func preparedUnpinMessage( - id: Int64, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: .roomUnpinMessage(roomToken, id: id), - authMethod: authMethod - ), - responseType: NoResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Removes _all_ pinned messages from this room - /// - /// The user must have `admin` (not just `moderator`) permissions in the room - public static func preparedUnpinAll( - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: .roomUnpinAll(roomToken), - authMethod: authMethod - ), - responseType: NoResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - // MARK: - Files - - public static func preparedUpload( - data: Data, - roomToken: String, - fileName: String? = nil, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - guard case .community(let server, let publicKey, _, _, _) = authMethod.info else { - throw NetworkError.invalidPreparedRequest - } - - return try Network.PreparedRequest( - request: Request( - endpoint: Endpoint.roomFile(roomToken), - destination: .serverUpload( - server: server, - x25519PublicKey: publicKey, - fileName: fileName - ), - body: data - ), - responseType: FileUploadResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - requestTimeout: Network.fileUploadTimeout, - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - public static func downloadUrlString( - for fileId: String, - server: String, - roomToken: String - ) -> String { - return "\(server)/\(Endpoint.roomFileIndividual(roomToken, fileId).path)" - } - - public static func preparedDownload( - url: URL, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - guard let fileId: String = Attachment.fileId(for: url.absoluteString) else { throw NetworkError.invalidURL } - - return try preparedDownload(fileId: fileId, roomToken: roomToken, authMethod: authMethod, using: dependencies) - } - - public static func preparedDownload( - fileId: String, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - endpoint: .roomFileIndividual(roomToken, fileId), - authMethod: authMethod - ), - responseType: Data.self, - additionalSignatureData: AdditionalSigningData(authMethod), - requestTimeout: Network.fileDownloadTimeout, - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - // MARK: - Inbox/Outbox (Message Requests) - - /// Retrieves all of the user's current DMs (up to limit) - /// - /// **Note:** `inbox` will return a `304` with an empty response if no messages (hence the optional return type) - public static func preparedInbox( - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest<[DirectMessage]?> { - return try Network.PreparedRequest( - request: Request( - endpoint: .inbox, - authMethod: authMethod - ), - responseType: [DirectMessage]?.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Polls for any DMs received since the given id, this method will return a `304` with an empty response if there are no messages - /// - /// **Note:** `inboxSince` will return a `304` with an empty response if no messages (hence the optional return type) - public static func preparedInboxSince( - id: Int64, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest<[DirectMessage]?> { - return try Network.PreparedRequest( - request: Request( - endpoint: .inboxSince(id: id), - authMethod: authMethod - ), - responseType: [DirectMessage]?.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Remove all message requests from inbox, this methrod will return the number of messages deleted - public static func preparedClearInbox( - requestTimeout: TimeInterval = Network.defaultTimeout, - requestAndPathBuildTimeout: TimeInterval? = nil, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - method: .delete, - endpoint: .inbox, - authMethod: authMethod - ), - responseType: DeleteInboxResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - requestTimeout: requestTimeout, - requestAndPathBuildTimeout: requestAndPathBuildTimeout, - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Delivers a direct message to a user via their blinded Session ID - /// - /// The body of this request is a JSON object containing a message key with a value of the encrypted-then-base64-encoded message to deliver - public static func preparedSend( - ciphertext: Data, - toInboxFor blindedSessionId: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: Endpoint.inboxFor(sessionId: blindedSessionId), - body: SendDirectMessageRequest( - message: ciphertext - ), - authMethod: authMethod - ), - responseType: SendDirectMessageResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Retrieves all of the user's sent DMs (up to limit) - /// - /// **Note:** `outbox` will return a `304` with an empty response if no messages (hence the optional return type) - public static func preparedOutbox( - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest<[DirectMessage]?> { - return try Network.PreparedRequest( - request: Request( - endpoint: .outbox, - authMethod: authMethod - ), - responseType: [DirectMessage]?.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Polls for any DMs sent since the given id, this method will return a `304` with an empty response if there are no messages - /// - /// **Note:** `outboxSince` will return a `304` with an empty response if no messages (hence the optional return type) - public static func preparedOutboxSince( - id: Int64, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest<[DirectMessage]?> { - return try Network.PreparedRequest( - request: Request( - endpoint: .outboxSince(id: id), - authMethod: authMethod - ), - responseType: [DirectMessage]?.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - // MARK: - Users - - /// Applies a ban of a user from specific rooms, or from the server globally - /// - /// The invoking user must have `moderator` (or `admin`) permission in all given rooms when specifying rooms, and must be a - /// `globalModerator` (or `globalAdmin`) if using the global parameter - /// - /// **Note:** The user's messages are not deleted by this request - In order to ban and delete all messages use the `/sequence` endpoint to - /// bundle a `/user/.../ban` with a `/user/.../deleteMessages` request - /// - /// - Parameters: - /// - sessionId: The sessionId (either standard or blinded) of the user whose messages should be deleted - /// - /// - timeout: Value specifying a time limit on the ban, in seconds - /// - /// The applied ban will expire and be removed after the given interval - If omitted (or `null`) then the ban is permanent - /// - /// If this endpoint is called multiple times then the timeout of the last call takes effect (eg. a permanent ban can be replaced - /// with a time-limited ban by calling the endpoint again with a timeout value, and vice versa) - /// - /// - roomTokens: List of one or more room tokens from which the user should be banned from - /// - /// The invoking user **must** be a moderator of all of the given rooms. - /// - /// This may be set to the single-element list `["*"]` to ban the user from all rooms in which the current user has moderator - /// permissions (the call will succeed if the calling user is a moderator in at least one channel) - /// - /// **Note:** You can ban from all rooms on a server by providing a `nil` value for this parameter (the invoking user must be a - /// global moderator in order to add a global ban) - /// - /// - server: The server to delete messages from - /// - /// - dependencies: Injected dependencies (used for unit testing) - public static func preparedUserBan( - sessionId: String, - for timeout: TimeInterval? = nil, - from roomTokens: [String]? = nil, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: Endpoint.userBan(sessionId), - body: UserBanRequest( - rooms: roomTokens, - global: (roomTokens == nil ? true : nil), - timeout: timeout - ), - authMethod: authMethod - ), - responseType: NoResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Removes a user ban from specific rooms, or from the server globally - /// - /// The invoking user must have `moderator` (or `admin`) permission in all given rooms when specifying rooms, and must be a global server `moderator` - /// (or `admin`) if using the `global` parameter - /// - /// **Note:** Room and global bans are independent: if a user is banned globally and has a room-specific ban then removing the global ban does not remove - /// the room specific ban, and removing the room-specific ban does not remove the global ban (to fully unban a user globally and from all rooms, submit a - /// `/sequence` request with a global unban followed by a "rooms": ["*"] unban) - /// - /// - Parameters: - /// - sessionId: The sessionId (either standard or blinded) of the user whose messages should be deleted - /// - /// - roomTokens: List of one or more room tokens from which the user should be unbanned from - /// - /// The invoking user **must** be a moderator of all of the given rooms. - /// - /// This may be set to the single-element list `["*"]` to unban the user from all rooms in which the current user has moderator - /// permissions (the call will succeed if the calling user is a moderator in at least one channel) - /// - /// **Note:** You can ban from all rooms on a server by providing a `nil` value for this parameter - /// - /// - server: The server to delete messages from - /// - /// - dependencies: Injected dependencies (used for unit testing) - public static func preparedUserUnban( - sessionId: String, - from roomTokens: [String]?, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: Endpoint.userUnban(sessionId), - body: UserUnbanRequest( - rooms: roomTokens, - global: (roomTokens == nil ? true : nil) - ), - authMethod: authMethod - ), - responseType: NoResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// Appoints or removes a moderator or admin - /// - /// This endpoint is used to appoint or remove moderator/admin permissions either for specific rooms or for server-wide global moderator permissions - /// - /// Admins/moderators of rooms can only be appointed or removed by a user who has admin permissions in the room (including global admins) - /// - /// Global admins/moderators may only be appointed by a global admin - /// - /// The admin/moderator paramters interact as follows: - /// - **admin=true, moderator omitted:** This adds admin permissions, which automatically also implies moderator permissions - /// - **admin=true, moderator=true:** Exactly the same as above - /// - **admin=false, moderator=true:** Removes any existing admin permissions from the rooms (or globally), if present, and adds - /// moderator permissions to the rooms/globally (if not already present) - /// - **admin=false, moderator omitted:** This removes admin permissions but leaves moderator permissions, if present (this - /// effectively "downgrades" an admin to a moderator). Unlike the above this does **not** add moderator permissions to matching rooms - /// if not already present - /// - **moderator=true, admin omitted:** Adds moderator permissions to the given rooms (or globally), if not already present. If - /// the user already has admin permissions this does nothing (that is, admin permission is *not* removed, unlike the above) - /// - **moderator=false, admin omitted:** This removes moderator **and** admin permissions from all given rooms (or globally) - /// - **moderator=false, admin=false:** Exactly the same as above - /// - **moderator=false, admin=true:** This combination is **not permitted** (because admin permissions imply moderator - /// permissions) and will result in Bad Request error if given - /// - /// - Parameters: - /// - sessionId: The sessionId (either standard or blinded) of the user to modify the permissions of - /// - /// - moderator: Value indicating that this user should have moderator permissions added (true), removed (false), or left alone (null) - /// - /// - admin: Value indicating that this user should have admin permissions added (true), removed (false), or left alone (null) - /// - /// Granting admin permission automatically includes granting moderator permission (and thus it is an error to use admin=true with - /// moderator=false) - /// - /// - visible: Value indicating whether the moderator/admin should be made publicly visible as a moderator/admin of the room(s) - /// (if true) or hidden (false) - /// - /// Hidden moderators/admins still have all the same permissions as visible moderators/admins, but are visible only to other - /// moderators/admins; regular users in the room will not know their moderator status - /// - /// - roomTokens: List of one or more room tokens to which the permission changes should be applied - /// - /// The invoking user **must** be an admin of all of the given rooms. - /// - /// This may be set to the single-element list `["*"]` to add or remove the moderator from all rooms in which the current user has admin - /// permissions (the call will succeed if the calling user is an admin in at least one channel) - /// - /// **Note:** You can specify a change to global permisisons by providing a `nil` value for this parameter - /// - /// - server: The server to perform the permission changes on - /// - /// - dependencies: Injected dependencies (used for unit testing) - public static func preparedUserModeratorUpdate( - sessionId: String, - moderator: Bool? = nil, - admin: Bool? = nil, - visible: Bool, - for roomTokens: [String]?, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - guard (moderator != nil && admin == nil) || (moderator == nil && admin != nil) else { - throw NetworkError.invalidPreparedRequest - } - - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: Endpoint.userModerator(sessionId), - body: UserModeratorRequest( - rooms: roomTokens, - global: (roomTokens == nil ? true : nil), - moderator: moderator, - admin: admin, - visible: visible - ), - authMethod: authMethod - ), - responseType: NoResponse.self, - additionalSignatureData: AdditionalSigningData(authMethod), - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - /// This is a convenience method which constructs a `/sequence` of the `userBan` and `userDeleteMessages` requests, refer to those - /// methods for the documented behaviour of each method - public static func preparedUserBanAndDeleteAllMessages( - sessionId: String, - roomToken: String, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest> { - return try OpenGroupAPI - .preparedSequence( - requests: [ - preparedUserBan( - sessionId: sessionId, - from: [roomToken], - authMethod: authMethod, - using: dependencies - ), - preparedMessagesDeleteAll( - sessionId: sessionId, - roomToken: roomToken, - authMethod: authMethod, - using: dependencies - ) - ], - authMethod: authMethod, - using: dependencies - ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) - } - - // MARK: - Authentication - - fileprivate static func signatureHeaders( - url: URL, - method: HTTPMethod, - body: Data?, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> [HTTPHeader: String] { - let path: String = url.path - .appending(url.query.map { value in "?\(value)" }) - let method: String = method.rawValue - let timestamp: Int = Int(floor(dependencies.dateNow.timeIntervalSince1970)) - - guard - case .community(_, let publicKey, _, _, _) = authMethod.info, - !publicKey.isEmpty, - let nonce: [UInt8] = dependencies[singleton: .crypto].generate(.randomBytes(16)), - let timestampBytes: [UInt8] = "\(timestamp)".data(using: .ascii).map({ Array($0) }) - else { throw OpenGroupAPIError.signingFailed } - - /// Get a hash of any body content - let bodyHash: [UInt8]? = { - guard let body: Data = body else { return nil } - - return dependencies[singleton: .crypto].generate(.hash(message: body.bytes, length: 64)) - }() - - /// Generate the signature message - /// "ServerPubkey || Nonce || Timestamp || Method || Path || Blake2b Hash(Body) - /// `ServerPubkey` - /// `Nonce` - /// `Timestamp` is the bytes of an ascii decimal string - /// `Method` - /// `Path` - /// `Body` is a Blake2b hash of the data (if there is a body) - let messageBytes: [UInt8] = Data(hex: publicKey).bytes - .appending(contentsOf: nonce) - .appending(contentsOf: timestampBytes) - .appending(contentsOf: method.bytes) - .appending(contentsOf: path.bytes) - .appending(contentsOf: bodyHash ?? []) - - /// Sign the above message - let signResult: (publicKey: String, signature: [UInt8]) = try sign( - messageBytes: messageBytes, - authMethod: authMethod, - fallbackSigningType: .unblinded, - using: dependencies - ) - - return [ - HTTPHeader.sogsPubKey: signResult.publicKey, - HTTPHeader.sogsTimestamp: "\(timestamp)", - HTTPHeader.sogsNonce: Data(nonce).base64EncodedString(), - HTTPHeader.sogsSignature: signResult.signature.toBase64() - ] - } - - /// Sign a message to be sent to SOGS (handles both un-blinded and blinded signing based on the server capabilities) - private static func sign( - messageBytes: [UInt8], - authMethod: AuthenticationMethod, - fallbackSigningType signingType: SessionId.Prefix, - using dependencies: Dependencies - ) throws -> (publicKey: String, signature: [UInt8]) { - guard - !dependencies[cache: .general].ed25519SecretKey.isEmpty, - !dependencies[cache: .general].ed25519Seed.isEmpty, - case .community(_, let publicKey, let hasCapabilities, let supportsBlinding, let forceBlinded) = authMethod.info - else { throw OpenGroupAPIError.signingFailed } - - // If we have no capabilities or if the server supports blinded keys then sign using the blinded key - if forceBlinded || !hasCapabilities || supportsBlinding { - guard - let blinded15KeyPair: KeyPair = dependencies[singleton: .crypto].generate( - .blinded15KeyPair( - serverPublicKey: publicKey, - ed25519SecretKey: dependencies[cache: .general].ed25519SecretKey - ) - ), - let signatureResult: [UInt8] = dependencies[singleton: .crypto].generate( - .signatureBlind15( - message: messageBytes, - serverPublicKey: publicKey, - ed25519SecretKey: dependencies[cache: .general].ed25519SecretKey - ) - ) - else { throw OpenGroupAPIError.signingFailed } - - return ( - publicKey: SessionId(.blinded15, publicKey: blinded15KeyPair.publicKey).hexString, - signature: signatureResult - ) - } - - // Otherwise sign using the fallback type - switch signingType { - case .unblinded: - guard - let signature: Authentication.Signature = dependencies[singleton: .crypto].generate( - .signature( - message: messageBytes, - ed25519SecretKey: dependencies[cache: .general].ed25519SecretKey - ) - ), - let ed25519KeyPair: KeyPair = dependencies[singleton: .crypto].generate( - .ed25519KeyPair(seed: dependencies[cache: .general].ed25519Seed) - ), - case .standard(let signatureResult) = signature - else { throw OpenGroupAPIError.signingFailed } - - return ( - publicKey: SessionId(.unblinded, publicKey: ed25519KeyPair.publicKey).hexString, - signature: signatureResult - ) - - // Default to using the 'standard' key - default: - guard - let ed25519KeyPair: KeyPair = dependencies[singleton: .crypto].generate( - .ed25519KeyPair(seed: dependencies[cache: .general].ed25519Seed) - ), - let x25519PublicKey: [UInt8] = dependencies[singleton: .crypto].generate( - .x25519(ed25519Pubkey: ed25519KeyPair.publicKey) - ), - let x25519SecretKey: [UInt8] = dependencies[singleton: .crypto].generate( - .x25519(ed25519Seckey: ed25519KeyPair.secretKey) - ), - let signatureResult: [UInt8] = dependencies[singleton: .crypto].generate( - .signatureXed25519(data: messageBytes, curve25519PrivateKey: x25519SecretKey) - ) - else { throw OpenGroupAPIError.signingFailed } - - return ( - publicKey: SessionId(.standard, publicKey: x25519PublicKey).hexString, - signature: signatureResult - ) - } - } - - /// Sign a request to be sent to SOGS (handles both un-blinded and blinded signing based on the server capabilities) - private static func signRequest( - preparedRequest: Network.PreparedRequest, - using dependencies: Dependencies - ) throws -> Network.Destination { - guard let signingData: AdditionalSigningData = preparedRequest.additionalSignatureData as? AdditionalSigningData else { - throw OpenGroupAPIError.signingFailed - } - - return try preparedRequest.destination - .signed(data: signingData, body: preparedRequest.body, using: dependencies) - } -} - -private extension OpenGroupAPI { - struct AdditionalSigningData { - let authMethod: AuthenticationMethod - - init(_ authMethod: AuthenticationMethod) { - self.authMethod = authMethod - } - } -} - -private extension Network.Destination { - func signed(data: OpenGroupAPI.AdditionalSigningData, body: Data?, using dependencies: Dependencies) throws -> Network.Destination { - switch self { - case .snode, .randomSnode, .randomSnodeLatestNetworkTimeTarget: throw NetworkError.unauthorised - case .cached: return self - case .server(let info): return .server(info: try info.signed(data, body, using: dependencies)) - case .serverUpload(let info, let fileName): - return .serverUpload(info: try info.signed(data, body, using: dependencies), fileName: fileName) - - case .serverDownload(let info): - return .serverDownload(info: try info.signed(data, body, using: dependencies)) - } - } -} - -private extension Network.Destination.ServerInfo { - func signed(_ data: OpenGroupAPI.AdditionalSigningData, _ body: Data?, using dependencies: Dependencies) throws -> Network.Destination.ServerInfo { - return updated(with: try OpenGroupAPI.signatureHeaders( - url: url, - method: method, - body: body, - authMethod: data.authMethod, - using: dependencies - )) - } -} diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 6b22a8c476..e0b7269ffd 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -35,7 +35,7 @@ public extension Log.Category { // MARK: - OpenGroupManager public final class OpenGroupManager { - public typealias DefaultRoomInfo = (room: OpenGroupAPI.Room, openGroup: OpenGroup) + public typealias DefaultRoomInfo = (room: Network.SOGS.Room, openGroup: OpenGroup) private let dependencies: Dependencies @@ -79,8 +79,8 @@ public final class OpenGroupManager { .replacingOccurrences(of: serverPort, with: "") ) let options: Set = Set([ - OpenGroupAPI.legacyDefaultServerIP, - OpenGroupAPI.defaultServer + Network.SOGS.legacyDefaultServerIP, + Network.SOGS.defaultServer .replacingOccurrences(of: "http://", with: "") .replacingOccurrences(of: "https://", with: "") ]) @@ -103,7 +103,7 @@ public final class OpenGroupManager { .lowercased() .replacingOccurrences(of: serverPort, with: "") ) - let defaultServerHost: String = OpenGroupAPI.defaultServer + let defaultServerHost: String = Network.SOGS.defaultServer .replacingOccurrences(of: "http://", with: "") .replacingOccurrences(of: "https://", with: "") var serverOptions: Set = Set([ @@ -119,9 +119,9 @@ public final class OpenGroupManager { serverOptions.insert(defaultServerHost) serverOptions.insert("http://\(defaultServerHost)") serverOptions.insert("https://\(defaultServerHost)") - serverOptions.insert(OpenGroupAPI.legacyDefaultServerIP) - serverOptions.insert("http://\(OpenGroupAPI.legacyDefaultServerIP)") - serverOptions.insert("https://\(OpenGroupAPI.legacyDefaultServerIP)") + serverOptions.insert(Network.SOGS.legacyDefaultServerIP) + serverOptions.insert("http://\(Network.SOGS.legacyDefaultServerIP)") + serverOptions.insert("https://\(Network.SOGS.legacyDefaultServerIP)") } // First check if there is no poller for the specified server @@ -161,7 +161,7 @@ public final class OpenGroupManager { return server.lowercased() } - return OpenGroupAPI.defaultServer + return Network.SOGS.defaultServer }() let threadId: String = OpenGroup.idFor(roomToken: roomToken, server: targetServer) @@ -223,11 +223,11 @@ public final class OpenGroupManager { return server.lowercased() } - return OpenGroupAPI.defaultServer + return Network.SOGS.defaultServer }() return Result { - try OpenGroupAPI + try Network.SOGS .preparedCapabilitiesAndRoom( roomToken: roomToken, authMethod: Authentication.community( @@ -243,7 +243,7 @@ public final class OpenGroupManager { } .publisher .flatMap { [dependencies] in $0.send(using: dependencies) } - .flatMapStorageWritePublisher(using: dependencies) { [dependencies] (db: ObservingDatabase, response: (info: ResponseInfoType, value: OpenGroupAPI.CapabilitiesAndRoomResponse)) -> Void in + .flatMapStorageWritePublisher(using: dependencies) { [dependencies] (db: ObservingDatabase, response: (info: ResponseInfoType, value: Network.SOGS.CapabilitiesAndRoomResponse)) -> Void in // Add the new open group to libSession try LibSession.add( db, @@ -263,7 +263,7 @@ public final class OpenGroupManager { // Then the room try OpenGroupManager.handlePollInfo( db, - pollInfo: OpenGroupAPI.RoomPollInfo(room: response.value.room.data), + pollInfo: Network.SOGS.RoomPollInfo(room: response.value.room.data), publicKey: publicKey, for: roomToken, on: targetServer, @@ -334,7 +334,7 @@ public final class OpenGroupManager { try MessageDeduplication.deleteIfNeeded(db, threadIds: [openGroupId], using: dependencies) // Remove the open group (no foreign key to the thread so it won't auto-delete) - if server?.lowercased() != OpenGroupAPI.defaultServer.lowercased() { + if server?.lowercased() != Network.SOGS.defaultServer.lowercased() { _ = try? OpenGroup .filter(id: openGroupId) .deleteAll(db) @@ -359,7 +359,7 @@ public final class OpenGroupManager { internal static func handleCapabilities( _ db: ObservingDatabase, - capabilities: OpenGroupAPI.Capabilities, + capabilities: Network.SOGS.CapabilitiesResponse, on server: String ) { // Remove old capabilities first @@ -371,7 +371,7 @@ public final class OpenGroupManager { capabilities.capabilities.forEach { capability in try? Capability( openGroupServer: server.lowercased(), - variant: capability, + variant: Capability.Variant(from: capability), isMissing: false ) .upsert(db) @@ -379,7 +379,7 @@ public final class OpenGroupManager { capabilities.missing?.forEach { capability in try? Capability( openGroupServer: server.lowercased(), - variant: capability, + variant: Capability.Variant(from: capability), isMissing: true ) .upsert(db) @@ -388,7 +388,7 @@ public final class OpenGroupManager { internal static func handlePollInfo( _ db: ObservingDatabase, - pollInfo: OpenGroupAPI.RoomPollInfo, + pollInfo: Network.SOGS.RoomPollInfo, publicKey maybePublicKey: String?, for roomToken: String, on server: String, @@ -431,7 +431,7 @@ public final class OpenGroupManager { .updateAllAndConfig(db, changes, using: dependencies) // Update the admin/moderator group members - if let roomDetails: OpenGroupAPI.Room = pollInfo.details { + if let roomDetails: Network.SOGS.Room = pollInfo.details { _ = try? GroupMember .filter(GroupMember.Columns.groupId == threadId) .deleteAll(db) @@ -531,7 +531,7 @@ public final class OpenGroupManager { internal static func handleMessages( _ db: ObservingDatabase, - messages: [OpenGroupAPI.Message], + messages: [Network.SOGS.Message], for roomToken: String, on server: String, using dependencies: Dependencies @@ -543,7 +543,7 @@ public final class OpenGroupManager { // Sorting the messages by server ID before importing them fixes an issue where messages // that quote older messages can't find those older messages - let sortedMessages: [OpenGroupAPI.Message] = messages + let sortedMessages: [Network.SOGS.Message] = messages .filter { $0.deleted != true } .sorted { lhs, rhs in lhs.id < rhs.id } var messageServerInfoToRemove: [(id: Int64, seqNo: Int64)] = messages @@ -684,7 +684,7 @@ public final class OpenGroupManager { internal static func handleDirectMessages( _ db: ObservingDatabase, - messages: [OpenGroupAPI.DirectMessage], + messages: [Network.SOGS.DirectMessage], fromOutbox: Bool, on server: String, using dependencies: Dependencies @@ -698,7 +698,7 @@ public final class OpenGroupManager { // Sorting the messages by server ID before importing them fixes an issue where messages // that quote older messages can't find those older messages - let sortedMessages: [OpenGroupAPI.DirectMessage] = messages + let sortedMessages: [Network.SOGS.DirectMessage] = messages .sorted { lhs, rhs in lhs.id < rhs.id } let latestMessageId: Int64 = sortedMessages[sortedMessages.count - 1].id var lookupCache: [String: BlindedIdLookup] = [:] // Only want this cache to exist for the current loop @@ -829,9 +829,9 @@ public final class OpenGroupManager { id: Int64, in roomToken: String, on server: String, - type: OpenGroupAPI.PendingChange.ReactAction - ) -> OpenGroupAPI.PendingChange { - let pendingChange = OpenGroupAPI.PendingChange( + type: OpenGroupManager.PendingChange.ReactAction + ) -> OpenGroupManager.PendingChange { + let pendingChange = OpenGroupManager.PendingChange( server: server, room: roomToken, changeType: .reaction, @@ -849,7 +849,7 @@ public final class OpenGroupManager { return pendingChange } - public func updatePendingChange(_ pendingChange: OpenGroupAPI.PendingChange, seqNo: Int64?) { + public func updatePendingChange(_ pendingChange: OpenGroupManager.PendingChange, seqNo: Int64?) { dependencies.mutate(cache: .openGroupManager) { if let index = $0.pendingChanges.firstIndex(of: pendingChange) { $0.pendingChanges[index].seqNo = seqNo @@ -857,7 +857,7 @@ public final class OpenGroupManager { } } - public func removePendingChange(_ pendingChange: OpenGroupAPI.PendingChange) { + public func removePendingChange(_ pendingChange: OpenGroupManager.PendingChange) { dependencies.mutate(cache: .openGroupManager) { if let index = $0.pendingChanges.firstIndex(of: pendingChange) { $0.pendingChanges.remove(at: index) @@ -989,7 +989,7 @@ public extension OpenGroupManager { private let dependencies: Dependencies private let defaultRoomsSubject: CurrentValueSubject<[DefaultRoomInfo], Error> = CurrentValueSubject([]) private var _lastSuccessfulCommunityPollTimestamp: TimeInterval? - public var pendingChanges: [OpenGroupAPI.PendingChange] = [] + public var pendingChanges: [OpenGroupManager.PendingChange] = [] public var defaultRoomsPublisher: AnyPublisher<[DefaultRoomInfo], Error> { defaultRoomsSubject @@ -1044,13 +1044,13 @@ public extension OpenGroupManager { public protocol OGMImmutableCacheType: ImmutableCacheType { var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error> { get } - var pendingChanges: [OpenGroupAPI.PendingChange] { get } + var pendingChanges: [OpenGroupManager.PendingChange] { get } } public protocol OGMCacheType: OGMImmutableCacheType, MutableCacheType { var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error> { get } - var pendingChanges: [OpenGroupAPI.PendingChange] { get set } + var pendingChanges: [OpenGroupManager.PendingChange] { get set } func getLastSuccessfulCommunityPollTimestamp() -> TimeInterval func setLastSuccessfulCommunityPollTimestamp(_ timestamp: TimeInterval) diff --git a/SessionMessagingKit/Open Groups/Types/Capabilities.swift b/SessionMessagingKit/Open Groups/Types/Capabilities.swift deleted file mode 100644 index c4cb7c5c77..0000000000 --- a/SessionMessagingKit/Open Groups/Types/Capabilities.swift +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -extension Network.SOGS { - public struct Capabilities: Codable, Equatable { - public let capabilities: [Network.SOGS.Capability.Variant] - public let missing: [Network.SOGS.Capability.Variant]? - - // MARK: - Initialization - - public init(capabilities: [Network.SOGS.Capability.Variant], missing: [Network.SOGS.Capability.Variant]? = nil) { - self.capabilities = capabilities - self.missing = missing - } - } -} diff --git a/SessionMessagingKit/Open Groups/Types/PendingChange.swift b/SessionMessagingKit/Open Groups/Types/PendingChange.swift index dd5af98b5f..6ab6cd2145 100644 --- a/SessionMessagingKit/Open Groups/Types/PendingChange.swift +++ b/SessionMessagingKit/Open Groups/Types/PendingChange.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension OpenGroupManager { public struct PendingChange: Equatable { public enum ChangeType { case reaction @@ -24,23 +24,23 @@ extension OpenGroupAPI { var seqNo: Int64? let metadata: Metadata - public static func == (lhs: OpenGroupAPI.PendingChange, rhs: OpenGroupAPI.PendingChange) -> Bool { - guard lhs.server == rhs.server && - lhs.room == rhs.room && - lhs.changeType == rhs.changeType && - lhs.seqNo == rhs.seqNo - else { - return false - } + public static func == (lhs: OpenGroupManager.PendingChange, rhs: OpenGroupManager.PendingChange) -> Bool { + guard + lhs.server == rhs.server && + lhs.room == rhs.room && + lhs.changeType == rhs.changeType && + lhs.seqNo == rhs.seqNo + else { return false } switch lhs.changeType { case .reaction: if case .reaction(let lhsMessageId, let lhsEmoji, let lhsAction) = lhs.metadata, - case .reaction(let rhsMessageId, let rhsEmoji, let rhsAction) = rhs.metadata { + case .reaction(let rhsMessageId, let rhsEmoji, let rhsAction) = rhs.metadata + { return lhsMessageId == rhsMessageId && lhsEmoji == rhsEmoji && lhsAction == rhsAction - } else { - return false } + + return false } } } diff --git a/SessionMessagingKit/Sending & Receiving/AttachmentUploader.swift b/SessionMessagingKit/Sending & Receiving/AttachmentUploader.swift index dec4a6495e..c543b0dd4b 100644 --- a/SessionMessagingKit/Sending & Receiving/AttachmentUploader.swift +++ b/SessionMessagingKit/Sending & Receiving/AttachmentUploader.swift @@ -11,7 +11,7 @@ import SessionUtilitiesKit public final class AttachmentUploader { private enum Destination { case fileServer - case community(LibSession.OpenGroupCapabilityInfo) + case community(roomToken: String, server: String) var shouldEncrypt: Bool { switch self { @@ -78,7 +78,9 @@ public final class AttachmentUploader { // Generate the correct upload info based on the state of the attachment let destination: AttachmentUploader.Destination = { switch authMethod { - case let auth as Authentication.community: return .community(auth.openGroupCapabilityInfo) + case let auth as Authentication.community: + return .community(roomToken: auth.roomToken, server: auth.server) + default: return .fileServer } }() @@ -86,14 +88,14 @@ public final class AttachmentUploader { let endpoint: (any EndpointType) = { switch destination { case .fileServer: return Network.FileServer.Endpoint.file - case .community(let info): return OpenGroupAPI.Endpoint.roomFile(info.roomToken) + case .community(let roomToken, _): return Network.SOGS.Endpoint.roomFile(roomToken) } }() // This can occur if an AttachmentUploadJob was explicitly created for a message // dependant on the attachment being uploaded (in this case the attachment has // already been uploaded so just succeed) - if attachment.state == .uploaded, let fileId: String = Attachment.fileId(for: attachment.downloadUrl) { + if attachment.state == .uploaded, let fileId: String = Network.FileServer.fileId(for: attachment.downloadUrl) { return ( attachment, try Network.PreparedRequest.cached( @@ -114,7 +116,7 @@ public final class AttachmentUploader { // Note: The most common cases for this will be for LinkPreviews or Quotes if attachment.state == .downloaded, - let fileId: String = Attachment.fileId(for: attachment.downloadUrl), + let fileId: String = Network.FileServer.fileId(for: attachment.downloadUrl), ( !destination.shouldEncrypt || ( attachment.encryptionKey != nil && @@ -174,13 +176,13 @@ public final class AttachmentUploader { digest ) - case .community(let info): + case .community(let roomToken, _): return ( attachment, - try OpenGroupAPI.preparedUpload( + try Network.SOGS.preparedUpload( data: finalData, - roomToken: info.roomToken, - authMethod: Authentication.community(info: info), + roomToken: roomToken, + authMethod: authMethod, using: dependencies ), encryptionKey, @@ -211,11 +213,11 @@ public final class AttachmentUploader { case (_, _, .fileServer): return Network.FileServer.downloadUrlString(for: response.id) - case (_, _, .community(let info)): - return OpenGroupAPI.downloadUrlString( + case (_, _, .community(let roomToken, let server)): + return Network.SOGS.downloadUrlString( for: response.id, - server: info.server, - roomToken: info.roomToken + server: server, + roomToken: roomToken ) } }(), diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift index e915187bad..79ca54ca61 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift @@ -307,7 +307,7 @@ extension MessageReceiver { // devices that had the group before they were promoted try SnodeReceivedMessageInfo .filter(SnodeReceivedMessageInfo.Columns.swarmPublicKey == groupSessionId.hexString) - .filter(SnodeReceivedMessageInfo.Columns.namespace == SnodeAPI.Namespace.groupMessages.rawValue) + .filter(SnodeReceivedMessageInfo.Columns.namespace == Network.SnodeAPI.Namespace.groupMessages.rawValue) .updateAllAndConfig( db, SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid.set(to: true), @@ -753,7 +753,7 @@ extension MessageReceiver { ) else { return } - try? SnodeAPI + try? Network.SnodeAPI .preparedDeleteMessages( serverHashes: Array(hashes), requireSuccessfulDeletion: false, @@ -921,7 +921,7 @@ extension MessageReceiver { db.afterCommit { dependencies[singleton: .storage] .readPublisher { db in - try SnodeAPI.preparedDeleteMessages( + try Network.SnodeAPI.preparedDeleteMessages( serverHashes: [serverHash], requireSuccessfulDeletion: false, authMethod: try Authentication.with( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift index 3796c45882..c10460d2f4 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift @@ -70,7 +70,7 @@ extension MessageReceiver { case .contact: dependencies[singleton: .storage] .readPublisher { db in - try SnodeAPI.preparedDeleteMessages( + try Network.SnodeAPI.preparedDeleteMessages( serverHashes: Array(hashes), requireSuccessfulDeletion: false, authMethod: try Authentication.with( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift index 241ce6d030..8defb0e98d 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift @@ -13,7 +13,7 @@ extension MessageSender { thread: SessionThread, group: ClosedGroup, members: [GroupMember], - preparedNotificationsSubscription: Network.PreparedRequest? + preparedNotificationsSubscription: Network.PreparedRequest? ) public static func createGroup( @@ -119,14 +119,19 @@ extension MessageSender { ) // Prepare the notification subscription - var preparedNotificationSubscription: Network.PreparedRequest? + var preparedNotificationSubscription: Network.PreparedRequest? if let token: String = dependencies[defaults: .standard, key: .deviceToken] { - preparedNotificationSubscription = try? PushNotificationAPI + preparedNotificationSubscription = try? Network.PushNotification .preparedSubscribe( - db, token: Data(hex: token), - sessionIds: [createdInfo.groupSessionId], + swarms: [( + createdInfo.groupSessionId, + Authentication.groupAdmin( + groupSessionId: createdInfo.groupSessionId, + ed25519SecretKey: createdInfo.identityKeyPair.secretKey + ) + )], using: dependencies ) } @@ -593,7 +598,7 @@ extension MessageSender { using: dependencies ) - maybeSupplementalKeyRequest = try SnodeAPI.preparedSendMessage( + maybeSupplementalKeyRequest = try Network.SnodeAPI.preparedSendMessage( message: SnodeMessage( recipient: sessionId.hexString, data: supplementData, @@ -677,7 +682,7 @@ extension MessageSender { /// Unrevoke the newly added members just in case they had previously gotten their access to the group /// revoked (fire-and-forget this request, we don't want it to be blocking - if the invited user still can't access /// the group the admin can resend their invitation which will also attempt to unrevoke their subaccount) - let unrevokeRequest: Network.PreparedRequest = try SnodeAPI.preparedUnrevokeSubaccounts( + let unrevokeRequest: Network.PreparedRequest = try Network.SnodeAPI.preparedUnrevokeSubaccounts( subaccountsToUnrevoke: memberJobData.map { _, _, _, subaccountToken in subaccountToken }, authMethod: Authentication.groupAdmin( groupSessionId: sessionId, @@ -856,7 +861,7 @@ extension MessageSender { using: dependencies ) - maybeSupplementalKeyRequest = try SnodeAPI.preparedSendMessage( + maybeSupplementalKeyRequest = try Network.SnodeAPI.preparedSendMessage( message: SnodeMessage( recipient: sessionId.hexString, data: supplementData, @@ -902,7 +907,7 @@ extension MessageSender { /// Unrevoke the member just in case they had previously gotten their access to the group revoked and the /// unrevoke request when initially added them failed (fire-and-forget this request, we don't want it to be blocking) - let unrevokeRequest: Network.PreparedRequest = try SnodeAPI + let unrevokeRequest: Network.PreparedRequest = try Network.SnodeAPI .preparedUnrevokeSubaccounts( subaccountsToUnrevoke: memberInfo.map { token, _ in token }, authMethod: Authentication.groupAdmin( diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 961a71a7f6..78ed522564 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -43,7 +43,7 @@ public final class MessageSender { public static func preparedSend( message: Message, to destination: Message.Destination, - namespace: SnodeAPI.Namespace?, + namespace: Network.SnodeAPI.Namespace?, interactionId: Int64?, attachments: [(attachment: Attachment, fileId: String)]?, authMethod: AuthenticationMethod, @@ -138,7 +138,7 @@ public final class MessageSender { private static func preparedSendToSnodeDestination( message: Message, to destination: Message.Destination, - namespace: SnodeAPI.Namespace?, + namespace: Network.SnodeAPI.Namespace?, interactionId: Int64?, attachments: [(attachment: Attachment, fileId: String)]?, messageSendTimestampMs: Int64, @@ -146,7 +146,9 @@ public final class MessageSender { onEvent: ((Event) -> Void)?, using dependencies: Dependencies ) throws -> Network.PreparedRequest { - guard let namespace: SnodeAPI.Namespace = namespace else { throw MessageSenderError.invalidMessage } + guard let namespace: Network.SnodeAPI.Namespace = namespace else { + throw MessageSenderError.invalidMessage + } /// Set the sender/recipient info (needed to be valid) /// @@ -201,7 +203,7 @@ public final class MessageSender { // Perform any pre-send actions onEvent?(.willSend(message, destination, interactionId: interactionId)) - return try SnodeAPI + return try Network.SnodeAPI .preparedSendMessage( message: snodeMessage, in: namespace, @@ -287,7 +289,7 @@ public final class MessageSender { // Perform any pre-send actions onEvent?(.willSend(message, destination, interactionId: interactionId)) - return try OpenGroupAPI + return try Network.SOGS .preparedSend( plaintext: plaintext, roomToken: roomToken, @@ -352,7 +354,7 @@ public final class MessageSender { // Perform any pre-send actions onEvent?(.willSend(message, destination, interactionId: interactionId)) - return try OpenGroupAPI + return try Network.SOGS .preparedSend( ciphertext: ciphertext, toInboxFor: recipientBlindedPublicKey, @@ -371,7 +373,7 @@ public final class MessageSender { // MARK: - Message Wrapping public static func encodeMessageForSending( - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, destination: Message.Destination, message: Message, attachments: [(attachment: Attachment, fileId: String)]?, diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupOnlyRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupOnlyRequest.swift deleted file mode 100644 index 1a87dcf8e5..0000000000 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupOnlyRequest.swift +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -extension PushNotificationAPI { - struct LegacyGroupOnlyRequest: Codable { - let token: String - let pubKey: String - let device: String - let legacyGroupPublicKeys: Set - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupRequest.swift deleted file mode 100644 index 962011dfd4..0000000000 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupRequest.swift +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -extension PushNotificationAPI { - struct LegacyGroupRequest: Codable { - let pubKey: String - let closedGroupPublicKey: String - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyNotifyRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyNotifyRequest.swift deleted file mode 100644 index 491fa77570..0000000000 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyNotifyRequest.swift +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -extension PushNotificationAPI { - struct LegacyNotifyRequest: Codable { - enum CodingKeys: String, CodingKey { - case data - case sendTo = "send_to" - } - - let data: String - let sendTo: String - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyPushServerResponse.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyPushServerResponse.swift deleted file mode 100644 index bd412f24e7..0000000000 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyPushServerResponse.swift +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -public extension PushNotificationAPI { - struct LegacyPushServerResponse: Codable { - let code: Int - let message: String? - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift deleted file mode 100644 index f29520550b..0000000000 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import SessionNetworkingKit - -extension PushNotificationAPI { - struct LegacyUnsubscribeRequest: Codable { - private let token: String - - init(token: String) { - self.token = token - } - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI+SMK.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI+SMK.swift new file mode 100644 index 0000000000..dda9ad50dd --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI+SMK.swift @@ -0,0 +1,127 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Combine +import GRDB +import SessionNetworkingKit +import SessionUtilitiesKit + +public extension Network.PushNotification { + static func subscribeAll( + token: Data, + isForcedUpdate: Bool, + using dependencies: Dependencies + ) -> AnyPublisher { + let hexEncodedToken: String = token.toHexString() + let oldToken: String? = dependencies[defaults: .standard, key: .deviceToken] + let lastUploadTime: Double = dependencies[defaults: .standard, key: .lastDeviceTokenUpload] + let now: TimeInterval = dependencies.dateNow.timeIntervalSince1970 + + guard isForcedUpdate || hexEncodedToken != oldToken || now - lastUploadTime > tokenExpirationInterval else { + Log.info(.pushNotificationAPI, "Device token hasn't changed or expired; no need to re-upload.") + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + return dependencies[singleton: .storage] + .readPublisher { db -> Network.PreparedRequest in + let userSessionId: SessionId = dependencies[cache: .general].sessionId + let userAuthMethod: AuthenticationMethod = try Authentication.with( + db, + swarmPublicKey: userSessionId.hexString, + using: dependencies + ) + + return try Network.PushNotification + .preparedSubscribe( + token: token, + swarms: [(userSessionId, userAuthMethod)] + .appending(contentsOf: try ClosedGroup + .select(.threadId) + .filter( + ClosedGroup.Columns.threadId > SessionId.Prefix.group.rawValue && + ClosedGroup.Columns.threadId < SessionId.Prefix.group.endOfRangeString + ) + .filter(ClosedGroup.Columns.shouldPoll) + .asRequest(of: String.self) + .fetchSet(db) + .map { threadId in + ( + SessionId(.group, hex: threadId), + try Authentication.with( + db, + swarmPublicKey: threadId, + using: dependencies + ) + ) + } + ), + using: dependencies + ) + .handleEvents( + receiveOutput: { _, response in + guard response.subResponses.first?.success == true else { return } + + dependencies[defaults: .standard, key: .deviceToken] = hexEncodedToken + dependencies[defaults: .standard, key: .lastDeviceTokenUpload] = now + dependencies[defaults: .standard, key: .isUsingFullAPNs] = true + } + ) + } + .flatMap { $0.send(using: dependencies) } + .map { _ in () } + .eraseToAnyPublisher() + } + + public static func unsubscribeAll( + token: Data, + using dependencies: Dependencies + ) -> AnyPublisher { + return dependencies[singleton: .storage] + .readPublisher { db -> Network.PreparedRequest in + let userSessionId: SessionId = dependencies[cache: .general].sessionId + let userAuthMethod: AuthenticationMethod = try Authentication.with( + db, + swarmPublicKey: userSessionId.hexString, + using: dependencies + ) + + return try Network.PushNotification + .preparedUnsubscribe( + token: token, + swarms: [(userSessionId, userAuthMethod)] + .appending(contentsOf: (try? ClosedGroup + .select(.threadId) + .filter( + ClosedGroup.Columns.threadId > SessionId.Prefix.group.rawValue && + ClosedGroup.Columns.threadId < SessionId.Prefix.group.endOfRangeString + ) + .asRequest(of: String.self) + .fetchSet(db)) + .defaulting(to: []) + .map { threadId in + ( + SessionId(.group, hex: threadId), + try Authentication.with( + db, + swarmPublicKey: threadId, + using: dependencies + ) + ) + }), + using: dependencies + ) + .handleEvents( + receiveOutput: { _, response in + guard response.subResponses.first?.success == true else { return } + + dependencies[defaults: .standard, key: .deviceToken] = nil + } + ) + } + .flatMap { $0.send(using: dependencies) } + .map { _ in () } + .eraseToAnyPublisher() + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift deleted file mode 100644 index 2b4201f5a1..0000000000 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ /dev/null @@ -1,320 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -// -// stringlint:disable - -import Foundation -import Combine -import GRDB -import SessionNetworkingKit -import SessionUtilitiesKit - -// MARK: - KeychainStorage - -public extension KeychainStorage.DataKey { static let pushNotificationEncryptionKey: Self = "PNEncryptionKeyKey" } - -// MARK: - Log.Category - -private extension Log.Category { - static let cat: Log.Category = .create("PushNotificationAPI", defaultLevel: .info) -} - -// MARK: - PushNotificationAPI - -public enum PushNotificationAPI { - internal static let encryptionKeyLength: Int = 32 - private static let maxRetryCount: Int = 4 - private static let tokenExpirationInterval: TimeInterval = (12 * 60 * 60) - - public static let server: String = "https://push.getsession.org" - public static let serverPublicKey = "d7557fe563e2610de876c0ac7341b62f3c82d5eea4b62c702392ea4368f51b3b" - - // MARK: - Batch Requests - - public static func subscribeAll( - token: Data, - isForcedUpdate: Bool, - using dependencies: Dependencies - ) -> AnyPublisher { - let hexEncodedToken: String = token.toHexString() - let oldToken: String? = dependencies[defaults: .standard, key: .deviceToken] - let lastUploadTime: Double = dependencies[defaults: .standard, key: .lastDeviceTokenUpload] - let now: TimeInterval = dependencies.dateNow.timeIntervalSince1970 - - guard isForcedUpdate || hexEncodedToken != oldToken || now - lastUploadTime > tokenExpirationInterval else { - Log.info(.cat, "Device token hasn't changed or expired; no need to re-upload.") - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - return dependencies[singleton: .storage] - .readPublisher { db -> Network.PreparedRequest in - let userSessionId: SessionId = dependencies[cache: .general].sessionId - - return try PushNotificationAPI - .preparedSubscribe( - db, - token: token, - sessionIds: [userSessionId] - .appending(contentsOf: try ClosedGroup - .select(.threadId) - .filter( - ClosedGroup.Columns.threadId > SessionId.Prefix.group.rawValue && - ClosedGroup.Columns.threadId < SessionId.Prefix.group.endOfRangeString - ) - .filter(ClosedGroup.Columns.shouldPoll) - .asRequest(of: String.self) - .fetchSet(db) - .map { SessionId(.group, hex: $0) } - ), - using: dependencies - ) - .handleEvents( - receiveOutput: { _, response in - guard response.subResponses.first?.success == true else { return } - - dependencies[defaults: .standard, key: .deviceToken] = hexEncodedToken - dependencies[defaults: .standard, key: .lastDeviceTokenUpload] = now - dependencies[defaults: .standard, key: .isUsingFullAPNs] = true - } - ) - } - .flatMap { $0.send(using: dependencies) } - .map { _ in () } - .eraseToAnyPublisher() - } - - public static func unsubscribeAll( - token: Data, - using dependencies: Dependencies - ) -> AnyPublisher { - return dependencies[singleton: .storage] - .readPublisher { db -> Network.PreparedRequest in - let userSessionId: SessionId = dependencies[cache: .general].sessionId - - return try PushNotificationAPI - .preparedUnsubscribe( - db, - token: token, - sessionIds: [userSessionId] - .appending(contentsOf: (try? ClosedGroup - .select(.threadId) - .filter( - ClosedGroup.Columns.threadId > SessionId.Prefix.group.rawValue && - ClosedGroup.Columns.threadId < SessionId.Prefix.group.endOfRangeString - ) - .asRequest(of: String.self) - .fetchSet(db)) - .defaulting(to: []) - .map { SessionId(.group, hex: $0) }), - using: dependencies - ) - .handleEvents( - receiveOutput: { _, response in - guard response.subResponses.first?.success == true else { return } - - dependencies[defaults: .standard, key: .deviceToken] = nil - } - ) - } - .flatMap { $0.send(using: dependencies) } - .map { _ in () } - .eraseToAnyPublisher() - } - - // MARK: - Prepared Requests - - public static func preparedSubscribe( - _ db: ObservingDatabase, - token: Data, - sessionIds: [SessionId], - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - guard dependencies[defaults: .standard, key: .isUsingFullAPNs] else { - throw NetworkError.invalidPreparedRequest - } - - guard let notificationsEncryptionKey: Data = try? dependencies[singleton: .keychain].getOrGenerateEncryptionKey( - forKey: .pushNotificationEncryptionKey, - length: encryptionKeyLength, - cat: .cat, - legacyKey: "PNEncryptionKeyKey", - legacyService: "PNKeyChainService" - ) else { - Log.error(.cat, "Unable to retrieve PN encryption key.") - throw KeychainStorageError.keySpecInvalid - } - - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: Endpoint.subscribe, - body: SubscribeRequest( - subscriptions: sessionIds.map { sessionId -> SubscribeRequest.Subscription in - SubscribeRequest.Subscription( - namespaces: { - switch sessionId.prefix { - case .group: return [ - .groupMessages, - .configGroupKeys, - .configGroupInfo, - .configGroupMembers, - .revokedRetrievableGroupMessages - ] - default: return [ - .default, - .configUserProfile, - .configContacts, - .configConvoInfoVolatile, - .configUserGroups - ] - } - }(), - /// Note: Unfortunately we always need the message content because without the content - /// control messages can't be distinguished from visible messages which results in the - /// 'generic' notification being shown when receiving things like typing indicator updates - includeMessageData: true, - serviceInfo: ServiceInfo( - token: token.toHexString() - ), - notificationsEncryptionKey: notificationsEncryptionKey, - authMethod: try Authentication.with( - db, - swarmPublicKey: sessionId.hexString, - using: dependencies - ), - timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000) // Seconds - ) - } - ) - ), - responseType: SubscribeResponse.self, - retryCount: PushNotificationAPI.maxRetryCount, - using: dependencies - ) - .handleEvents( - receiveOutput: { _, response in - zip(response.subResponses, sessionIds).forEach { subResponse, sessionId in - guard subResponse.success != true else { return } - - Log.error(.cat, "Couldn't subscribe for push notifications for: \(sessionId) due to error (\(subResponse.error ?? -1)): \(subResponse.message ?? "nil").") - } - }, - receiveCompletion: { result in - switch result { - case .finished: break - case .failure(let error): Log.error(.cat, "Couldn't subscribe for push notifications due to error: \(error).") - } - } - ) - } - - public static func preparedUnsubscribe( - _ db: ObservingDatabase, - token: Data, - sessionIds: [SessionId], - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: Endpoint.unsubscribe, - body: UnsubscribeRequest( - subscriptions: sessionIds.map { sessionId -> UnsubscribeRequest.Subscription in - UnsubscribeRequest.Subscription( - serviceInfo: ServiceInfo( - token: token.toHexString() - ), - authMethod: try Authentication.with( - db, - swarmPublicKey: sessionId.hexString, - using: dependencies - ), - timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000) // Seconds - ) - } - ) - ), - responseType: UnsubscribeResponse.self, - retryCount: PushNotificationAPI.maxRetryCount, - using: dependencies - ) - .handleEvents( - receiveOutput: { _, response in - zip(response.subResponses, sessionIds).forEach { subResponse, sessionId in - guard subResponse.success != true else { return } - - Log.error(.cat, "Couldn't unsubscribe for push notifications for: \(sessionId) due to error (\(subResponse.error ?? -1)): \(subResponse.message ?? "nil").") - } - }, - receiveCompletion: { result in - switch result { - case .finished: break - case .failure(let error): Log.error(.cat, "Couldn't unsubscribe for push notifications due to error: \(error).") - } - } - ) - } - - // MARK: - Notification Handling - - public static func processNotification( - notificationContent: UNNotificationContent, - using dependencies: Dependencies - ) -> (data: Data?, metadata: NotificationMetadata, result: ProcessResult) { - // Make sure the notification is from the updated push server - guard notificationContent.userInfo["spns"] != nil else { - return (nil, .invalid, .legacyFailure) - } - - guard let base64EncodedEncString: String = notificationContent.userInfo["enc_payload"] as? String else { - return (nil, .invalid, .failureNoContent) - } - - // Decrypt and decode the payload - let notification: BencodeResponse - - do { - guard let encryptedData: Data = Data(base64Encoded: base64EncodedEncString) else { - throw CryptoError.invalidBase64EncodedData - } - - let notificationsEncryptionKey: Data = try dependencies[singleton: .keychain].getOrGenerateEncryptionKey( - forKey: .pushNotificationEncryptionKey, - length: encryptionKeyLength, - cat: .cat, - legacyKey: "PNEncryptionKeyKey", - legacyService: "PNKeyChainService" - ) - let decryptedData: Data = try dependencies[singleton: .crypto].tryGenerate( - .plaintextWithPushNotificationPayload( - payload: encryptedData, - encKey: notificationsEncryptionKey - ) - ) - notification = try BencodeDecoder(using: dependencies) - .decode(BencodeResponse.self, from: decryptedData) - } - catch { - Log.error(.cat, "Failed to decrypt or decode notification due to error: \(error)") - return (nil, .invalid, .failure) - } - - // If the metadata says that the message was too large then we should show the generic - // notification (this is a valid case) - guard !notification.info.dataTooLong else { return (nil, notification.info, .successTooLong) } - - // Check that the body we were given is valid and not empty - guard - let notificationData: Data = notification.data, - notification.info.dataLength == notificationData.count, - !notificationData.isEmpty - else { - Log.error(.cat, "Get notification data failed") - return (nil, notification.info, .failureNoContent) - } - - // Success, we have the notification content - return (notificationData, notification.info, .success) - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CommunityPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CommunityPoller.swift index 39b2d0956f..40477f59f3 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CommunityPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CommunityPoller.swift @@ -20,7 +20,7 @@ public extension Cache { // MARK: - CommunityPollerType public protocol CommunityPollerType { - typealias PollResponse = (info: ResponseInfoType, data: Network.BatchResponseMap) + typealias PollResponse = (info: ResponseInfoType, data: Network.BatchResponseMap) var isPolling: Bool { get } var receivedPollResponse: AnyPublisher { get } @@ -31,14 +31,13 @@ public protocol CommunityPollerType { // MARK: - CommunityPoller -private typealias Capabilities = OpenGroupAPI.Capabilities +private typealias Capabilities = Network.SOGS.CapabilitiesResponse public final class CommunityPoller: CommunityPollerType & PollerType { // MARK: - Settings private static let minPollInterval: TimeInterval = 3 private static let maxPollInterval: TimeInterval = (60 * 60) - internal static let maxInactivityPeriod: TimeInterval = (14 * 24 * 60 * 60) /// If there are hidden rooms that we poll and they fail too many times we want to prune them (as it likely means they no longer /// exist, and since they are already hidden it's unlikely that the user will notice that we stopped polling for them) @@ -75,7 +74,7 @@ public final class CommunityPoller: CommunityPollerType & PollerType { pollerQueue: DispatchQueue, pollerDestination: PollerDestination, pollerDrainBehaviour: ThreadSafeObject = .alwaysRandom, - namespaces: [SnodeAPI.Namespace] = [], + namespaces: [Network.SnodeAPI.Namespace] = [], failureCount: Int, shouldStoreMessages: Bool, logStartAndStopCalls: Bool, @@ -217,13 +216,13 @@ public final class CommunityPoller: CommunityPollerType & PollerType { .subscribe(on: pollerQueue, using: dependencies) .receive(on: pollerQueue, using: dependencies) .tryMap { [dependencies] authMethod in - try OpenGroupAPI.preparedCapabilities( + try Network.SOGS.preparedCapabilities( authMethod: authMethod, using: dependencies ) } .flatMap { [dependencies] in $0.send(using: dependencies) } - .flatMapStorageWritePublisher(using: dependencies) { [pollerDestination] (db: ObservingDatabase, response: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities)) in + .flatMapStorageWritePublisher(using: dependencies) { [pollerDestination] (db: ObservingDatabase, response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesResponse)) in OpenGroupManager.handleCapabilities( db, capabilities: response.data, @@ -260,7 +259,7 @@ public final class CommunityPoller: CommunityPollerType & PollerType { /// for cases where we need explicit/custom behaviours to occur (eg. Onboarding) public func poll(forceSynchronousProcessing: Bool = false) -> AnyPublisher { typealias PollInfo = ( - roomInfo: [OpenGroupAPI.RoomInfo], + roomInfo: [Network.SOGS.PollRoomInfo], lastInboxMessageId: Int64, lastOutboxMessageId: Int64, authMethod: AuthenticationMethod @@ -276,15 +275,15 @@ public final class CommunityPoller: CommunityPollerType & PollerType { .readPublisher { [pollerDestination, dependencies] db -> PollInfo in /// **Note:** The `OpenGroup` type converts to lowercase in init let server: String = pollerDestination.target.lowercased() - let roomInfo: [OpenGroupAPI.RoomInfo] = try OpenGroup + let roomInfo: [Network.SOGS.PollRoomInfo] = try OpenGroup .select(.roomToken, .infoUpdates, .sequenceNumber) .filter(OpenGroup.Columns.server == server) .filter(OpenGroup.Columns.isActive == true) .filter(OpenGroup.Columns.roomToken != "") - .asRequest(of: OpenGroupAPI.RoomInfo.self) + .asRequest(of: Network.SOGS.PollRoomInfo.self) .fetchAll(db) - guard !roomInfo.isEmpty else { throw OpenGroupAPIError.invalidPoll } + guard !roomInfo.isEmpty else { throw SOGSError.invalidPoll } return ( roomInfo, @@ -303,12 +302,15 @@ public final class CommunityPoller: CommunityPollerType & PollerType { try Authentication.with(db, server: server, using: dependencies) ) } - .tryFlatMap { [pollCount, dependencies] pollInfo -> AnyPublisher<(ResponseInfoType, Network.BatchResponseMap), Error> in - try OpenGroupAPI + .tryFlatMap { [pollCount, dependencies] pollInfo -> AnyPublisher<(ResponseInfoType, Network.BatchResponseMap), Error> in + try Network.SOGS .preparedPoll( roomInfo: pollInfo.roomInfo, lastInboxMessageId: pollInfo.lastInboxMessageId, lastOutboxMessageId: pollInfo.lastOutboxMessageId, + checkForCommunityMessageRequests: dependencies.mutate(cache: .libSession) { + $0.get(.checkForCommunityMessageRequests) + }, hasPerformedInitialPoll: (pollCount > 0), timeSinceLastPoll: (dependencies.dateNow.timeIntervalSince1970 - lastSuccessfulPollTimestamp), authMethod: pollInfo.authMethod, @@ -340,16 +342,16 @@ public final class CommunityPoller: CommunityPollerType & PollerType { private func handlePollResponse( info: ResponseInfoType, - response: Network.BatchResponseMap, + response: Network.BatchResponseMap, failureCount: Int, using dependencies: Dependencies ) -> AnyPublisher { var rawMessageCount: Int = 0 - let validResponses: [OpenGroupAPI.Endpoint: Any] = response.data + let validResponses: [Network.SOGS.Endpoint: Any] = response.data .filter { endpoint, data in switch endpoint { case .capabilities: - guard (data as? Network.BatchSubResponse)?.body != nil else { + guard (data as? Network.BatchSubResponse)?.body != nil else { Log.error(.poller, "\(pollerName) failed due to invalid capability data.") return false } @@ -357,8 +359,8 @@ public final class CommunityPoller: CommunityPollerType & PollerType { return true case .roomPollInfo(let roomToken, _): - guard (data as? Network.BatchSubResponse)?.body != nil else { - switch (data as? Network.BatchSubResponse)?.code { + guard (data as? Network.BatchSubResponse)?.body != nil else { + switch (data as? Network.BatchSubResponse)?.code { case 404: Log.error(.poller, "\(pollerName) failed to retrieve info for unknown room '\(roomToken)'.") default: Log.error(.poller, "\(pollerName) failed due to invalid room info data.") } @@ -369,17 +371,17 @@ public final class CommunityPoller: CommunityPollerType & PollerType { case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _): guard - let responseData: Network.BatchSubResponse<[Failable]> = data as? Network.BatchSubResponse<[Failable]>, - let responseBody: [Failable] = responseData.body + let responseData: Network.BatchSubResponse<[Failable]> = data as? Network.BatchSubResponse<[Failable]>, + let responseBody: [Failable] = responseData.body else { - switch (data as? Network.BatchSubResponse<[Failable]>)?.code { + switch (data as? Network.BatchSubResponse<[Failable]>)?.code { case 404: Log.error(.poller, "\(pollerName) failed to retrieve messages for unknown room '\(roomToken)'.") default: Log.error(.poller, "\(pollerName) failed due to invalid messages data.") } return false } - let successfulMessages: [OpenGroupAPI.Message] = responseBody.compactMap { $0.value } + let successfulMessages: [Network.SOGS.Message] = responseBody.compactMap { $0.value } rawMessageCount += successfulMessages.count if successfulMessages.count != responseBody.count { @@ -392,7 +394,7 @@ public final class CommunityPoller: CommunityPollerType & PollerType { case .inbox, .inboxSince, .outbox, .outboxSince: guard - let responseData: Network.BatchSubResponse<[OpenGroupAPI.DirectMessage]?> = data as? Network.BatchSubResponse<[OpenGroupAPI.DirectMessage]?>, + let responseData: Network.BatchSubResponse<[Network.SOGS.DirectMessage]?> = data as? Network.BatchSubResponse<[Network.SOGS.DirectMessage]?>, !responseData.failedToParseBody else { Log.error(.poller, "\(pollerName) failed due to invalid inbox/outbox data.") @@ -400,7 +402,7 @@ public final class CommunityPoller: CommunityPollerType & PollerType { } // Double optional because the server can return a `304` with an empty body - let messages: [OpenGroupAPI.DirectMessage] = ((responseData.body ?? []) ?? []) + let messages: [Network.SOGS.DirectMessage] = ((responseData.body ?? []) ?? []) rawMessageCount += messages.count return !messages.isEmpty @@ -428,18 +430,18 @@ public final class CommunityPoller: CommunityPollerType & PollerType { } return dependencies[singleton: .storage] - .readPublisher { [pollerDestination] db -> (capabilities: OpenGroupAPI.Capabilities, groups: [OpenGroup]) in + .readPublisher { [pollerDestination] db -> (capabilities: Network.SOGS.CapabilitiesResponse, groups: [OpenGroup]) in let allCapabilities: [Capability] = try Capability .filter(Capability.Columns.openGroupServer == pollerDestination.target) .fetchAll(db) - let capabilities: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities( + let capabilities: Network.SOGS.CapabilitiesResponse = Network.SOGS.CapabilitiesResponse( capabilities: allCapabilities .filter { !$0.isMissing } - .map { $0.variant }, + .map { $0.variant.rawValue }, missing: { - let missingCapabilities: [Capability.Variant] = allCapabilities + let missingCapabilities: [String] = allCapabilities .filter { $0.isMissing } - .map { $0.variant } + .map { $0.variant.rawValue } return (missingCapabilities.isEmpty ? nil : missingCapabilities) }() @@ -452,22 +454,22 @@ public final class CommunityPoller: CommunityPollerType & PollerType { return (capabilities, groups) } - .flatMap { [pollerDestination, dependencies] (capabilities: OpenGroupAPI.Capabilities, groups: [OpenGroup]) -> AnyPublisher in - let changedResponses: [OpenGroupAPI.Endpoint: Any] = validResponses + .flatMap { [pollerDestination, dependencies] (capabilities: Network.SOGS.CapabilitiesResponse, groups: [OpenGroup]) -> AnyPublisher in + let changedResponses: [Network.SOGS.Endpoint: Any] = validResponses .filter { endpoint, data in switch endpoint { case .capabilities: guard - let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, - let responseBody: OpenGroupAPI.Capabilities = responseData.body + let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, + let responseBody: Network.SOGS.CapabilitiesResponse = responseData.body else { return false } return (responseBody != capabilities) case .roomPollInfo(let roomToken, _): guard - let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, - let responseBody: OpenGroupAPI.RoomPollInfo = responseData.body + let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, + let responseBody: Network.SOGS.RoomPollInfo = responseData.body else { return false } guard let existingOpenGroup: OpenGroup = groups.first(where: { $0.roomToken == roomToken }) else { return true @@ -507,8 +509,8 @@ public final class CommunityPoller: CommunityPollerType & PollerType { switch endpoint { case .capabilities: guard - let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, - let responseBody: OpenGroupAPI.Capabilities = responseData.body + let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, + let responseBody: Network.SOGS.CapabilitiesResponse = responseData.body else { return } OpenGroupManager.handleCapabilities( @@ -519,8 +521,8 @@ public final class CommunityPoller: CommunityPollerType & PollerType { case .roomPollInfo(let roomToken, _): guard - let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, - let responseBody: OpenGroupAPI.RoomPollInfo = responseData.body + let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, + let responseBody: Network.SOGS.RoomPollInfo = responseData.body else { return } try OpenGroupManager.handlePollInfo( @@ -534,8 +536,8 @@ public final class CommunityPoller: CommunityPollerType & PollerType { case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _): guard - let responseData: Network.BatchSubResponse<[Failable]> = data as? Network.BatchSubResponse<[Failable]>, - let responseBody: [Failable] = responseData.body + let responseData: Network.BatchSubResponse<[Failable]> = data as? Network.BatchSubResponse<[Failable]>, + let responseBody: [Failable] = responseData.body else { return } interactionInfo.append( @@ -550,12 +552,12 @@ public final class CommunityPoller: CommunityPollerType & PollerType { case .inbox, .inboxSince, .outbox, .outboxSince: guard - let responseData: Network.BatchSubResponse<[OpenGroupAPI.DirectMessage]?> = data as? Network.BatchSubResponse<[OpenGroupAPI.DirectMessage]?>, + let responseData: Network.BatchSubResponse<[Network.SOGS.DirectMessage]?> = data as? Network.BatchSubResponse<[Network.SOGS.DirectMessage]?>, !responseData.failedToParseBody else { return } // Double optional because the server can return a `304` with an empty body - let messages: [OpenGroupAPI.DirectMessage] = ((responseData.body ?? []) ?? []) + let messages: [Network.SOGS.DirectMessage] = ((responseData.body ?? []) ?? []) let fromOutbox: Bool = { switch endpoint { case .outbox, .outboxSince: return true @@ -734,4 +736,4 @@ public extension CommunityPollerCacheType { // MARK: - Conformance -extension OpenGroupAPI.RoomInfo: FetchableRecord {} +extension Network.SOGS.PollRoomInfo: @retroactive FetchableRecord {} diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift index 804ed95182..57c63b8be8 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift @@ -33,7 +33,7 @@ public extension Singleton { // MARK: - CurrentUserPoller public final class CurrentUserPoller: SwarmPoller { - public static let namespaces: [SnodeAPI.Namespace] = [ + public static let namespaces: [Network.SnodeAPI.Namespace] = [ .default, .configUserProfile, .configContacts, .configConvoInfoVolatile, .configUserGroups ] private let pollInterval: TimeInterval = 1.5 diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift index 1ec82b4124..7e816d4a4f 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift @@ -23,7 +23,7 @@ public final class GroupPoller: SwarmPoller { private let minPollInterval: Double = 3 private let maxPollInterval: Double = 30 - public static func namespaces(swarmPublicKey: String) -> [SnodeAPI.Namespace] { + public static func namespaces(swarmPublicKey: String) -> [Network.SnodeAPI.Namespace] { guard (try? SessionId.Prefix(from: swarmPublicKey)) == .group else { return [.legacyClosedGroup] } @@ -62,7 +62,7 @@ public final class GroupPoller: SwarmPoller { .flatMap { [receivedPollResponse] _ in receivedPollResponse } .first() .map { $0.filter { $0.isConfigMessage } } - .filter { !$0.contains(where: { $0.namespace == SnodeAPI.Namespace.configGroupKeys }) } + .filter { !$0.contains(where: { $0.namespace == Network.SnodeAPI.Namespace.configGroupKeys }) } .sinkUntilComplete( receiveValue: { [pollerDestination, pollerName, dependencies] configMessages in Log.error(.poller, "\(pollerName) received no config messages in it's first poll, flagging as expired.") diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/PollerType.swift b/SessionMessagingKit/Sending & Receiving/Pollers/PollerType.swift index 91ef8cf1b9..c3e16e5fc5 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/PollerType.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/PollerType.swift @@ -64,7 +64,7 @@ public protocol PollerType: AnyObject { pollerQueue: DispatchQueue, pollerDestination: PollerDestination, pollerDrainBehaviour: ThreadSafeObject, - namespaces: [SnodeAPI.Namespace], + namespaces: [Network.SnodeAPI.Namespace], failureCount: Int, shouldStoreMessages: Bool, logStartAndStopCalls: Bool, diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift index 2f3ca1906a..cfb327b06e 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift @@ -41,7 +41,7 @@ public class SwarmPoller: SwarmPollerType & PollerType { public var lastPollStart: TimeInterval = 0 public var cancellable: AnyCancellable? - private let namespaces: [SnodeAPI.Namespace] + private let namespaces: [Network.SnodeAPI.Namespace] private let customAuthMethod: AuthenticationMethod? private let shouldStoreMessages: Bool private let receivedPollResponseSubject: PassthroughSubject = PassthroughSubject() @@ -53,7 +53,7 @@ public class SwarmPoller: SwarmPollerType & PollerType { pollerQueue: DispatchQueue, pollerDestination: PollerDestination, pollerDrainBehaviour: ThreadSafeObject, - namespaces: [SnodeAPI.Namespace], + namespaces: [Network.SnodeAPI.Namespace], failureCount: Int = 0, shouldStoreMessages: Bool, logStartAndStopCalls: Bool, @@ -108,8 +108,8 @@ public class SwarmPoller: SwarmPollerType & PollerType { /// Fetch the messages return dependencies[singleton: .network] .getSwarm(for: pollerDestination.target) - .tryFlatMapWithRandomSnode(drainBehaviour: _pollerDrainBehaviour, using: dependencies) { [pollerDestination, customAuthMethod, namespaces, dependencies] snode -> AnyPublisher<(LibSession.Snode, Network.PreparedRequest), Error> in - dependencies[singleton: .storage].readPublisher { db -> (LibSession.Snode, Network.PreparedRequest) in + .tryFlatMapWithRandomSnode(drainBehaviour: _pollerDrainBehaviour, using: dependencies) { [pollerDestination, customAuthMethod, namespaces, dependencies] snode -> AnyPublisher<(LibSession.Snode, Network.PreparedRequest), Error> in + dependencies[singleton: .storage].readPublisher { db -> (LibSession.Snode, Network.PreparedRequest) in let authMethod: AuthenticationMethod = try (customAuthMethod ?? Authentication.with( db, swarmPublicKey: pollerDestination.target, @@ -118,7 +118,7 @@ public class SwarmPoller: SwarmPollerType & PollerType { return ( snode, - try SnodeAPI.preparedPoll( + try Network.SnodeAPI.preparedPoll( db, namespaces: namespaces, refreshingConfigHashes: activeHashes, @@ -134,10 +134,10 @@ public class SwarmPoller: SwarmPollerType & PollerType { .map { _, response in (snode, response) } } .flatMapStorageWritePublisher(using: dependencies, updates: { [pollerDestination, shouldStoreMessages, forceSynchronousProcessing, dependencies] db, info -> ([Job], [Job], PollResult) in - let (snode, namespacedResults): (LibSession.Snode, SnodeAPI.PollResponse) = info + let (snode, namespacedResults): (LibSession.Snode, Network.SnodeAPI.PollResponse) = info /// Get all of the messages and sort them by their required `processingOrder` - typealias MessageData = (namespace: SnodeAPI.Namespace, messages: [SnodeReceivedMessage], lastHash: String?) + typealias MessageData = (namespace: Network.SnodeAPI.Namespace, messages: [SnodeReceivedMessage], lastHash: String?) let sortedMessages: [MessageData] = namespacedResults .compactMap { namespace, result -> MessageData? in (result.data?.messages).map { (namespace, $0, result.data?.lastHash) } @@ -233,7 +233,7 @@ public class SwarmPoller: SwarmPollerType & PollerType { shouldStoreMessages: Bool, ignoreDedupeFiles: Bool, forceSynchronousProcessing: Bool, - sortedMessages: [(namespace: SnodeAPI.Namespace, messages: [SnodeReceivedMessage], lastHash: String?)], + sortedMessages: [(namespace: Network.SnodeAPI.Namespace, messages: [SnodeReceivedMessage], lastHash: String?)], using dependencies: Dependencies ) -> ([Job], [Job], PollResult) { /// No need to do anything if there are no messages diff --git a/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift b/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift index c67a3437eb..b56a2a423a 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift @@ -415,7 +415,7 @@ public extension MessageViewModel.DeletionBehaviours { .chunked(by: Network.BatchRequest.childRequestLimit) .map { unsendRequestChunk in .preparedRequest( - try SnodeAPI.preparedBatch( + try Network.SnodeAPI.preparedBatch( requests: unsendRequestChunk, requireAllBatchResponses: false, swarmPublicKey: threadData.threadId, @@ -426,7 +426,7 @@ public extension MessageViewModel.DeletionBehaviours { ) .appending(serverHashes.isEmpty ? nil : .preparedRequest( - try SnodeAPI.preparedDeleteMessages( + try Network.SnodeAPI.preparedDeleteMessages( serverHashes: Array(serverHashes), requireSuccessfulDeletion: false, authMethod: try Authentication.with( @@ -496,7 +496,7 @@ public extension MessageViewModel.DeletionBehaviours { .chunked(by: Network.BatchRequest.childRequestLimit) .map { unsendRequestChunk in .preparedRequest( - try SnodeAPI.preparedBatch( + try Network.SnodeAPI.preparedBatch( requests: unsendRequestChunk, requireAllBatchResponses: false, swarmPublicKey: threadData.threadId, @@ -616,7 +616,7 @@ public extension MessageViewModel.DeletionBehaviours { ) ) .appending(serverHashes.isEmpty ? nil : - .preparedRequest(try SnodeAPI + .preparedRequest(try Network.SnodeAPI .preparedDeleteMessages( serverHashes: Array(serverHashes), requireSuccessfulDeletion: false, @@ -658,7 +658,7 @@ public extension MessageViewModel.DeletionBehaviours { let deleteRequests: [Network.PreparedRequest] = try cellViewModels .compactMap { $0.openGroupServerMessageId } .map { messageId in - try OpenGroupAPI.preparedMessageDelete( + try Network.SOGS.preparedMessageDelete( id: messageId, roomToken: roomToken, authMethod: authMethod, @@ -674,7 +674,7 @@ public extension MessageViewModel.DeletionBehaviours { .chunked(by: Network.BatchRequest.childRequestLimit) .map { deleteRequestsChunk in .preparedRequest( - try OpenGroupAPI.preparedBatch( + try Network.SOGS.preparedBatch( requests: deleteRequestsChunk, authMethod: authMethod, using: dependencies diff --git a/SessionMessagingKit/Utilities/Authentication+SessionMessagingKit.swift b/SessionMessagingKit/Utilities/Authentication+SessionMessagingKit.swift index 745e1e4418..d51fb9fd78 100644 --- a/SessionMessagingKit/Utilities/Authentication+SessionMessagingKit.swift +++ b/SessionMessagingKit/Utilities/Authentication+SessionMessagingKit.swift @@ -78,38 +78,6 @@ public extension Authentication { } } } - - /// Used when interacting with a community - struct community: AuthenticationMethod { - public let openGroupCapabilityInfo: LibSession.OpenGroupCapabilityInfo - public let forceBlinded: Bool - - public var server: String { openGroupCapabilityInfo.server } - public var publicKey: String { openGroupCapabilityInfo.publicKey } - public var hasCapabilities: Bool { !openGroupCapabilityInfo.capabilities.isEmpty } - public var supportsBlinding: Bool { openGroupCapabilityInfo.capabilities.contains(.blind) } - - public var info: Info { - .community( - server: server, - publicKey: publicKey, - hasCapabilities: hasCapabilities, - supportsBlinding: supportsBlinding, - forceBlinded: forceBlinded - ) - } - - public init(info: LibSession.OpenGroupCapabilityInfo, forceBlinded: Bool = false) { - self.openGroupCapabilityInfo = info - self.forceBlinded = forceBlinded - } - - // MARK: - SignatureGenerator - - public func generateSignature(with verificationBytes: [UInt8], using dependencies: Dependencies) throws -> Authentication.Signature { - throw CryptoError.signatureGenerationFailed - } - } } // MARK: - Convenience @@ -119,6 +87,19 @@ fileprivate struct GroupAuthData: Codable, FetchableRecord { let authData: Data? } +public extension Authentication.community { + init(info: LibSession.OpenGroupCapabilityInfo, forceBlinded: Bool = false) { + self.init( + roomToken: info.roomToken, + server: info.server, + publicKey: info.publicKey, + hasCapabilities: !info.capabilities.isEmpty, + supportsBlinding: info.capabilities.contains(.blind), + forceBlinded: forceBlinded + ) + } +} + public extension Authentication { static func with( _ db: ObservingDatabase, diff --git a/SessionMessagingKit/Utilities/ExtensionHelper.swift b/SessionMessagingKit/Utilities/ExtensionHelper.swift index ed44a08963..99d5314260 100644 --- a/SessionMessagingKit/Utilities/ExtensionHelper.swift +++ b/SessionMessagingKit/Utilities/ExtensionHelper.swift @@ -738,7 +738,7 @@ public class ExtensionHelper: ExtensionHelperType { } public func loadMessages() async throws { - typealias MessageData = (namespace: SnodeAPI.Namespace, messages: [SnodeReceivedMessage], lastHash: String?) + typealias MessageData = (namespace: Network.SnodeAPI.Namespace, messages: [SnodeReceivedMessage], lastHash: String?) /// Retrieve all conversation file paths /// @@ -781,7 +781,7 @@ public class ExtensionHelper: ExtensionHelperType { do { let sortedMessages: [MessageData] = try configMessageHashes - .reduce([SnodeAPI.Namespace: [SnodeReceivedMessage]]()) { (result: [SnodeAPI.Namespace: [SnodeReceivedMessage]], hash: String) in + .reduce([Network.SnodeAPI.Namespace: [SnodeReceivedMessage]]()) { (result: [Network.SnodeAPI.Namespace: [SnodeReceivedMessage]], hash: String) in let path: String = URL(fileURLWithPath: this.conversationsPath) .appendingPathComponent(conversationHash) .appendingPathComponent(this.conversationConfigDir) diff --git a/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift b/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift index 130facfd6c..8cdfca3459 100644 --- a/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift +++ b/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift @@ -541,7 +541,7 @@ class DisplayPictureDownloadJobSpec: QuickSpec { ) ) let expectedRequest: Network.PreparedRequest = mockStorage.read { db in - try OpenGroupAPI.preparedDownload( + try Network.SOGS.preparedDownload( fileId: "12", roomToken: "testRoom", authMethod: Authentication.community( diff --git a/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift b/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift index 79692802c7..1a0770edc9 100644 --- a/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift +++ b/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift @@ -42,17 +42,22 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { MockNetwork.batchResponseData( with: [ ( - OpenGroupAPI.Endpoint.capabilities, - OpenGroupAPI.Capabilities(capabilities: [.blind, .reactions]).batchSubResponse() + Network.SOGS.Endpoint.capabilities, + Network.SOGS.CapabilitiesResponse( + capabilities: [ + Capability.Variant.blind.rawValue, + Capability.Variant.reactions.rawValue + ] + ).batchSubResponse() ), ( - OpenGroupAPI.Endpoint.rooms, + Network.SOGS.Endpoint.rooms, [ - OpenGroupAPI.Room.mock.with( + Network.SOGS.Room.mock.with( token: "testRoom", name: "TestRoomName" ), - OpenGroupAPI.Room.mock.with( + Network.SOGS.Room.mock.with( token: "testRoom2", name: "TestRoomName2", infoUpdates: 12, @@ -191,9 +196,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { let openGroups: [OpenGroup]? = mockStorage.read { db in try OpenGroup.fetchAll(db) } expect(openGroups?.count).to(equal(1)) - expect(openGroups?.map { $0.server }).to(equal([OpenGroupAPI.defaultServer])) + expect(openGroups?.map { $0.server }).to(equal([Network.SOGS.defaultServer])) expect(openGroups?.map { $0.roomToken }).to(equal([""])) - expect(openGroups?.map { $0.publicKey }).to(equal([OpenGroupAPI.defaultServerPublicKey])) + expect(openGroups?.map { $0.publicKey }).to(equal([Network.SOGS.defaultServerPublicKey])) expect(openGroups?.map { $0.isActive }).to(equal([false])) expect(openGroups?.map { $0.name }).to(equal([""])) } @@ -206,9 +211,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { mockStorage.write { db in try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestExisting", userCount: 0, @@ -228,9 +233,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { let openGroups: [OpenGroup]? = mockStorage.read { db in try OpenGroup.fetchAll(db) } expect(openGroups?.count).to(equal(1)) - expect(openGroups?.map { $0.server }).to(equal([OpenGroupAPI.defaultServer])) + expect(openGroups?.map { $0.server }).to(equal([Network.SOGS.defaultServer])) expect(openGroups?.map { $0.roomToken }).to(equal([""])) - expect(openGroups?.map { $0.publicKey }).to(equal([OpenGroupAPI.defaultServerPublicKey])) + expect(openGroups?.map { $0.publicKey }).to(equal([Network.SOGS.defaultServerPublicKey])) expect(openGroups?.map { $0.isActive }).to(equal([false])) expect(openGroups?.map { $0.name }).to(equal(["TestExisting"])) } @@ -239,9 +244,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { it("sends the correct request") { mockStorage.write { db in try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestExisting", userCount: 0, @@ -249,13 +254,13 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { ) .insert(db) } - let expectedRequest: Network.PreparedRequest! = mockStorage.read { db in - try OpenGroupAPI.preparedCapabilitiesAndRooms( + let expectedRequest: Network.PreparedRequest! = mockStorage.read { db in + try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: Authentication.community( info: LibSession.OpenGroupCapabilityInfo( roomToken: "", - server: OpenGroupAPI.defaultServer, - publicKey: OpenGroupAPI.defaultServerPublicKey, + server: Network.SOGS.defaultServer, + publicKey: Network.SOGS.defaultServerPublicKey, capabilities: [] ), forceBlinded: false @@ -322,7 +327,7 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { let capabilities: [Capability]? = mockStorage.read { db in try Capability.fetchAll(db) } expect(capabilities?.count).to(equal(2)) expect(capabilities?.map { $0.openGroupServer }) - .to(equal([OpenGroupAPI.defaultServer, OpenGroupAPI.defaultServer])) + .to(equal([Network.SOGS.defaultServer, Network.SOGS.defaultServer])) expect(capabilities?.map { $0.variant }).to(equal([.blind, .reactions])) expect(capabilities?.map { $0.isMissing }).to(equal([false, false])) } @@ -341,13 +346,13 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { let openGroups: [OpenGroup]? = mockStorage.read { db in try OpenGroup.fetchAll(db) } expect(openGroups?.count).to(equal(3)) // 1 for the entry used to fetch the default rooms expect(openGroups?.map { $0.server }) - .to(equal([OpenGroupAPI.defaultServer, OpenGroupAPI.defaultServer, OpenGroupAPI.defaultServer])) + .to(equal([Network.SOGS.defaultServer, Network.SOGS.defaultServer, Network.SOGS.defaultServer])) expect(openGroups?.map { $0.roomToken }).to(equal(["", "testRoom", "testRoom2"])) expect(openGroups?.map { $0.publicKey }) .to(equal([ - OpenGroupAPI.defaultServerPublicKey, - OpenGroupAPI.defaultServerPublicKey, - OpenGroupAPI.defaultServerPublicKey + Network.SOGS.defaultServerPublicKey, + Network.SOGS.defaultServerPublicKey, + Network.SOGS.defaultServerPublicKey ])) expect(openGroups?.map { $0.isActive }).to(equal([false, false, false])) expect(openGroups?.map { $0.name }).to(equal(["", "TestRoomName", "TestRoomName2"])) @@ -357,9 +362,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { it("does not override existing rooms that were returned") { mockStorage.write { db in try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestExisting", userCount: 0, @@ -372,15 +377,15 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { .thenReturn( MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), ( - OpenGroupAPI.Endpoint.rooms, + Network.SOGS.Endpoint.rooms, try! JSONEncoder().with(outputFormatting: .sortedKeys).encode( Network.BatchSubResponse( code: 200, headers: [:], body: [ - OpenGroupAPI.Room.mock.with( + Network.SOGS.Room.mock.with( token: "testRoom", name: "TestReplacementName" ) @@ -405,10 +410,10 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { let openGroups: [OpenGroup]? = mockStorage.read { db in try OpenGroup.fetchAll(db) } expect(openGroups?.count).to(equal(2)) // 1 for the entry used to fetch the default rooms expect(openGroups?.map { $0.server }) - .to(equal([OpenGroupAPI.defaultServer, OpenGroupAPI.defaultServer])) + .to(equal([Network.SOGS.defaultServer, Network.SOGS.defaultServer])) expect(openGroups?.map { $0.roomToken }.sorted()).to(equal(["", "testRoom"])) expect(openGroups?.map { $0.publicKey }) - .to(equal([OpenGroupAPI.defaultServerPublicKey, OpenGroupAPI.defaultServerPublicKey])) + .to(equal([Network.SOGS.defaultServerPublicKey, Network.SOGS.defaultServerPublicKey])) expect(openGroups?.map { $0.isActive }).to(equal([false, false])) expect(openGroups?.map { $0.name }.sorted()).to(equal(["", "TestExisting"])) } @@ -435,7 +440,7 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { target: .community( imageId: "12", roomToken: "testRoom2", - server: OpenGroupAPI.defaultServer + server: Network.SOGS.defaultServer ), timestamp: 1234567890 ) @@ -450,9 +455,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { it("schedules a display picture download if the imageId has changed") { mockStorage.write { db in try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom2", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestExisting", imageId: "10", @@ -482,7 +487,7 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { target: .community( imageId: "12", roomToken: "testRoom2", - server: OpenGroupAPI.defaultServer + server: Network.SOGS.defaultServer ), timestamp: 1234567890 ) @@ -501,17 +506,22 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { MockNetwork.batchResponseData( with: [ ( - OpenGroupAPI.Endpoint.capabilities, - OpenGroupAPI.Capabilities(capabilities: [.blind, .reactions]).batchSubResponse() + Network.SOGS.Endpoint.capabilities, + Network.SOGS.CapabilitiesResponse( + capabilities: [ + Capability.Variant.blind.rawValue, + Capability.Variant.reactions.rawValue + ] + ).batchSubResponse() ), ( - OpenGroupAPI.Endpoint.rooms, + Network.SOGS.Endpoint.rooms, [ - OpenGroupAPI.Room.mock.with( + Network.SOGS.Room.mock.with( token: "testRoom", name: "TestRoomName" ), - OpenGroupAPI.Room.mock.with( + Network.SOGS.Room.mock.with( token: "testRoom2", name: "TestRoomName2" ) @@ -538,9 +548,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { it("does not schedule a display picture download if the imageId matches and the image has already been downloaded") { mockStorage.write { db in try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom2", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestExisting", imageId: "12", @@ -579,14 +589,14 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { .toNot(call(matchingParameters: .all) { $0.setDefaultRoomInfo([ ( - room: OpenGroupAPI.Room.mock.with( + room: Network.SOGS.Room.mock.with( token: "testRoom", name: "TestRoomName" ), openGroup: OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestRoomName", userCount: 0, @@ -594,16 +604,16 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { ) ), ( - room: OpenGroupAPI.Room.mock.with( + room: Network.SOGS.Room.mock.with( token: "testRoom2", name: "TestRoomName2", infoUpdates: 12, imageId: "12" ), openGroup: OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom2", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestRoomName2", imageId: "12", diff --git a/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift b/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift index 309aa452d8..209274b6fd 100644 --- a/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift +++ b/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift @@ -882,7 +882,7 @@ class LibSessionGroupInfoSpec: QuickSpec { ) } - let expectedRequest: Network.PreparedRequest<[String: Bool]> = try SnodeAPI.preparedDeleteMessages( + let expectedRequest: Network.PreparedRequest<[String: Bool]> = try Network.SnodeAPI.preparedDeleteMessages( serverHashes: ["1234"], requireSuccessfulDeletion: false, authMethod: Authentication.groupAdmin( diff --git a/SessionMessagingKitTests/Open Groups/Crypto/CryptoOpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/Crypto/CryptoOpenGroupSpec.swift similarity index 61% rename from SessionMessagingKitTests/Open Groups/Crypto/CryptoOpenGroupAPISpec.swift rename to SessionMessagingKitTests/Open Groups/Crypto/CryptoOpenGroupSpec.swift index 35b3ba1679..7b04fa35a1 100644 --- a/SessionMessagingKitTests/Open Groups/Crypto/CryptoOpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/Crypto/CryptoOpenGroupSpec.swift @@ -8,7 +8,7 @@ import Nimble @testable import SessionMessagingKit -class CryptoOpenGroupAPISpec: QuickSpec { +class CryptoOpenGroupSpec: QuickSpec { override class func spec() { // MARK: Configuration @@ -21,161 +21,8 @@ class CryptoOpenGroupAPISpec: QuickSpec { } ) - // MARK: - Crypto for OpenGroupAPI - describe("Crypto for OpenGroupAPI") { - // MARK: -- when generating a blinded15 key pair - context("when generating a blinded15 key pair") { - // MARK: ---- successfully generates - it("successfully generates") { - let result = crypto.generate( - .blinded15KeyPair( - serverPublicKey: TestConstants.serverPublicKey, - ed25519SecretKey: Data(hex: TestConstants.edSecretKey).bytes - ) - ) - - // Note: The first 64 characters of the secretKey are consistent but the chars after that always differ - expect(result?.publicKey.toHexString()).to(equal(TestConstants.blind15PublicKey)) - expect(result?.secretKey.toHexString()).to(equal(TestConstants.blind15SecretKey)) - } - - // MARK: ---- fails if the edKeyPair secret key length wrong - it("fails if the ed25519SecretKey length wrong") { - let result = crypto.generate( - .blinded15KeyPair( - serverPublicKey: TestConstants.serverPublicKey, - ed25519SecretKey: Array(Data(hex: String(TestConstants.edSecretKey.prefix(4)))) - ) - ) - - expect(result).to(beNil()) - } - } - - // MARK: -- when generating a blinded25 key pair - context("when generating a blinded25 key pair") { - // MARK: ---- successfully generates - it("successfully generates") { - let result = crypto.generate( - .blinded25KeyPair( - serverPublicKey: TestConstants.serverPublicKey, - ed25519SecretKey: Data(hex: TestConstants.edSecretKey).bytes - ) - ) - - // Note: The first 64 characters of the secretKey are consistent but the chars after that always differ - expect(result?.publicKey.toHexString()).to(equal(TestConstants.blind25PublicKey)) - expect(result?.secretKey.toHexString()).to(equal(TestConstants.blind25SecretKey)) - } - - // MARK: ---- fails if the edKeyPair secret key length wrong - it("fails if the ed25519SecretKey length wrong") { - let result = crypto.generate( - .blinded25KeyPair( - serverPublicKey: TestConstants.serverPublicKey, - ed25519SecretKey: Data(hex: String(TestConstants.edSecretKey.prefix(4))).bytes - ) - ) - - expect(result).to(beNil()) - } - } - - // MARK: -- when generating a signatureBlind15 - context("when generating a signatureBlind15") { - // MARK: ---- generates a correct signature - it("generates a correct signature") { - let result = crypto.generate( - .signatureBlind15( - message: "TestMessage".bytes, - serverPublicKey: TestConstants.serverPublicKey, - ed25519SecretKey: Array(Data(hex: TestConstants.edSecretKey)) - ) - ) - - expect(result?.toHexString()) - .to(equal( - "245003f1627ebdfc6099c32597d426ef84d1b301861a5ffbbac92dde6c608334" + - "ceb56a022a094a9a664fae034b50eed40bd1bfb262c7e542c979eec265ae3f07" - )) - } - } - - // MARK: -- when generating a signatureBlind25 - context("when generating a signatureBlind25") { - // MARK: ---- generates a correct signature - it("generates a correct signature") { - let result = crypto.generate( - .signatureBlind25( - message: "TestMessage".bytes, - serverPublicKey: TestConstants.serverPublicKey, - ed25519SecretKey: Data(hex: TestConstants.edSecretKey).bytes - ) - ) - - expect(result?.toHexString()) - .to(equal( - "9ff9b7fb7d435c7a2c0b0b2ae64963baaf394386b9f7c7f924eeac44ec0f74c7" + - "fe6304c73a9b3a65491f81e44b545e54631e83e9a412eaed5fd4db2e05ec830c" - )) - } - } - - // MARK: -- when checking if a session id matches a blinded id - context("when checking if a session id matches a blinded id") { - // MARK: ---- returns true when a blind15 id matches - it("returns true when a blind15 id matches") { - let result = crypto.verify( - .sessionId( - "05\(TestConstants.publicKey)", - matchesBlindedId: "15\(TestConstants.blind15PublicKey)", - serverPublicKey: TestConstants.serverPublicKey - ) - ) - - expect(result).to(beTrue()) - } - - // MARK: ---- returns true when a blind25 id matches - it("returns true when a blind25 id matches") { - let result = crypto.verify( - .sessionId( - "05\(TestConstants.publicKey)", - matchesBlindedId: "25\(TestConstants.blind25PublicKey)", - serverPublicKey: TestConstants.serverPublicKey - ) - ) - - expect(result).to(beTrue()) - } - - // MARK: ---- returns false if given an invalid session id - it("returns false if given an invalid session id") { - let result = crypto.verify( - .sessionId( - "AB\(TestConstants.publicKey)", - matchesBlindedId: "15\(TestConstants.blind15PublicKey)", - serverPublicKey: TestConstants.serverPublicKey - ) - ) - - expect(result).to(beFalse()) - } - - // MARK: ---- returns false if given an invalid blinded id - it("returns false if given an invalid blinded id") { - let result = crypto.verify( - .sessionId( - "05\(TestConstants.publicKey)", - matchesBlindedId: "AB\(TestConstants.blind15PublicKey)", - serverPublicKey: TestConstants.serverPublicKey - ) - ) - - expect(result).to(beFalse()) - } - } - + // MARK: - Crypto for Open Group + describe("Crypto for Open Group") { // MARK: -- when encrypting with the session blinding protocol context("when encrypting with the session blinding protocol") { // MARK: ---- can encrypt for a blind15 recipient correctly diff --git a/SessionMessagingKitTests/Open Groups/Models/CapabilitiesSpec.swift b/SessionMessagingKitTests/Open Groups/Models/CapabilitySpec.swift similarity index 73% rename from SessionMessagingKitTests/Open Groups/Models/CapabilitiesSpec.swift rename to SessionMessagingKitTests/Open Groups/Models/CapabilitySpec.swift index 0fc98cf1a8..a3a918bcff 100644 --- a/SessionMessagingKitTests/Open Groups/Models/CapabilitiesSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/CapabilitySpec.swift @@ -7,34 +7,8 @@ import Nimble @testable import SessionMessagingKit -class CapabilitiesSpec: QuickSpec { +class CapabilitySpec: QuickSpec { override class func spec() { - // MARK: - Capabilities - describe("Capabilities") { - // MARK: -- when initializing - context("when initializing") { - // MARK: ---- assigns values correctly - it("assigns values correctly") { - let capabilities: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities( - capabilities: [.sogs], - missing: [.sogs] - ) - - expect(capabilities.capabilities).to(equal([.sogs])) - expect(capabilities.missing).to(equal([.sogs])) - } - - it("defaults missing to nil") { - let capabilities: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities( - capabilities: [.sogs] - ) - - expect(capabilities.capabilities).to(equal([.sogs])) - expect(capabilities.missing).to(beNil()) - } - } - } - // MARK: - a Capability describe("a Capability") { // MARK: -- when initializing diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index 2cd025dc58..f0933e8d92 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -4,13 +4,13 @@ import UIKit import Combine import GRDB import SessionUtil -import SessionNetworkingKit import SessionUtilitiesKit import Quick import Nimble @testable import SessionMessagingKit +@testable import SessionNetworkingKit class OpenGroupManagerSpec: QuickSpec { override class func spec() { @@ -61,12 +61,12 @@ class OpenGroupManagerSpec: QuickSpec { infoUpdates: 10, sequenceNumber: 5 ) - @TestState var testPollInfo: OpenGroupAPI.RoomPollInfo! = OpenGroupAPI.RoomPollInfo.mock.with( + @TestState var testPollInfo: Network.SOGS.RoomPollInfo! = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, details: .mock ) - @TestState var testMessage: OpenGroupAPI.Message! = OpenGroupAPI.Message( + @TestState var testMessage: Network.SOGS.Message! = Network.SOGS.Message( id: 127, sender: "05\(TestConstants.publicKey)", posted: 123, @@ -92,14 +92,14 @@ class OpenGroupManagerSpec: QuickSpec { base64EncodedSignature: nil, reactions: nil ) - @TestState var testDirectMessage: OpenGroupAPI.DirectMessage! = { + @TestState var testDirectMessage: Network.SOGS.DirectMessage! = { let proto = SNProtoContent.builder() let protoDataBuilder = SNProtoDataMessage.builder() proto.setSigTimestamp(1234567890000) protoDataBuilder.setBody("TestMessage") proto.setDataMessage(try! protoDataBuilder.build()) - return OpenGroupAPI.DirectMessage( + return Network.SOGS.DirectMessage( id: 128, sender: "15\(TestConstants.blind15PublicKey)", recipient: "15\(TestConstants.blind15PublicKey)", @@ -969,7 +969,7 @@ class OpenGroupManagerSpec: QuickSpec { mockStorage.write { db in try OpenGroup.deleteAll(db) try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom", publicKey: TestConstants.publicKey, isActive: true, @@ -983,7 +983,7 @@ class OpenGroupManagerSpec: QuickSpec { outboxLatestMessageId: 0 ).insert(db) try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom1", publicKey: TestConstants.publicKey, isActive: true, @@ -1004,7 +1004,7 @@ class OpenGroupManagerSpec: QuickSpec { mockStorage.write { db in try openGroupManager.delete( db, - openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: OpenGroupAPI.defaultServer), + openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: Network.SOGS.defaultServer), skipLibSessionUpdate: true ) } @@ -1018,7 +1018,7 @@ class OpenGroupManagerSpec: QuickSpec { mockStorage.write { db in try openGroupManager.delete( db, - openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: OpenGroupAPI.defaultServer), + openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: Network.SOGS.defaultServer), skipLibSessionUpdate: true ) } @@ -1027,7 +1027,7 @@ class OpenGroupManagerSpec: QuickSpec { mockStorage.read { db in try OpenGroup .select(.isActive) - .filter(id: OpenGroup.idFor(roomToken: "testRoom", server: OpenGroupAPI.defaultServer)) + .filter(id: OpenGroup.idFor(roomToken: "testRoom", server: Network.SOGS.defaultServer)) .asRequest(of: Bool.self) .fetchOne(db) } @@ -1043,7 +1043,10 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager .handleCapabilities( db, - capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: []), + capabilities: Network.SOGS.CapabilitiesResponse( + capabilities: ["sogs"], + missing: [] + ), on: "http://127.0.0.1" ) } @@ -1180,10 +1183,10 @@ class OpenGroupManagerSpec: QuickSpec { context("and updating the moderator list") { // MARK: ------ successfully updates it("successfully updates") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, - details: OpenGroupAPI.Room.mock.with( + details: Network.SOGS.Room.mock.with( moderators: ["TestMod"], hiddenModerators: [], admins: [], @@ -1227,10 +1230,10 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ------ updates for hidden moderators it("updates for hidden moderators") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, - details: OpenGroupAPI.Room.mock.with( + details: Network.SOGS.Room.mock.with( moderators: [], hiddenModerators: ["TestMod2"], admins: [], @@ -1274,7 +1277,7 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ------ does not insert mods if no moderators are provided it("does not insert mods if no moderators are provided") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10 ) @@ -1299,10 +1302,10 @@ class OpenGroupManagerSpec: QuickSpec { context("and updating the admin list") { // MARK: ------ successfully updates it("successfully updates") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, - details: OpenGroupAPI.Room.mock.with( + details: Network.SOGS.Room.mock.with( moderators: [], hiddenModerators: [], admins: ["TestAdmin"], @@ -1346,10 +1349,10 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ------ updates for hidden admins it("updates for hidden admins") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, - details: OpenGroupAPI.Room.mock.with( + details: Network.SOGS.Room.mock.with( moderators: [], hiddenModerators: [], admins: [], @@ -1393,7 +1396,7 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ------ does not insert an admin if no admins are provided it("does not insert an admin if no admins are provided") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, details: nil @@ -1475,10 +1478,10 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ------ schedules a download for the room image it("schedules a download for the room image") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, - details: OpenGroupAPI.Room.mock.with( + details: Network.SOGS.Room.mock.with( token: "test", name: "test", imageId: "10" @@ -1543,7 +1546,7 @@ class OpenGroupManagerSpec: QuickSpec { ).insert(db) } - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, details: nil @@ -1596,10 +1599,10 @@ class OpenGroupManagerSpec: QuickSpec { ).insert(db) } - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, - details: OpenGroupAPI.Room.mock.with( + details: Network.SOGS.Room.mock.with( token: "test", name: "test", infoUpdates: 10, @@ -1700,7 +1703,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleMessages( db, messages: [ - OpenGroupAPI.Message( + Network.SOGS.Message( id: 1, sender: nil, posted: 123, @@ -1763,7 +1766,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleMessages( db, messages: [ - OpenGroupAPI.Message( + Network.SOGS.Message( id: 1, sender: nil, posted: 123, @@ -1797,7 +1800,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleMessages( db, messages: [ - OpenGroupAPI.Message( + Network.SOGS.Message( id: 1, sender: "05\(TestConstants.publicKey)", posted: 123, @@ -1842,7 +1845,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleMessages( db, messages: [ - OpenGroupAPI.Message( + Network.SOGS.Message( id: 2, sender: "05\(TestConstants.publicKey)", posted: 122, @@ -1883,7 +1886,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleMessages( db, messages: [ - OpenGroupAPI.Message( + Network.SOGS.Message( id: 127, sender: "05\(TestConstants.publicKey)", posted: 123, @@ -1913,7 +1916,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleMessages( db, messages: [ - OpenGroupAPI.Message( + Network.SOGS.Message( id: 127, sender: "05\(TestConstants.publicKey)", posted: 123, @@ -2030,7 +2033,7 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ---- ignores messages with non base64 encoded data it("ignores messages with non base64 encoded data") { - testDirectMessage = OpenGroupAPI.DirectMessage( + testDirectMessage = Network.SOGS.DirectMessage( id: testDirectMessage.id, sender: testDirectMessage.sender.replacingOccurrences(of: "8", with: "9"), recipient: testDirectMessage.recipient, @@ -2134,7 +2137,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleDirectMessages( db, messages: [ - OpenGroupAPI.DirectMessage( + Network.SOGS.DirectMessage( id: testDirectMessage.id, sender: testDirectMessage.sender.replacingOccurrences(of: "8", with: "9"), recipient: testDirectMessage.recipient, @@ -2290,7 +2293,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleDirectMessages( db, messages: [ - OpenGroupAPI.DirectMessage( + Network.SOGS.DirectMessage( id: testDirectMessage.id, sender: testDirectMessage.sender.replacingOccurrences(of: "8", with: "9"), recipient: testDirectMessage.recipient, @@ -2526,9 +2529,9 @@ class OpenGroupManagerSpec: QuickSpec { .thenReturn(true) mockStorage.write { db in try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestExisting", userCount: 0, @@ -2536,13 +2539,13 @@ class OpenGroupManagerSpec: QuickSpec { ) .insert(db) } - let expectedRequest: Network.PreparedRequest! = mockStorage.read { db in - try OpenGroupAPI.preparedCapabilitiesAndRooms( + let expectedRequest: Network.PreparedRequest! = mockStorage.read { db in + try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: Authentication.community( info: LibSession.OpenGroupCapabilityInfo( roomToken: "", - server: OpenGroupAPI.defaultServer, - publicKey: OpenGroupAPI.defaultServerPublicKey, + server: Network.SOGS.defaultServer, + publicKey: Network.SOGS.defaultServerPublicKey, capabilities: [] ), forceBlinded: false @@ -2566,7 +2569,7 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ---- does not start a job to retrieve the default rooms if we already have rooms it("does not start a job to retrieve the default rooms if we already have rooms") { mockAppGroupDefaults.when { $0.bool(forKey: UserDefaults.BoolKey.isMainAppActive.rawValue) }.thenReturn(true) - cache.setDefaultRoomInfo([(room: OpenGroupAPI.Room.mock, openGroup: OpenGroup.mock)]) + cache.setDefaultRoomInfo([(room: Network.SOGS.Room.mock, openGroup: OpenGroup.mock)]) cache.defaultRoomsPublisher.sinkUntilComplete() expect(mockNetwork) @@ -2579,7 +2582,7 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: - Convenience Extensions -extension OpenGroupAPI.Room { +extension Network.SOGS.Room { func with( token: String? = nil, name: String? = nil, @@ -2589,8 +2592,8 @@ extension OpenGroupAPI.Room { hiddenModerators: [String]? = nil, admins: [String]? = nil, hiddenAdmins: [String]? = nil - ) -> OpenGroupAPI.Room { - return OpenGroupAPI.Room( + ) -> Network.SOGS.Room { + return Network.SOGS.Room( token: (token ?? self.token), name: (name ?? self.name), roomDescription: self.roomDescription, @@ -2620,13 +2623,13 @@ extension OpenGroupAPI.Room { } } -extension OpenGroupAPI.RoomPollInfo { +extension Network.SOGS.RoomPollInfo { func with( token: String? = nil, activeUsers: Int64? = nil, - details: OpenGroupAPI.Room? = .mock - ) -> OpenGroupAPI.RoomPollInfo { - return OpenGroupAPI.RoomPollInfo( + details: Network.SOGS.Room? = .mock + ) -> Network.SOGS.RoomPollInfo { + return Network.SOGS.RoomPollInfo( token: (token ?? self.token), activeUsers: (activeUsers ?? self.activeUsers), admin: self.admin, @@ -2658,133 +2661,49 @@ extension OpenGroup: Mocked { infoUpdates: 0 ) } - -extension OpenGroupAPI.Capabilities: Mocked { - static var mock: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities(capabilities: [], missing: nil) -} - -extension OpenGroupAPI.Room: Mocked { - static var mock: OpenGroupAPI.Room = OpenGroupAPI.Room( - token: "test", - name: "testRoom", - roomDescription: nil, - infoUpdates: 1, - messageSequence: 1, - created: 1, - activeUsers: 1, - activeUsersCutoff: 1, - imageId: nil, - pinnedMessages: nil, - admin: false, - globalAdmin: false, - admins: [], - hiddenAdmins: nil, - moderator: false, - globalModerator: false, - moderators: [], - hiddenModerators: nil, - read: true, - defaultRead: nil, - defaultAccessible: nil, - write: true, - defaultWrite: nil, - upload: true, - defaultUpload: nil - ) -} - -extension OpenGroupAPI.RoomPollInfo: Mocked { - static var mock: OpenGroupAPI.RoomPollInfo = OpenGroupAPI.RoomPollInfo( - token: "test", - activeUsers: 1, - admin: false, - globalAdmin: false, - moderator: false, - globalModerator: false, - read: true, - defaultRead: nil, - defaultAccessible: nil, - write: true, - defaultWrite: nil, - upload: true, - defaultUpload: false, - details: .mock - ) -} - -extension OpenGroupAPI.Message: Mocked { - static var mock: OpenGroupAPI.Message = OpenGroupAPI.Message( - id: 100, - sender: TestConstants.blind15PublicKey, - posted: 1, - edited: nil, - deleted: nil, - seqNo: 1, - whisper: false, - whisperMods: false, - whisperTo: nil, - base64EncodedData: nil, - base64EncodedSignature: nil, - reactions: nil - ) -} - -extension OpenGroupAPI.SendDirectMessageResponse: Mocked { - static var mock: OpenGroupAPI.SendDirectMessageResponse = OpenGroupAPI.SendDirectMessageResponse( - id: 1, - sender: TestConstants.blind15PublicKey, - recipient: "testRecipient", - posted: 1122, - expires: 2233 - ) -} - -extension OpenGroupAPI.DirectMessage: Mocked { - static var mock: OpenGroupAPI.DirectMessage = OpenGroupAPI.DirectMessage( - id: 101, - sender: TestConstants.blind15PublicKey, - recipient: "testRecipient", - posted: 1212, - expires: 2323, - base64EncodedMessage: "TestMessage".data(using: .utf8)!.base64EncodedString() - ) -} extension Network.BatchResponse { static let mockUnblindedPollResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.roomPollInfo("testRoom", 0), OpenGroupAPI.RoomPollInfo.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.roomMessagesRecent("testRoom"), [OpenGroupAPI.Message].mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.roomPollInfo("testRoom", 0), Network.SOGS.RoomPollInfo.mockBatchSubResponse()), + (Network.SOGS.Endpoint.roomMessagesRecent("testRoom"), [Network.SOGS.Message].mockBatchSubResponse()) ] ) static let mockBlindedPollResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.roomPollInfo("testRoom", 0), OpenGroupAPI.RoomPollInfo.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.roomMessagesRecent("testRoom"), OpenGroupAPI.Message.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.inboxSince(id: 0), OpenGroupAPI.DirectMessage.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.outboxSince(id: 0), OpenGroupAPI.DirectMessage.self.mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.roomPollInfo("testRoom", 0), Network.SOGS.RoomPollInfo.mockBatchSubResponse()), + (Network.SOGS.Endpoint.roomMessagesRecent("testRoom"), Network.SOGS.Message.mockBatchSubResponse()), + (Network.SOGS.Endpoint.inboxSince(id: 0), Network.SOGS.DirectMessage.mockBatchSubResponse()), + (Network.SOGS.Endpoint.outboxSince(id: 0), Network.SOGS.DirectMessage.self.mockBatchSubResponse()) ] ) static let mockCapabilitiesResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()) ] ) static let mockRoomResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Room.mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.Room.mockBatchSubResponse()) ] ) static let mockBanAndDeleteAllResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.roomDeleteMessages("testRoon", sessionId: ""), NoResponse.mockBatchSubResponse()) + (Network.SOGS.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.roomDeleteMessages("testRoon", sessionId: ""), NoResponse.mockBatchSubResponse()) + ] + ) + + static let mockCapabilitiesAndRoomResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( + with: [ + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.room("testRoom"), Network.SOGS.Room.mockBatchSubResponse()) ] ) } diff --git a/SessionMessagingKitTests/Open Groups/Types/SOGSErrorSpec.swift b/SessionMessagingKitTests/Open Groups/Types/SOGSErrorSpec.swift deleted file mode 100644 index 7dba9ab779..0000000000 --- a/SessionMessagingKitTests/Open Groups/Types/SOGSErrorSpec.swift +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -import Quick -import Nimble - -@testable import SessionMessagingKit - -class SOGSErrorSpec: QuickSpec { - override class func spec() { - // MARK: - a SOGSError - describe("a SOGSError") { - // MARK: -- generates the error description correctly - it("generates the error description correctly") { - expect(OpenGroupAPIError.decryptionFailed.description).to(equal("Couldn't decrypt response.")) - expect(OpenGroupAPIError.signingFailed.description).to(equal("Couldn't sign message.")) - expect(OpenGroupAPIError.noPublicKey.description).to(equal("Couldn't find server public key.")) - expect(OpenGroupAPIError.invalidEmoji.description).to(equal("The emoji is invalid.")) - expect(OpenGroupAPIError.invalidPoll.description).to(equal("Poller in invalid state.")) - } - } - } -} diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift index f731e18ecf..e42508adee 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift @@ -138,7 +138,7 @@ class MessageReceiverGroupsSpec: QuickSpec { .thenReturn(Data([1, 2, 3])) keychain .when { try $0.data(forKey: .pushNotificationEncryptionKey) } - .thenReturn(Data((0.. = mockStorage.write { db in + let expectedRequest: Network.PreparedRequest = mockStorage.write { db in _ = try SessionThread.upsert( db, id: groupId.hexString, @@ -856,10 +856,17 @@ class MessageReceiverGroupsSpec: QuickSpec { groupIdentityPrivateKey: groupSecretKey, invited: nil ).upsert(db) - let result = try PushNotificationAPI.preparedSubscribe( - db, + let result = try Network.PushNotification.preparedSubscribe( token: Data([5, 4, 3, 2, 1]), - sessionIds: [groupId], + swarms: [ + ( + groupId, + Authentication.groupAdmin( + groupSessionId: groupId, + ed25519SecretKey: Array(groupSecretKey) + ) + ) + ], using: dependencies ) @@ -910,7 +917,7 @@ class MessageReceiverGroupsSpec: QuickSpec { // MARK: -------- subscribes for push notifications it("subscribes for push notifications") { - let expectedRequest: Network.PreparedRequest = mockStorage.write { db in + let expectedRequest: Network.PreparedRequest = mockStorage.write { db in _ = try SessionThread.upsert( db, id: groupId.hexString, @@ -929,10 +936,17 @@ class MessageReceiverGroupsSpec: QuickSpec { authData: inviteMessage.memberAuthData, invited: nil ).upsert(db) - let result = try PushNotificationAPI.preparedSubscribe( - db, + let result = try Network.PushNotification.preparedSubscribe( token: Data(hex: Data([5, 4, 3, 2, 1]).toHexString()), - sessionIds: [groupId], + swarms: [ + ( + groupId, + Authentication.groupMember( + groupSessionId: groupId, + authData: inviteMessage.memberAuthData + ) + ) + ], using: dependencies ) @@ -2821,7 +2835,7 @@ class MessageReceiverGroupsSpec: QuickSpec { deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111112" deleteContentMessage.sentTimestampMs = 1234567800000 - let preparedRequest: Network.PreparedRequest<[String: Bool]> = try! SnodeAPI + let preparedRequest: Network.PreparedRequest<[String: Bool]> = try! Network.SnodeAPI .preparedDeleteMessages( serverHashes: ["TestMessageHash3"], requireSuccessfulDeletion: false, @@ -3092,11 +3106,18 @@ class MessageReceiverGroupsSpec: QuickSpec { .when { $0.bool(forKey: UserDefaults.BoolKey.isUsingFullAPNs.rawValue) } .thenReturn(true) - let expectedRequest: Network.PreparedRequest = mockStorage.read { db in - try PushNotificationAPI.preparedUnsubscribe( - db, + let expectedRequest: Network.PreparedRequest = mockStorage.read { db in + try Network.PushNotification.preparedUnsubscribe( token: Data([5, 4, 3, 2, 1]), - sessionIds: [groupId], + swarms: [ + ( + groupId, + Authentication.groupMember( + groupSessionId: groupId, + authData: Data([1, 2, 3]) + ) + ) + ], using: dependencies ) }! diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift index 69d1a6d505..ddebdba938 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift @@ -159,7 +159,7 @@ class MessageSenderGroupsSpec: QuickSpec { .thenReturn(Data([1, 2, 3])) keychain .when { try $0.data(forKey: .pushNotificationEncryptionKey) } - .thenReturn(Data((0.. = try SnodeAPI.preparedSequence( + let preparedRequest: Network.PreparedRequest = try Network.SnodeAPI.preparedSequence( requests: [ - try SnodeAPI + try Network.SnodeAPI .preparedSendMessage( message: SnodeMessage( recipient: groupId.hexString, @@ -731,7 +731,7 @@ class MessageSenderGroupsSpec: QuickSpec { // MARK: ------ and trying to subscribe for push notifications context("and trying to subscribe for push notifications") { - @TestState var expectedRequest: Network.PreparedRequest! + @TestState var expectedRequest: Network.PreparedRequest! beforeEach { // Need to set `isUsingFullAPNs` to true to generate the `expectedRequest` @@ -760,10 +760,17 @@ class MessageSenderGroupsSpec: QuickSpec { groupIdentityPrivateKey: groupSecretKey, invited: nil ).upsert(db) - let result = try PushNotificationAPI.preparedSubscribe( - db, + let result = try Network.PushNotification.preparedSubscribe( token: Data([5, 4, 3, 2, 1]), - sessionIds: [groupId], + swarms: [ + ( + groupId, + Authentication.groupAdmin( + groupSessionId: groupId, + ed25519SecretKey: Array(groupSecretKey) + ) + ) + ], using: dependencies ) @@ -1024,9 +1031,9 @@ class MessageSenderGroupsSpec: QuickSpec { "LPczVOFKOPs+rrB3aUpMsNUnJHOEhW9g6zi/UPjuCWTnnvpxlMTpHaTFlMTp+NjQ6dKi86jZJ" + "l3oiJEA5h5pBE5oOJHQNvtF8GOcsYwrIFTZKnI7AGkBSu1TxP0xLWwTUzjOGMgmKvlIgkQ6e9" + "r3JBmU=" - let expectedRequest: Network.PreparedRequest = try SnodeAPI.preparedSequence( + let expectedRequest: Network.PreparedRequest = try Network.SnodeAPI.preparedSequence( requests: [] - .appending(try SnodeAPI.preparedUnrevokeSubaccounts( + .appending(try Network.SnodeAPI.preparedUnrevokeSubaccounts( subaccountsToUnrevoke: [Array("TestSubAccountToken".data(using: .utf8)!)], authMethod: Authentication.groupAdmin( groupSessionId: groupId, @@ -1034,7 +1041,7 @@ class MessageSenderGroupsSpec: QuickSpec { ), using: dependencies )) - .appending(try SnodeAPI.preparedSendMessage( + .appending(try Network.SnodeAPI.preparedSendMessage( message: SnodeMessage( recipient: groupId.hexString, data: Data(base64Encoded: requestDataString)!, @@ -1048,7 +1055,7 @@ class MessageSenderGroupsSpec: QuickSpec { ), using: dependencies )) - .appending(try SnodeAPI.preparedDeleteMessages( + .appending(try Network.SnodeAPI.preparedDeleteMessages( serverHashes: ["testHash"], requireSuccessfulDeletion: false, authMethod: Authentication.groupAdmin( @@ -1239,9 +1246,9 @@ class MessageSenderGroupsSpec: QuickSpec { // MARK: ---- includes the unrevoke subaccounts as part of the config sync sequence it("includes the unrevoke subaccounts as part of the config sync sequence") { - let expectedRequest: Network.PreparedRequest = try SnodeAPI.preparedSequence( + let expectedRequest: Network.PreparedRequest = try Network.SnodeAPI.preparedSequence( requests: [] - .appending(try SnodeAPI + .appending(try Network.SnodeAPI .preparedUnrevokeSubaccounts( subaccountsToUnrevoke: [Array("TestSubAccountToken".data(using: .utf8)!)], authMethod: Authentication.groupAdmin( @@ -1251,7 +1258,7 @@ class MessageSenderGroupsSpec: QuickSpec { using: dependencies ) ) - .appending(try SnodeAPI.preparedDeleteMessages( + .appending(try Network.SnodeAPI.preparedDeleteMessages( serverHashes: ["testHash"], requireSuccessfulDeletion: false, authMethod: Authentication.groupAdmin( @@ -1472,25 +1479,25 @@ extension Network.BatchResponse { fileprivate static let mockConfigSyncResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), - (SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), - (SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), - (SnodeAPI.Endpoint.deleteMessages, DeleteMessagesResponse.mockBatchSubResponse()) + (Network.SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), + (Network.SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), + (Network.SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), + (Network.SnodeAPI.Endpoint.deleteMessages, DeleteMessagesResponse.mockBatchSubResponse()) ] ) fileprivate static let mockAddMemberConfigSyncResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (SnodeAPI.Endpoint.unrevokeSubaccount, UnrevokeSubaccountResponse.mockBatchSubResponse()), - (SnodeAPI.Endpoint.deleteMessages, DeleteMessagesResponse.mockBatchSubResponse()) + (Network.SnodeAPI.Endpoint.unrevokeSubaccount, UnrevokeSubaccountResponse.mockBatchSubResponse()), + (Network.SnodeAPI.Endpoint.deleteMessages, DeleteMessagesResponse.mockBatchSubResponse()) ] ) fileprivate static let mockAddMemberHistoricConfigSyncResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (SnodeAPI.Endpoint.unrevokeSubaccount, UnrevokeSubaccountResponse.mockBatchSubResponse()), - (SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), - (SnodeAPI.Endpoint.deleteMessages, DeleteMessagesResponse.mockBatchSubResponse()) + (Network.SnodeAPI.Endpoint.unrevokeSubaccount, UnrevokeSubaccountResponse.mockBatchSubResponse()), + (Network.SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), + (Network.SnodeAPI.Endpoint.deleteMessages, DeleteMessagesResponse.mockBatchSubResponse()) ] ) } diff --git a/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift b/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift index a7db4da5de..191d60c4d0 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift @@ -11,7 +11,7 @@ class MockOGMCache: Mock, OGMCacheType { mock() } - var pendingChanges: [OpenGroupAPI.PendingChange] { + var pendingChanges: [OpenGroupManager.PendingChange] { get { return mock() } set { mockNoReturn(args: [newValue]) } } diff --git a/SessionMessagingKitTests/_TestUtilities/MockPoller.swift b/SessionMessagingKitTests/_TestUtilities/MockPoller.swift index 794fa81829..cfc5968bd2 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockPoller.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockPoller.swift @@ -43,7 +43,7 @@ class MockPoller: Mock, PollerType { pollerQueue: DispatchQueue, pollerDestination: PollerDestination, pollerDrainBehaviour: ThreadSafeObject, - namespaces: [SnodeAPI.Namespace], + namespaces: [Network.SnodeAPI.Namespace], failureCount: Int, shouldStoreMessages: Bool, logStartAndStopCalls: Bool, diff --git a/SessionMessagingKitTests/_TestUtilities/MockSwarmPoller.swift b/SessionMessagingKitTests/_TestUtilities/MockSwarmPoller.swift index d20f1da875..05af305032 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockSwarmPoller.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockSwarmPoller.swift @@ -39,7 +39,7 @@ class MockSwarmPoller: Mock, SwarmPollerType & Pol pollerQueue: DispatchQueue, pollerDestination: PollerDestination, pollerDrainBehaviour: ThreadSafeObject, - namespaces: [SnodeAPI.Namespace], + namespaces: [Network.SnodeAPI.Namespace], failureCount: Int, shouldStoreMessages: Bool, logStartAndStopCalls: Bool, diff --git a/SessionNetworkingKit/Crypto/Crypto+SessionNetworkingKit.swift b/SessionNetworkingKit/Crypto/Crypto+SessionNetworkingKit.swift index 94fed80795..dad09fce75 100644 --- a/SessionNetworkingKit/Crypto/Crypto+SessionNetworkingKit.swift +++ b/SessionNetworkingKit/Crypto/Crypto+SessionNetworkingKit.swift @@ -11,7 +11,7 @@ import SessionUtilitiesKit internal extension Crypto.Generator { static func sessionId( name: String, - response: SnodeAPI.ONSResolveResponse + response: Network.SnodeAPI.ONSResolveResponse ) -> Crypto.Generator { return Crypto.Generator( id: "sessionId_for_ONS_response", @@ -59,84 +59,3 @@ internal extension Crypto.Generator { } } -// MARK: - Version Blinded ID - -public extension Crypto.Generator { - static func versionBlinded07KeyPair( - ed25519SecretKey: [UInt8] - ) -> Crypto.Generator { - return Crypto.Generator( - id: "versionBlinded07KeyPair", - args: [ed25519SecretKey] - ) { - var cEd25519SecretKey: [UInt8] = Array(ed25519SecretKey) - var cBlindedPubkey: [UInt8] = [UInt8](repeating: 0, count: 32) - var cBlindedSeckey: [UInt8] = [UInt8](repeating: 0, count: 64) - - guard - cEd25519SecretKey.count == 64, - session_blind_version_key_pair( - &cEd25519SecretKey, - &cBlindedPubkey, - &cBlindedSeckey - ) - else { throw CryptoError.keyGenerationFailed } - - return KeyPair(publicKey: cBlindedPubkey, secretKey: cBlindedSeckey) - } - } - - static func signatureVersionBlind07( - timestamp: UInt64, - method: String, - path: String, - body: String?, - ed25519SecretKey: [UInt8] - ) -> Crypto.Generator<[UInt8]> { - return Crypto.Generator( - id: "signatureVersionBlind07", - args: [timestamp, method, path, body, ed25519SecretKey] - ) { - var cEd25519SecretKey: [UInt8] = Array(ed25519SecretKey) - guard - cEd25519SecretKey.count == 64, - var cMethod: [CChar] = method.cString(using: .utf8), - var cPath: [CChar] = path.cString(using: .utf8) - else { - throw CryptoError.signatureGenerationFailed - } - - var cSignature: [UInt8] = [UInt8](repeating: 0, count: 64) - - if let body: String = body { - var cBody: [UInt8] = Array(body.bytes) - guard session_blind_version_sign_request( - &cEd25519SecretKey, - timestamp, - &cMethod, - &cPath, - &cBody, - cBody.count, - &cSignature - ) else { - throw CryptoError.signatureGenerationFailed - } - } else { - guard session_blind_version_sign_request( - &cEd25519SecretKey, - timestamp, - &cMethod, - &cPath, - nil, - 0, - &cSignature - ) - else { - throw CryptoError.signatureGenerationFailed - } - } - - return cSignature - } - } -} diff --git a/SessionNetworkingKit/Database/Models/SnodeReceivedMessageInfo.swift b/SessionNetworkingKit/Database/Models/SnodeReceivedMessageInfo.swift deleted file mode 100644 index a54cbc083f..0000000000 --- a/SessionNetworkingKit/Database/Models/SnodeReceivedMessageInfo.swift +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -// -// stringlint:disable - -import Foundation -import GRDB -import SessionUtilitiesKit - -public struct SnodeReceivedMessageInfo: Codable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible { - public static var databaseTableName: String { "snodeReceivedMessageInfo" } - - public typealias Columns = CodingKeys - public enum CodingKeys: String, CodingKey, ColumnExpression { - case swarmPublicKey - case snodeAddress - case namespace - case hash - case expirationDateMs - case wasDeletedOrInvalid - } - - /// The public key for the swarm this message info was retrieved from - public let swarmPublicKey: String - - /// The address for the snode this message info was retrieved from (in the form of `{server}:{port}`) - public let snodeAddress: String - - /// The namespace this message info was retrieved from - public let namespace: Int - - /// The is the hash for the received message - public let hash: String - - /// This is the timestamp (in milliseconds since epoch) when the message hash should expire - /// - /// **Note:** If no value exists this will default to 15 days from now (since the service node caches messages for - /// 14 days for standard messages) - public let expirationDateMs: Int64 - - /// This flag indicates whether the message associated with this message hash was deleted or whether this message - /// hash is potentially invalid (if a poll results in 100% of the `SnodeReceivedMessageInfo` entries being seen as - /// duplicates then we assume that the `lastHash` value provided when retrieving messages was invalid and mark - /// it as such) - /// - /// This flag can also be used to refetch messages from a swarm without impacting the hash-based deduping mechanism - /// as if a hash with this value set to `true` is received when pollig then the value gets reset to `false` - /// - /// **Note:** When retrieving the `lastNotExpired` we will ignore any entries where this flag is `true` - public var wasDeletedOrInvalid: Bool -} - -// MARK: - Convenience - -public extension SnodeReceivedMessageInfo { - init( - snode: LibSession.Snode, - swarmPublicKey: String, - namespace: SnodeAPI.Namespace, - hash: String, - expirationDateMs: Int64? - ) { - self.swarmPublicKey = swarmPublicKey - self.snodeAddress = snode.address - self.namespace = namespace.rawValue - self.hash = hash - self.expirationDateMs = (expirationDateMs ?? 0) - self.wasDeletedOrInvalid = false - } -} - -// MARK: - GRDB Interactions - -public extension SnodeReceivedMessageInfo { - /// This method fetches the last non-expired hash from the database for message retrieval - static func fetchLastNotExpired( - _ db: ObservingDatabase, - for snode: LibSession.Snode, - namespace: SnodeAPI.Namespace, - swarmPublicKey: String, - using dependencies: Dependencies - ) throws -> SnodeReceivedMessageInfo? { - let currentOffsetTimestampMs: Int64 = dependencies[cache: .snodeAPI].currentOffsetTimestampMs() - - return try SnodeReceivedMessageInfo - .filter(SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == false) - .filter( - SnodeReceivedMessageInfo.Columns.swarmPublicKey == swarmPublicKey && - SnodeReceivedMessageInfo.Columns.snodeAddress == snode.address && - SnodeReceivedMessageInfo.Columns.namespace == namespace.rawValue - ) - .filter(SnodeReceivedMessageInfo.Columns.expirationDateMs > currentOffsetTimestampMs) - .order(Column.rowID.desc) - .fetchOne(db) - } - - /// There are some cases where the latest message can be removed from a swarm, if we then try to poll for that message the swarm - /// will see it as invalid and start returning messages from the beginning which can result in a lot of wasted, duplicate downloads - /// - /// This method should be called when deleting a message, handling an UnsendRequest or when receiving a poll response which contains - /// solely duplicate messages (for the specific service node - if even one message in a response is new for that service node then this shouldn't - /// be called if if the message has already been received and processed by a separate service node) - static func handlePotentialDeletedOrInvalidHash( - _ db: ObservingDatabase, - potentiallyInvalidHashes: [String], - otherKnownValidHashes: [String] = [] - ) throws { - if !potentiallyInvalidHashes.isEmpty { - _ = try SnodeReceivedMessageInfo - .filter(potentiallyInvalidHashes.contains(SnodeReceivedMessageInfo.Columns.hash)) - .updateAll( - db, - SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid.set(to: true) - ) - } - - // If we have any server hashes which we know are valid (eg. we fetched the oldest messages) then - // mark them all as valid to prevent the case where we just slowly work backwards from the latest - // message, polling for one earlier each time - if !otherKnownValidHashes.isEmpty { - _ = try SnodeReceivedMessageInfo - .filter(otherKnownValidHashes.contains(SnodeReceivedMessageInfo.Columns.hash)) - .updateAll( - db, - SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid.set(to: false) - ) - } - } - - func storeUpdatedLastHash(_ db: ObservingDatabase) -> Bool { - do { - _ = try self.inserted(db) - return true - } - catch { return false } - } -} diff --git a/SessionNetworkingKit/FileServer/AppVersionResponse.swift b/SessionNetworkingKit/FileServer/AppVersionResponse.swift deleted file mode 100644 index ae5c33739b..0000000000 --- a/SessionNetworkingKit/FileServer/AppVersionResponse.swift +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -public class AppVersionResponse: AppVersionInfo { - enum CodingKeys: String, CodingKey { - case prerelease - } - - public let prerelease: AppVersionInfo? - - public init( - version: String, - updated: TimeInterval?, - name: String?, - notes: String?, - assets: [Asset]?, - prerelease: AppVersionInfo? - ) { - self.prerelease = prerelease - - super.init( - version: version, - updated: updated, - name: name, - notes: notes, - assets: assets - ) - } - - required init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - - self.prerelease = try? container.decode(AppVersionInfo?.self, forKey: .prerelease) - - try super.init(from: decoder) - } - - public override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - - var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - try container.encodeIfPresent(prerelease, forKey: .prerelease) - } -} - -// MARK: - AppVersionInfo - -public class AppVersionInfo: Codable { - enum CodingKeys: String, CodingKey { - case version = "result" - case updated - case name - case notes - case assets - } - - public struct Asset: Codable { - enum CodingKeys: String, CodingKey { - case name - case url - } - - public let name: String - public let url: String - } - - public let version: String - public let updated: TimeInterval? - public let name: String? - public let notes: String? - public let assets: [Asset]? - - public init( - version: String, - updated: TimeInterval?, - name: String?, - notes: String?, - assets: [Asset]? - ) { - self.version = version - self.updated = updated - self.name = name - self.notes = notes - self.assets = assets - } - - public func encode(to encoder: Encoder) throws { - var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(version, forKey: .version) - try container.encodeIfPresent(updated, forKey: .updated) - try container.encodeIfPresent(name, forKey: .name) - try container.encodeIfPresent(notes, forKey: .notes) - try container.encodeIfPresent(assets, forKey: .assets) - } -} diff --git a/SessionNetworkingKit/FileServer/Crypto/Crypto+FileServer.swift b/SessionNetworkingKit/FileServer/Crypto/Crypto+FileServer.swift index e69de29bb2..2dced50d25 100644 --- a/SessionNetworkingKit/FileServer/Crypto/Crypto+FileServer.swift +++ b/SessionNetworkingKit/FileServer/Crypto/Crypto+FileServer.swift @@ -0,0 +1,89 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import SessionUtil +import SessionUtilitiesKit + +// MARK: - Version Blinded ID + +public extension Crypto.Generator { + static func versionBlinded07KeyPair( + ed25519SecretKey: [UInt8] + ) -> Crypto.Generator { + return Crypto.Generator( + id: "versionBlinded07KeyPair", + args: [ed25519SecretKey] + ) { + var cEd25519SecretKey: [UInt8] = Array(ed25519SecretKey) + var cBlindedPubkey: [UInt8] = [UInt8](repeating: 0, count: 32) + var cBlindedSeckey: [UInt8] = [UInt8](repeating: 0, count: 64) + + guard + cEd25519SecretKey.count == 64, + session_blind_version_key_pair( + &cEd25519SecretKey, + &cBlindedPubkey, + &cBlindedSeckey + ) + else { throw CryptoError.keyGenerationFailed } + + return KeyPair(publicKey: cBlindedPubkey, secretKey: cBlindedSeckey) + } + } + + static func signatureVersionBlind07( + timestamp: UInt64, + method: String, + path: String, + body: String?, + ed25519SecretKey: [UInt8] + ) -> Crypto.Generator<[UInt8]> { + return Crypto.Generator( + id: "signatureVersionBlind07", + args: [timestamp, method, path, body, ed25519SecretKey] + ) { + var cEd25519SecretKey: [UInt8] = Array(ed25519SecretKey) + guard + cEd25519SecretKey.count == 64, + var cMethod: [CChar] = method.cString(using: .utf8), + var cPath: [CChar] = path.cString(using: .utf8) + else { + throw CryptoError.signatureGenerationFailed + } + + var cSignature: [UInt8] = [UInt8](repeating: 0, count: 64) + + if let body: String = body { + var cBody: [UInt8] = Array(body.bytes) + guard session_blind_version_sign_request( + &cEd25519SecretKey, + timestamp, + &cMethod, + &cPath, + &cBody, + cBody.count, + &cSignature + ) else { + throw CryptoError.signatureGenerationFailed + } + } else { + guard session_blind_version_sign_request( + &cEd25519SecretKey, + timestamp, + &cMethod, + &cPath, + nil, + 0, + &cSignature + ) + else { + throw CryptoError.signatureGenerationFailed + } + } + + return cSignature + } + } +} diff --git a/SessionNetworkingKit/FileServer/FileServer.swift b/SessionNetworkingKit/FileServer/FileServer.swift index 2458124898..90fdec9e08 100644 --- a/SessionNetworkingKit/FileServer/FileServer.swift +++ b/SessionNetworkingKit/FileServer/FileServer.swift @@ -1,34 +1,16 @@ // Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable import Foundation import SessionUtilitiesKit -// MARK: - FileServer Convenience - public extension Network { enum FileServer { - fileprivate static let fileServer = "http://filev2.getsession.org" - fileprivate static let fileServerPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" - fileprivate static let legacyFileServer = "http://88.99.175.227" - fileprivate static let legacyFileServerPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69" - - public enum Endpoint: EndpointType { - case file - case fileIndividual(String) - case directUrl(URL) - case sessionVersion - - public static var name: String { "FileServerAPI.Endpoint" } - - public var path: String { - switch self { - case .file: return "file" - case .fileIndividual(let fileId): return "file/\(fileId)" - case .directUrl(let url): return url.path.removingPrefix("/") - case .sessionVersion: return "session_version" - } - } - } + internal static let fileServer = "http://filev2.getsession.org" + internal static let fileServerPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" + internal static let legacyFileServer = "http://88.99.175.227" + internal static let legacyFileServerPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69" static func fileServerPubkey(url: String? = nil) -> String { switch url?.contains(legacyFileServer) { @@ -54,6 +36,16 @@ public extension Network { public static func downloadUrlString(for fileId: String) -> String { return "\(fileServer)/\(Endpoint.fileIndividual(fileId).path)" } + + public static func fileId(for downloadUrl: String?) -> String? { + return downloadUrl + .map { urlString -> String? in + urlString + .split(separator: "/") // stringlint:ignore + .last + .map { String($0) } + } + } } static func preparedUpload( diff --git a/SessionNetworkingKit/FileServer/FileServerAPI.swift b/SessionNetworkingKit/FileServer/FileServerAPI.swift index e69de29bb2..4b3304b3dd 100644 --- a/SessionNetworkingKit/FileServer/FileServerAPI.swift +++ b/SessionNetworkingKit/FileServer/FileServerAPI.swift @@ -0,0 +1,50 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +private typealias FileServer = Network.FileServer +private typealias Endpoint = Network.FileServer.Endpoint + +public extension Network.FileServer { + static func preparedUpload( + data: Data, + requestAndPathBuildTimeout: TimeInterval? = nil, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try Network.PreparedRequest( + request: Request( + endpoint: .file, + destination: .serverUpload( + server: FileServer.fileServer, + x25519PublicKey: FileServer.fileServerPublicKey, + fileName: nil + ), + body: data + ), + responseType: FileUploadResponse.self, + requestTimeout: Network.fileUploadTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, + using: dependencies + ) + } + + static func preparedDownload( + url: URL, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try Network.PreparedRequest( + request: Request( + endpoint: .directUrl(url), + destination: .serverDownload( + url: url, + x25519PublicKey: FileServer.fileServerPublicKey, + fileName: nil + ) + ), + responseType: Data.self, + requestTimeout: Network.fileUploadTimeout, + using: dependencies + ) + } +} diff --git a/SessionNetworkingKit/FileServer/FileServerEndpoint.swift b/SessionNetworkingKit/FileServer/FileServerEndpoint.swift index e69de29bb2..5f23b85624 100644 --- a/SessionNetworkingKit/FileServer/FileServerEndpoint.swift +++ b/SessionNetworkingKit/FileServer/FileServerEndpoint.swift @@ -0,0 +1,23 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension Network.FileServer { + enum Endpoint: EndpointType { + case file + case fileIndividual(String) + case directUrl(URL) + case sessionVersion + + public static var name: String { "FileServer.Endpoint" } + + public var path: String { + switch self { + case .file: return "file" + case .fileIndividual(let fileId): return "file/\(fileId)" + case .directUrl(let url): return url.path.removingPrefix("/") + case .sessionVersion: return "session_version" + } + } + } +} diff --git a/SessionNetworkingKit/FileServer/Models/AppVersionResponse.swift b/SessionNetworkingKit/FileServer/Models/AppVersionResponse.swift index ae5c33739b..12c1d7fcbb 100644 --- a/SessionNetworkingKit/FileServer/Models/AppVersionResponse.swift +++ b/SessionNetworkingKit/FileServer/Models/AppVersionResponse.swift @@ -2,96 +2,98 @@ import Foundation -public class AppVersionResponse: AppVersionInfo { - enum CodingKeys: String, CodingKey { - case prerelease - } - - public let prerelease: AppVersionInfo? - - public init( - version: String, - updated: TimeInterval?, - name: String?, - notes: String?, - assets: [Asset]?, - prerelease: AppVersionInfo? - ) { - self.prerelease = prerelease +public extension Network.FileServer { + class AppVersionResponse: AppVersionInfo { + enum CodingKeys: String, CodingKey { + case prerelease + } - super.init( - version: version, - updated: updated, - name: name, - notes: notes, - assets: assets - ) - } - - required init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + public let prerelease: AppVersionInfo? - self.prerelease = try? container.decode(AppVersionInfo?.self, forKey: .prerelease) + public init( + version: String, + updated: TimeInterval?, + name: String?, + notes: String?, + assets: [Asset]?, + prerelease: AppVersionInfo? + ) { + self.prerelease = prerelease + + super.init( + version: version, + updated: updated, + name: name, + notes: notes, + assets: assets + ) + } - try super.init(from: decoder) - } - - public override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + self.prerelease = try? container.decode(AppVersionInfo?.self, forKey: .prerelease) + + try super.init(from: decoder) + } - var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - try container.encodeIfPresent(prerelease, forKey: .prerelease) - } -} - -// MARK: - AppVersionInfo - -public class AppVersionInfo: Codable { - enum CodingKeys: String, CodingKey { - case version = "result" - case updated - case name - case notes - case assets + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(prerelease, forKey: .prerelease) + } } - public struct Asset: Codable { + // MARK: - AppVersionInfo + + class AppVersionInfo: Codable { enum CodingKeys: String, CodingKey { + case version = "result" + case updated case name - case url + case notes + case assets } - public let name: String - public let url: String - } - - public let version: String - public let updated: TimeInterval? - public let name: String? - public let notes: String? - public let assets: [Asset]? - - public init( - version: String, - updated: TimeInterval?, - name: String?, - notes: String?, - assets: [Asset]? - ) { - self.version = version - self.updated = updated - self.name = name - self.notes = notes - self.assets = assets - } - - public func encode(to encoder: Encoder) throws { - var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + public struct Asset: Codable { + enum CodingKeys: String, CodingKey { + case name + case url + } + + public let name: String + public let url: String + } + + public let version: String + public let updated: TimeInterval? + public let name: String? + public let notes: String? + public let assets: [Asset]? - try container.encode(version, forKey: .version) - try container.encodeIfPresent(updated, forKey: .updated) - try container.encodeIfPresent(name, forKey: .name) - try container.encodeIfPresent(notes, forKey: .notes) - try container.encodeIfPresent(assets, forKey: .assets) + public init( + version: String, + updated: TimeInterval?, + name: String?, + notes: String?, + assets: [Asset]? + ) { + self.version = version + self.updated = updated + self.name = name + self.notes = notes + self.assets = assets + } + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(version, forKey: .version) + try container.encodeIfPresent(updated, forKey: .updated) + try container.encodeIfPresent(name, forKey: .name) + try container.encodeIfPresent(notes, forKey: .notes) + try container.encodeIfPresent(assets, forKey: .assets) + } } } diff --git a/SessionNetworkingKit/LibSession/LibSession+Networking.swift b/SessionNetworkingKit/LibSession/LibSession+Networking.swift index c445d3c433..a0a2be6d27 100644 --- a/SessionNetworkingKit/LibSession/LibSession+Networking.swift +++ b/SessionNetworkingKit/LibSession/LibSession+Networking.swift @@ -158,7 +158,7 @@ class LibSessionNetwork: NetworkType { return getSwarm(for: swarmPublicKey) .tryFlatMapWithRandomSnode(retry: retryCount, using: dependencies) { [weak self, dependencies] snode in - try SnodeAPI + try Network.SnodeAPI .preparedGetNetworkTime(from: snode, using: dependencies) .send(using: dependencies) .tryFlatMap { _, timestampMs in @@ -175,7 +175,7 @@ class LibSessionNetwork: NetworkType { ) .map { info, response -> (ResponseInfoType, Data?) in ( - SnodeAPI.LatestTimestampResponseInfo( + Network.SnodeAPI.LatestTimestampResponseInfo( code: info.code, headers: info.headers, timestampMs: timestampMs @@ -188,7 +188,7 @@ class LibSessionNetwork: NetworkType { } } - func checkClientVersion(ed25519SecretKey: [UInt8]) -> AnyPublisher<(ResponseInfoType, AppVersionResponse), Error> { + func checkClientVersion(ed25519SecretKey: [UInt8]) -> AnyPublisher<(ResponseInfoType, Network.FileServer.AppVersionResponse), Error> { typealias Output = (success: Bool, timeout: Bool, statusCode: Int, headers: [String: String], data: Data?) return dependencies @@ -213,14 +213,14 @@ class LibSessionNetwork: NetworkType { ctx ) } - .tryMap { [dependencies] success, timeout, statusCode, headers, maybeData -> (any ResponseInfoType, AppVersionResponse) in + .tryMap { [dependencies] success, timeout, statusCode, headers, maybeData -> (any ResponseInfoType, Network.FileServer.AppVersionResponse) in try LibSessionNetwork.throwErrorIfNeeded(success, timeout, statusCode, headers, maybeData, using: dependencies) guard let data: Data = maybeData else { throw NetworkError.parsingFailed } return ( Network.ResponseInfo(code: statusCode), - try AppVersionResponse.decoded(from: data, using: dependencies) + try Network.FileServer.AppVersionResponse.decoded(from: data, using: dependencies) ) } .eraseToAnyPublisher() diff --git a/SessionNetworkingKit/Models/FileUploadResponse.swift b/SessionNetworkingKit/Models/FileUploadResponse.swift index 41ba747b0f..0f7b328d84 100644 --- a/SessionNetworkingKit/Models/FileUploadResponse.swift +++ b/SessionNetworkingKit/Models/FileUploadResponse.swift @@ -1,5 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +import Foundation + public struct FileUploadResponse: Codable { public let id: String diff --git a/SessionNetworkingKit/PushNotification/Crypto/Crypto+PushNotification.swift b/SessionNetworkingKit/PushNotification/Crypto/Crypto+PushNotification.swift new file mode 100644 index 0000000000..d0b3158e38 --- /dev/null +++ b/SessionNetworkingKit/PushNotification/Crypto/Crypto+PushNotification.swift @@ -0,0 +1,41 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import SessionUtil +import SessionUtilitiesKit + +public extension Crypto.Generator { + static func plaintextWithPushNotificationPayload( + payload: Data, + encKey: Data + ) -> Crypto.Generator { + return Crypto.Generator( + id: "plaintextWithPushNotificationPayload", + args: [payload, encKey] + ) { + var cPayload: [UInt8] = Array(payload) + var cEncKey: [UInt8] = Array(encKey) + var maybePlaintext: UnsafeMutablePointer? = nil + var plaintextLen: Int = 0 + + guard + cEncKey.count == 32, + session_decrypt_push_notification( + &cPayload, + cPayload.count, + &cEncKey, + &maybePlaintext, + &plaintextLen + ), + plaintextLen > 0, + let plaintext: Data = maybePlaintext.map({ Data(bytes: $0, count: plaintextLen) }) + else { throw CryptoError.decryptionFailed } + + free(UnsafeMutableRawPointer(mutating: maybePlaintext)) + + return plaintext + } + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/AuthenticatedRequest.swift b/SessionNetworkingKit/PushNotification/Models/AuthenticatedRequest.swift similarity index 97% rename from SessionMessagingKit/Sending & Receiving/Notifications/Models/AuthenticatedRequest.swift rename to SessionNetworkingKit/PushNotification/Models/AuthenticatedRequest.swift index 9e7d30447b..6fc0c0ff54 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/AuthenticatedRequest.swift +++ b/SessionNetworkingKit/PushNotification/Models/AuthenticatedRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension PushNotificationAPI { - public class AuthenticatedRequest: Encodable { +extension Network.PushNotification { + class AuthenticatedRequest: Encodable { private enum CodingKeys: String, CodingKey { case pubkey case subaccount diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift b/SessionNetworkingKit/PushNotification/Models/NotificationMetadata.swift similarity index 81% rename from SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift rename to SessionNetworkingKit/PushNotification/Models/NotificationMetadata.swift index 2267c9e130..817e19185d 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift +++ b/SessionNetworkingKit/PushNotification/Models/NotificationMetadata.swift @@ -1,10 +1,9 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionNetworkingKit -extension PushNotificationAPI { - public struct NotificationMetadata: Codable, Equatable { +public extension Network.PushNotification { + struct NotificationMetadata: Codable, Equatable { private enum CodingKeys: String, CodingKey { case accountId = "@" case hash = "#" @@ -22,7 +21,7 @@ extension PushNotificationAPI { public let hash: String /// The swarm namespace in which this message arrived. - public let namespace: SnodeAPI.Namespace + public let namespace: Network.SnodeAPI.Namespace /// The swarm timestamp when the message was created (unix epoch milliseconds) public let createdTimestampMs: Int64 @@ -43,18 +42,19 @@ extension PushNotificationAPI { // MARK: - Decodable -extension PushNotificationAPI.NotificationMetadata { +extension Network.PushNotification.NotificationMetadata { public init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) /// There was a bug at one point where the metadata would include a `null` value for the namespace because we were storing /// messages in a namespace that the storage server didn't have an explicit `namespace_id` for, as a result we need to assume /// that the `namespace` value may not be present in the payload - let namespace: SnodeAPI.Namespace = try container.decodeIfPresent(Int.self, forKey: .namespace) - .map { SnodeAPI.Namespace(rawValue: $0) } + let namespace: Network.SnodeAPI.Namespace = try container + .decodeIfPresent(Int.self, forKey: .namespace) + .map { Network.SnodeAPI.Namespace(rawValue: $0) } .defaulting(to: .unknown) - self = PushNotificationAPI.NotificationMetadata( + self = Network.PushNotification.NotificationMetadata( accountId: try container.decode(String.self, forKey: .accountId), hash: try container.decode(String.self, forKey: .hash), namespace: namespace, @@ -68,9 +68,9 @@ extension PushNotificationAPI.NotificationMetadata { // MARK: - Convenience -public extension PushNotificationAPI.NotificationMetadata { - static var invalid: PushNotificationAPI.NotificationMetadata { - PushNotificationAPI.NotificationMetadata( +public extension Network.PushNotification.NotificationMetadata { + static var invalid: Network.PushNotification.NotificationMetadata { + Network.PushNotification.NotificationMetadata( accountId: "", hash: "", namespace: .unknown, diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift b/SessionNetworkingKit/PushNotification/Models/SubscribeRequest.swift similarity index 96% rename from SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift rename to SessionNetworkingKit/PushNotification/Models/SubscribeRequest.swift index 971b969560..f28f2eb92c 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift +++ b/SessionNetworkingKit/PushNotification/Models/SubscribeRequest.swift @@ -3,10 +3,9 @@ // stringlint:disable import Foundation -import SessionNetworkingKit import SessionUtilitiesKit -extension PushNotificationAPI { +public extension Network.PushNotification { struct SubscribeRequest: Encodable { class Subscription: AuthenticatedRequest { private enum CodingKeys: String, CodingKey { @@ -18,7 +17,7 @@ extension PushNotificationAPI { } /// List of integer namespace (-32768 through 32767). These must be sorted in ascending order. - private let namespaces: [SnodeAPI.Namespace] + private let namespaces: [Network.SnodeAPI.Namespace] /// If provided and true then notifications will include the body of the message (as long as it isn't too large); if false then the body will /// not be included in notifications. @@ -68,7 +67,7 @@ extension PushNotificationAPI { // MARK: - Initialization init( - namespaces: [SnodeAPI.Namespace], + namespaces: [Network.SnodeAPI.Namespace], includeMessageData: Bool, serviceInfo: ServiceInfo, notificationsEncryptionKey: Data, @@ -109,9 +108,7 @@ extension PushNotificationAPI { private let subscriptions: [Subscription] - public init( - subscriptions: [Subscription] - ) { + init(subscriptions: [Subscription]) { self.subscriptions = subscriptions } diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeResponse.swift b/SessionNetworkingKit/PushNotification/Models/SubscribeResponse.swift similarity index 85% rename from SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeResponse.swift rename to SessionNetworkingKit/PushNotification/Models/SubscribeResponse.swift index bff4193f7d..59c8404399 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeResponse.swift +++ b/SessionNetworkingKit/PushNotification/Models/SubscribeResponse.swift @@ -2,17 +2,17 @@ import Foundation -public extension PushNotificationAPI { +public extension Network.PushNotification { struct SubscribeResponse: Codable { - struct SubResponse: Codable { + public struct SubResponse: Codable { /// Flag indicating the success of the registration - let success: Bool? + public let success: Bool? /// Value is `true` upon an initial registration - let added: Bool? + public let added: Bool? /// Value is `true` upon a renewal/update registration - let updated: Bool? + public let updated: Bool? /// This will be one of the errors found here: /// https://github.com/jagerman/session-push-notification-server/blob/spns-v2/spns/hive/subscription.hpp#L21 @@ -24,13 +24,13 @@ public extension PushNotificationAPI { /// SERVICE_TIMEOUT = 3 // The backend service did not response /// ERROR = 4 // There was some other error processing the subscription (details in the string) /// INTERNAL_ERROR = 5 // An internal program error occured processing the request - let error: Int? + public let error: Int? /// Includes additional information about the error - let message: String? + public let message: String? } - let subResponses: [SubResponse] + public let subResponses: [SubResponse] public init(from decoder: Decoder) throws { guard diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift b/SessionNetworkingKit/PushNotification/Models/UnsubscribeRequest.swift similarity index 98% rename from SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift rename to SessionNetworkingKit/PushNotification/Models/UnsubscribeRequest.swift index 4f4b7da70c..911632d92b 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift +++ b/SessionNetworkingKit/PushNotification/Models/UnsubscribeRequest.swift @@ -3,10 +3,9 @@ // stringlint:disable import Foundation -import SessionNetworkingKit import SessionUtilitiesKit -extension PushNotificationAPI { +extension Network.PushNotification { struct UnsubscribeRequest: Encodable { class Subscription: AuthenticatedRequest { private enum CodingKeys: String, CodingKey { diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeResponse.swift b/SessionNetworkingKit/PushNotification/Models/UnsubscribeResponse.swift similarity index 85% rename from SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeResponse.swift rename to SessionNetworkingKit/PushNotification/Models/UnsubscribeResponse.swift index c89aa19f3a..4e5e0f9e56 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeResponse.swift +++ b/SessionNetworkingKit/PushNotification/Models/UnsubscribeResponse.swift @@ -2,17 +2,17 @@ import Foundation -public extension PushNotificationAPI { +public extension Network.PushNotification { struct UnsubscribeResponse: Codable { - struct SubResponse: Codable { + public struct SubResponse: Codable { /// Flag indicating the success of the registration - let success: Bool? + public let success: Bool? /// Value is `true` upon an initial registration - let added: Bool? + public let added: Bool? /// Value is `true` upon a renewal/update registration - let updated: Bool? + public let updated: Bool? /// This will be one of the errors found here: /// https://github.com/jagerman/session-push-notification-server/blob/spns-v2/spns/hive/subscription.hpp#L21 @@ -24,13 +24,13 @@ public extension PushNotificationAPI { /// SERVICE_TIMEOUT = 3 // The backend service did not response /// ERROR = 4 // There was some other error processing the subscription (details in the string) /// INTERNAL_ERROR = 5 // An internal program error occured processing the request - let error: Int? + public let error: Int? /// Includes additional information about the error - let message: String? + public let message: String? } - let subResponses: [SubResponse] + public let subResponses: [SubResponse] public init(from decoder: Decoder) throws { guard diff --git a/SessionNetworkingKit/PushNotification/PushNotification.swift b/SessionNetworkingKit/PushNotification/PushNotification.swift new file mode 100644 index 0000000000..b080f7154e --- /dev/null +++ b/SessionNetworkingKit/PushNotification/PushNotification.swift @@ -0,0 +1,29 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import SessionUtilitiesKit + +// MARK: - KeychainStorage + +public extension KeychainStorage.DataKey { static let pushNotificationEncryptionKey: Self = "PNEncryptionKeyKey" } + +// MARK: - Log.Category + +public extension Log.Category { + static let pushNotificationAPI: Log.Category = .create("PushNotificationAPI", defaultLevel: .info) +} + +// MARK: - Network.PushNotification + +public extension Network { + enum PushNotification { + internal static let encryptionKeyLength: Int = 32 + internal static let maxRetryCount: Int = 4 + public static let tokenExpirationInterval: TimeInterval = (12 * 60 * 60) + + internal static let server: String = "https://push.getsession.org" + internal static let serverPublicKey = "d7557fe563e2610de876c0ac7341b62f3c82d5eea4b62c702392ea4368f51b3b" + } +} diff --git a/SessionNetworkingKit/PushNotification/PushNotificationAPI.swift b/SessionNetworkingKit/PushNotification/PushNotificationAPI.swift new file mode 100644 index 0000000000..dfc2cbee59 --- /dev/null +++ b/SessionNetworkingKit/PushNotification/PushNotificationAPI.swift @@ -0,0 +1,194 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import Combine +import UserNotifications +import SessionUtilitiesKit + +public extension Network.PushNotification { + static func preparedSubscribe( + token: Data, + swarms: [(sessionId: SessionId, authMethod: AuthenticationMethod)], + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + guard dependencies[defaults: .standard, key: .isUsingFullAPNs] else { + throw NetworkError.invalidPreparedRequest + } + + guard let notificationsEncryptionKey: Data = try? dependencies[singleton: .keychain].getOrGenerateEncryptionKey( + forKey: .pushNotificationEncryptionKey, + length: encryptionKeyLength, + cat: .pushNotificationAPI, + legacyKey: "PNEncryptionKeyKey", + legacyService: "PNKeyChainService" + ) else { + Log.error(.pushNotificationAPI, "Unable to retrieve PN encryption key.") + throw KeychainStorageError.keySpecInvalid + } + + return try Network.PreparedRequest( + request: Request( + method: .post, + endpoint: Endpoint.subscribe, + body: SubscribeRequest( + subscriptions: swarms.map { sessionId, authMethod -> SubscribeRequest.Subscription in + SubscribeRequest.Subscription( + namespaces: { + switch sessionId.prefix { + case .group: return [ + .groupMessages, + .configGroupKeys, + .configGroupInfo, + .configGroupMembers, + .revokedRetrievableGroupMessages + ] + default: return [ + .default, + .configUserProfile, + .configContacts, + .configConvoInfoVolatile, + .configUserGroups + ] + } + }(), + /// Note: Unfortunately we always need the message content because without the content + /// control messages can't be distinguished from visible messages which results in the + /// 'generic' notification being shown when receiving things like typing indicator updates + includeMessageData: true, + serviceInfo: ServiceInfo( + token: token.toHexString() + ), + notificationsEncryptionKey: notificationsEncryptionKey, + authMethod: authMethod, + timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000) // Seconds + ) + } + ) + ), + responseType: SubscribeResponse.self, + retryCount: Network.PushNotification.maxRetryCount, + using: dependencies + ) + .handleEvents( + receiveOutput: { _, response in + zip(response.subResponses, swarms).forEach { subResponse, swarm in + guard subResponse.success != true else { return } + + Log.error(.pushNotificationAPI, "Couldn't subscribe for push notifications for: \(swarm.sessionId) due to error (\(subResponse.error ?? -1)): \(subResponse.message ?? "nil").") + } + }, + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): Log.error(.pushNotificationAPI, "Couldn't subscribe for push notifications due to error: \(error).") + } + } + ) + } + + static func preparedUnsubscribe( + token: Data, + swarms: [(sessionId: SessionId, authMethod: AuthenticationMethod)], + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try Network.PreparedRequest( + request: Request( + method: .post, + endpoint: Endpoint.unsubscribe, + body: UnsubscribeRequest( + subscriptions: swarms.map { sessionId, authMethod -> UnsubscribeRequest.Subscription in + UnsubscribeRequest.Subscription( + serviceInfo: ServiceInfo( + token: token.toHexString() + ), + authMethod: authMethod, + timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000) // Seconds + ) + } + ) + ), + responseType: UnsubscribeResponse.self, + retryCount: Network.PushNotification.maxRetryCount, + using: dependencies + ) + .handleEvents( + receiveOutput: { _, response in + zip(response.subResponses, swarms).forEach { subResponse, swarm in + guard subResponse.success != true else { return } + + Log.error(.pushNotificationAPI, "Couldn't unsubscribe for push notifications for: \(swarm.sessionId) due to error (\(subResponse.error ?? -1)): \(subResponse.message ?? "nil").") + } + }, + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): Log.error(.pushNotificationAPI, "Couldn't unsubscribe for push notifications due to error: \(error).") + } + } + ) + } + + // MARK: - Notification Handling + + static func processNotification( + notificationContent: UNNotificationContent, + using dependencies: Dependencies + ) -> (data: Data?, metadata: NotificationMetadata, result: ProcessResult) { + // Make sure the notification is from the updated push server + guard notificationContent.userInfo["spns"] != nil else { + return (nil, .invalid, .legacyFailure) + } + + guard let base64EncodedEncString: String = notificationContent.userInfo["enc_payload"] as? String else { + return (nil, .invalid, .failureNoContent) + } + + // Decrypt and decode the payload + let notification: BencodeResponse + + do { + guard let encryptedData: Data = Data(base64Encoded: base64EncodedEncString) else { + throw CryptoError.invalidBase64EncodedData + } + + let notificationsEncryptionKey: Data = try dependencies[singleton: .keychain].getOrGenerateEncryptionKey( + forKey: .pushNotificationEncryptionKey, + length: encryptionKeyLength, + cat: .pushNotificationAPI, + legacyKey: "PNEncryptionKeyKey", + legacyService: "PNKeyChainService" + ) + let decryptedData: Data = try dependencies[singleton: .crypto].tryGenerate( + .plaintextWithPushNotificationPayload( + payload: encryptedData, + encKey: notificationsEncryptionKey + ) + ) + notification = try BencodeDecoder(using: dependencies) + .decode(BencodeResponse.self, from: decryptedData) + } + catch { + Log.error(.pushNotificationAPI, "Failed to decrypt or decode notification due to error: \(error)") + return (nil, .invalid, .failure) + } + + // If the metadata says that the message was too large then we should show the generic + // notification (this is a valid case) + guard !notification.info.dataTooLong else { return (nil, notification.info, .successTooLong) } + + // Check that the body we were given is valid and not empty + guard + let notificationData: Data = notification.data, + notification.info.dataLength == notificationData.count, + !notificationData.isEmpty + else { + Log.error(.pushNotificationAPI, "Get notification data failed") + return (nil, notification.info, .failureNoContent) + } + + // Success, we have the notification content + return (notificationData, notification.info, .success) + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift b/SessionNetworkingKit/PushNotification/PushNotificationEndpoint.swift similarity index 80% rename from SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift rename to SessionNetworkingKit/PushNotification/PushNotificationEndpoint.swift index a5d92afb4c..5fdb987b04 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift +++ b/SessionNetworkingKit/PushNotification/PushNotificationEndpoint.swift @@ -6,12 +6,12 @@ import Foundation import SessionNetworkingKit import SessionUtilitiesKit -public extension PushNotificationAPI { +public extension Network.PushNotification { enum Endpoint: EndpointType { case subscribe case unsubscribe - public static var name: String { "PushNotificationAPI.Endpoint" } + public static var name: String { "PushNotification.Endpoint" } public var path: String { switch self { diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/ProcessResult.swift b/SessionNetworkingKit/PushNotification/Types/ProcessResult.swift similarity index 84% rename from SessionMessagingKit/Sending & Receiving/Notifications/Types/ProcessResult.swift rename to SessionNetworkingKit/PushNotification/Types/ProcessResult.swift index 07496de265..a33e84f6c0 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/ProcessResult.swift +++ b/SessionNetworkingKit/PushNotification/Types/ProcessResult.swift @@ -2,7 +2,7 @@ import Foundation -public extension PushNotificationAPI { +public extension Network.PushNotification { enum ProcessResult { case success case successTooLong diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/Request+PushNotificationAPI.swift b/SessionNetworkingKit/PushNotification/Types/Request+PushNotificationAPI.swift similarity index 68% rename from SessionMessagingKit/Sending & Receiving/Notifications/Types/Request+PushNotificationAPI.swift rename to SessionNetworkingKit/PushNotification/Types/Request+PushNotificationAPI.swift index c63988f5d1..480c90bf65 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/Request+PushNotificationAPI.swift +++ b/SessionNetworkingKit/PushNotification/Types/Request+PushNotificationAPI.swift @@ -1,12 +1,9 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionNetworkingKit import SessionUtilitiesKit -// MARK: Request - PushNotificationAPI - -public extension Request where Endpoint == PushNotificationAPI.Endpoint { +public extension Request where Endpoint == Network.PushNotification.Endpoint { init( method: HTTPMethod, endpoint: Endpoint, @@ -18,10 +15,10 @@ public extension Request where Endpoint == PushNotificationAPI.Endpoint { endpoint: endpoint, destination: try .server( method: method, - server: PushNotificationAPI.server, + server: Network.PushNotification.server, queryParameters: queryParameters, headers: headers, - x25519PublicKey: PushNotificationAPI.serverPublicKey + x25519PublicKey: Network.PushNotification.serverPublicKey ), body: body ) diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift b/SessionNetworkingKit/PushNotification/Types/Service.swift similarity index 84% rename from SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift rename to SessionNetworkingKit/PushNotification/Types/Service.swift index c930c0a176..fa33fe3cde 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift +++ b/SessionNetworkingKit/PushNotification/Types/Service.swift @@ -8,15 +8,15 @@ import SessionUtilitiesKit // MARK: - FeatureStorage public extension FeatureStorage { - static let pushNotificationService: FeatureConfig = Dependencies.create( + static let pushNotificationService: FeatureConfig = Dependencies.create( identifier: "pushNotificationService", defaultOption: .apns ) } -// MARK: - PushNotificationAPI.Service +// MARK: - Network.PushNotification.Service -public extension PushNotificationAPI { +public extension Network.PushNotification { enum Service: String, Codable, CaseIterable, FeatureOption { case apns case sandbox = "apns-sandbox" // Use for push notifications in Testnet diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/ServiceInfo.swift b/SessionNetworkingKit/PushNotification/Types/ServiceInfo.swift similarity index 91% rename from SessionMessagingKit/Sending & Receiving/Notifications/Types/ServiceInfo.swift rename to SessionNetworkingKit/PushNotification/Types/ServiceInfo.swift index 8b1ccfeaed..b534816f35 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/ServiceInfo.swift +++ b/SessionNetworkingKit/PushNotification/Types/ServiceInfo.swift @@ -2,7 +2,7 @@ import Foundation -extension PushNotificationAPI { +extension Network.PushNotification { struct ServiceInfo: Codable { private enum CodingKeys: String, CodingKey { case token diff --git a/SessionNetworkingKit/SOGS/Crypto/Crypto+SOGS.swift b/SessionNetworkingKit/SOGS/Crypto/Crypto+SOGS.swift index e69de29bb2..9b48b1c7f4 100644 --- a/SessionNetworkingKit/SOGS/Crypto/Crypto+SOGS.swift +++ b/SessionNetworkingKit/SOGS/Crypto/Crypto+SOGS.swift @@ -0,0 +1,153 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import SessionUtil +import SessionUtilitiesKit + +public extension Crypto.Generator { + /// Constructs a "blinded" key pair (`ka, kA`) based on an open group server `publicKey` and an ed25519 `keyPair` + static func blinded15KeyPair( + serverPublicKey: String, + ed25519SecretKey: [UInt8] + ) -> Crypto.Generator { + return Crypto.Generator( + id: "blinded15KeyPair", + args: [serverPublicKey, ed25519SecretKey] + ) { + var cEd25519SecretKey: [UInt8] = Array(ed25519SecretKey) + var cServerPublicKey: [UInt8] = Array(Data(hex: serverPublicKey)) + var cBlindedPubkey: [UInt8] = [UInt8](repeating: 0, count: 32) + var cBlindedSeckey: [UInt8] = [UInt8](repeating: 0, count: 32) + + guard + cEd25519SecretKey.count == 64, + cServerPublicKey.count == 32, + session_blind15_key_pair( + &cEd25519SecretKey, + &cServerPublicKey, + &cBlindedPubkey, + &cBlindedSeckey + ) + else { throw CryptoError.keyGenerationFailed } + + return KeyPair(publicKey: cBlindedPubkey, secretKey: cBlindedSeckey) + } + } + + /// Constructs a "blinded" key pair (`ka, kA`) based on an open group server `publicKey` and an ed25519 `keyPair` + static func blinded25KeyPair( + serverPublicKey: String, + ed25519SecretKey: [UInt8] + ) -> Crypto.Generator { + return Crypto.Generator( + id: "blinded25KeyPair", + args: [serverPublicKey, ed25519SecretKey] + ) { + var cEd25519SecretKey: [UInt8] = Array(ed25519SecretKey) + var cServerPublicKey: [UInt8] = Array(Data(hex: serverPublicKey)) + var cBlindedPubkey: [UInt8] = [UInt8](repeating: 0, count: 32) + var cBlindedSeckey: [UInt8] = [UInt8](repeating: 0, count: 32) + + guard + cEd25519SecretKey.count == 64, + cServerPublicKey.count == 32, + session_blind25_key_pair( + &cEd25519SecretKey, + &cServerPublicKey, + &cBlindedPubkey, + &cBlindedSeckey + ) + else { throw CryptoError.keyGenerationFailed } + + return KeyPair(publicKey: cBlindedPubkey, secretKey: cBlindedSeckey) + } + } + + static func signatureBlind15( + message: [UInt8], + serverPublicKey: String, + ed25519SecretKey: [UInt8] + ) -> Crypto.Generator<[UInt8]> { + return Crypto.Generator( + id: "signatureBlind15", + args: [message, serverPublicKey, ed25519SecretKey] + ) { + var cEd25519SecretKey: [UInt8] = ed25519SecretKey + var cServerPublicKey: [UInt8] = Array(Data(hex: serverPublicKey)) + var cMessage: [UInt8] = message + var cSignature: [UInt8] = [UInt8](repeating: 0, count: 64) + + guard + cEd25519SecretKey.count == 64, + cServerPublicKey.count == 32, + session_blind15_sign( + &cEd25519SecretKey, + &cServerPublicKey, + &cMessage, + cMessage.count, + &cSignature + ) + else { throw CryptoError.signatureGenerationFailed } + + return cSignature + } + } + + static func signatureBlind25( + message: [UInt8], + serverPublicKey: String, + ed25519SecretKey: [UInt8] + ) -> Crypto.Generator<[UInt8]> { + return Crypto.Generator( + id: "signatureBlind25", + args: [message, serverPublicKey, ed25519SecretKey] + ) { + var cEd25519SecretKey: [UInt8] = ed25519SecretKey + var cServerPublicKey: [UInt8] = Array(Data(hex: serverPublicKey)) + var cMessage: [UInt8] = message + var cSignature: [UInt8] = [UInt8](repeating: 0, count: 64) + + guard + cEd25519SecretKey.count == 64, + cServerPublicKey.count == 32, + session_blind25_sign( + &cEd25519SecretKey, + &cServerPublicKey, + &cMessage, + cMessage.count, + &cSignature + ) + else { throw CryptoError.signatureGenerationFailed } + + return cSignature + } + } +} + +public extension Crypto.Verification { + /// This method should be used to check if a users standard sessionId matches a blinded one + static func sessionId( + _ standardSessionId: String, + matchesBlindedId blindedSessionId: String, + serverPublicKey: String + ) -> Crypto.Verification { + return Crypto.Verification( + id: "sessionId", + args: [standardSessionId, blindedSessionId, serverPublicKey] + ) { + guard + var cStandardSessionId: [CChar] = standardSessionId.cString(using: .utf8), + var cBlindedSessionId: [CChar] = blindedSessionId.cString(using: .utf8), + var cServerPublicKey: [CChar] = serverPublicKey.cString(using: .utf8) + else { return false } + + return session_id_matches_blinded_id( + &cStandardSessionId, + &cBlindedSessionId, + &cServerPublicKey + ) + } + } +} diff --git a/SessionNetworkingKit/SOGS/Models/CapabilitiesResponse.swift b/SessionNetworkingKit/SOGS/Models/CapabilitiesResponse.swift index 031c9c9753..1e5ddf28c1 100644 --- a/SessionNetworkingKit/SOGS/Models/CapabilitiesResponse.swift +++ b/SessionNetworkingKit/SOGS/Models/CapabilitiesResponse.swift @@ -3,13 +3,13 @@ import Foundation extension Network.SOGS { - public struct Capabilities: Codable, Equatable { - public let capabilities: [Network.SOGS.Capability.Variant] - public let missing: [Network.SOGS.Capability.Variant]? + public struct CapabilitiesResponse: Codable, Equatable { + public let capabilities: [String] + public let missing: [String]? // MARK: - Initialization - public init(capabilities: [Network.SOGS.Capability.Variant], missing: [Network.SOGS.Capability.Variant]? = nil) { + public init(capabilities: [String], missing: [String]? = nil) { self.capabilities = capabilities self.missing = missing } diff --git a/SessionNetworkingKit/SOGS/Models/DeleteInboxResponse.swift b/SessionNetworkingKit/SOGS/Models/DeleteInboxResponse.swift new file mode 100644 index 0000000000..c5b82413bf --- /dev/null +++ b/SessionNetworkingKit/SOGS/Models/DeleteInboxResponse.swift @@ -0,0 +1,9 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Network.SOGS { + public struct DeleteInboxResponse: Codable { + let deleted: UInt64 + } +} diff --git a/SessionNetworkingKit/SOGS/Models/DirectMessage.swift b/SessionNetworkingKit/SOGS/Models/DirectMessage.swift new file mode 100644 index 0000000000..507eb5c576 --- /dev/null +++ b/SessionNetworkingKit/SOGS/Models/DirectMessage.swift @@ -0,0 +1,34 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Network.SOGS { + public struct DirectMessage: Codable { + enum CodingKeys: String, CodingKey { + case id + case sender + case recipient + case posted = "posted_at" + case expires = "expires_at" + case base64EncodedMessage = "message" + } + + /// The unique integer message id + public let id: Int64 + + /// The (blinded) Session ID of the sender of the message + public let sender: String + + /// The (blinded) Session ID of the recipient of the message + public let recipient: String + + /// Unix timestamp when the message was received by SOGS + public let posted: TimeInterval + + /// Unix timestamp when SOGS will expire and delete the message + public let expires: TimeInterval + + /// The encrypted message body + public let base64EncodedMessage: String + } +} diff --git a/SessionNetworkingKit/SOGS/Models/PinnedMessage.swift b/SessionNetworkingKit/SOGS/Models/PinnedMessage.swift new file mode 100644 index 0000000000..332a8bb34a --- /dev/null +++ b/SessionNetworkingKit/SOGS/Models/PinnedMessage.swift @@ -0,0 +1,22 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Network.SOGS { + public struct PinnedMessage: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case id + case pinnedAt = "pinned_at" + case pinnedBy = "pinned_by" + } + + /// The numeric message id + let id: Int64 + + /// The unix timestamp when the message was pinned + let pinnedAt: TimeInterval + + /// The session ID of the admin who pinned this message (which is not necessarily the same as the author of the message) + let pinnedBy: String + } +} diff --git a/SessionNetworkingKit/SOGS/Models/ReactionResponse.swift b/SessionNetworkingKit/SOGS/Models/ReactionResponse.swift new file mode 100644 index 0000000000..6e4992d688 --- /dev/null +++ b/SessionNetworkingKit/SOGS/Models/ReactionResponse.swift @@ -0,0 +1,44 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Network.SOGS { + public struct ReactionAddResponse: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case added + case seqNo = "seqno" + } + + /// This field indicates whether the reaction was added (true) or already present (false). + public let added: Bool + + /// The seqNo after the reaction is added. + public let seqNo: Int64? + } + + public struct ReactionRemoveResponse: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case removed + case seqNo = "seqno" + } + + /// This field indicates whether the reaction was removed (true) or was not present to begin with (false). + public let removed: Bool + + /// The seqNo after the reaction is removed. + public let seqNo: Int64? + } + + public struct ReactionRemoveAllResponse: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case removed + case seqNo = "seqno" + } + + /// This field shows the total number of reactions that were deleted. + public let removed: Int64 + + /// The seqNo after the reactions is all removed. + public let seqNo: Int64? + } +} diff --git a/SessionNetworkingKit/SOGS/Models/Room.swift b/SessionNetworkingKit/SOGS/Models/Room.swift new file mode 100644 index 0000000000..6f188c5ce7 --- /dev/null +++ b/SessionNetworkingKit/SOGS/Models/Room.swift @@ -0,0 +1,191 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Network.SOGS { + public struct Room: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case token + case name + case roomDescription = "description" + case infoUpdates = "info_updates" + case messageSequence = "message_sequence" + case created + + case activeUsers = "active_users" + case activeUsersCutoff = "active_users_cutoff" + case imageId = "image_id" + case pinnedMessages = "pinned_messages" + + case admin + case globalAdmin = "global_admin" + case admins + case hiddenAdmins = "hidden_admins" + + case moderator + case globalModerator = "global_moderator" + case moderators + case hiddenModerators = "hidden_moderators" + + case read + case defaultRead = "default_read" + case defaultAccessible = "default_accessible" + case write + case defaultWrite = "default_write" + case upload + case defaultUpload = "default_upload" + } + + /// The room token as used in a URL, e.g. "sudoku" + public let token: String + + /// The room name typically shown to users, e.g. "Sodoku Solvers" + public let name: String + + /// Text description of the room, e.g. "All the best sodoku discussion!" + public let roomDescription: String? + + /// Monotonic integer counter that increases whenever the room's metadata changes + public let infoUpdates: Int64 + + /// Monotonic room post counter that increases each time a message is posted, edited, or deleted in this room + /// + /// Note that changes to this field do not imply an update the room's info_updates value, nor vice versa + public let messageSequence: Int64 + + /// Unix timestamp (as a float) of the room creation time. Note that unlike earlier versions of SOGS, this is a proper + /// seconds-since-epoch unix timestamp, not a javascript-style millisecond value + public let created: TimeInterval + + /// Number of recently active users in the room over a recent time period (as given in the active_users_cutoff value) + /// + /// Users are considered "active" if they have accessed the room (checking for new messages, etc.) at least once in the given period + /// + /// **Note:** changes to this field do not update the room's info_updates value + public let activeUsers: Int64 + + /// The length of time (in seconds) of the active_users period. Defaults to a week (604800), but the open group administrator can configure it + public let activeUsersCutoff: Int64 + + /// File ID of an uploaded file containing the room's image + /// + /// Omitted if there is no image + public let imageId: String? + + /// Array of pinned message information (omitted entirely if there are no pinned messages) + public let pinnedMessages: [PinnedMessage]? + + /// This flag is `true` if the current user has admin permissions in the room + public let admin: Bool + + /// This flag is `true` if the current user is a global admin + /// + /// This is not exclusive of `globalModerator`/`moderator`/`admin` (a global admin will have all four set to `true`) + public let globalAdmin: Bool + + /// Array of Session IDs of the room's publicly viewable moderators + /// + /// This does not include room moderator nor hidden admins + public let admins: [String] + + /// Array of Session IDs of the room's publicly hidden admins + /// + /// This field is only included if the requesting user has moderator or admin permissions, and is omitted if empty + public let hiddenAdmins: [String]? + + /// This flag is `true` if the current user has moderator permissions in the room + public let moderator: Bool + + /// This flag is `true` if the current user is a global moderator + /// + /// This is not exclusive of `moderator` (a global moderator will have both flags set to `true`) + public let globalModerator: Bool + + /// Array of Session IDs of the room's publicly viewable moderators + /// + /// This does not include room administrators nor hidden moderators + public let moderators: [String] + + /// Array of Session IDs of the room's publicly hidden moderators + /// + /// This field is only included if the requesting user has moderator or admin permissions, and is omitted if empty + public let hiddenModerators: [String]? + + /// This flag indicates whether the **current** user has permission to read the room's messages + /// + /// **Note:** If this value is `false` the user only has access the room metadata + public let read: Bool + + /// This field indicates whether new users have read permissions in the room + /// + /// It is included in the response only if the requesting user has moderator or admin permissions + public let defaultRead: Bool? + + /// This field indicates whether new users have access permissions in the room + /// + /// It is included in the response only if the requesting user has moderator or admin permissions + public let defaultAccessible: Bool? + + /// This flag indicates whether the **current** user has permission to post messages in the room + public let write: Bool + + /// This field indicates whether new users have write permissions in the room + /// + /// It is included in the response only if the requesting user has moderator or admin permissions + public let defaultWrite: Bool? + + /// This flag indicates whether the **current** user has permission to upload files to the room + public let upload: Bool + + /// This field indicates whether new users have upload permissions in the room + /// + /// It is included in the response only if the requesting user has moderator or admin permissions + public let defaultUpload: Bool? + } +} + +// MARK: - Decoding + +extension Network.SOGS.Room { + public init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + // This logic is to future-proof the transition from int-based to string-based image ids + let maybeImageId: String? = ( + ((try? container.decode(Int64.self, forKey: .imageId)).map { "\($0)" }) ?? + (try? container.decode(String.self, forKey: .imageId)) + ) + + self = Network.SOGS.Room( + token: try container.decode(String.self, forKey: .token), + name: try container.decode(String.self, forKey: .name), + roomDescription: try? container.decode(String.self, forKey: .roomDescription), + infoUpdates: try container.decode(Int64.self, forKey: .infoUpdates), + messageSequence: try container.decode(Int64.self, forKey: .messageSequence), + created: try container.decode(TimeInterval.self, forKey: .created), + + activeUsers: try container.decode(Int64.self, forKey: .activeUsers), + activeUsersCutoff: try container.decode(Int64.self, forKey: .activeUsersCutoff), + imageId: maybeImageId, + pinnedMessages: try? container.decode([Network.SOGS.PinnedMessage].self, forKey: .pinnedMessages), + + admin: ((try? container.decode(Bool.self, forKey: .admin)) ?? false), + globalAdmin: ((try? container.decode(Bool.self, forKey: .globalAdmin)) ?? false), + admins: try container.decode([String].self, forKey: .admins), + hiddenAdmins: try? container.decode([String].self, forKey: .hiddenAdmins), + + moderator: ((try? container.decode(Bool.self, forKey: .moderator)) ?? false), + globalModerator: ((try? container.decode(Bool.self, forKey: .globalModerator)) ?? false), + moderators: try container.decode([String].self, forKey: .moderators), + hiddenModerators: try? container.decode([String].self, forKey: .hiddenModerators), + + read: try container.decode(Bool.self, forKey: .read), + defaultRead: try? container.decode(Bool.self, forKey: .defaultRead), + defaultAccessible: try? container.decode(Bool.self, forKey: .defaultAccessible), + write: try container.decode(Bool.self, forKey: .write), + defaultWrite: try? container.decode(Bool.self, forKey: .defaultWrite), + upload: try container.decode(Bool.self, forKey: .upload), + defaultUpload: try? container.decode(Bool.self, forKey: .defaultUpload) + ) + } +} diff --git a/SessionNetworkingKit/SOGS/Models/RoomPollInfo.swift b/SessionNetworkingKit/SOGS/Models/RoomPollInfo.swift new file mode 100644 index 0000000000..a2b87f424d --- /dev/null +++ b/SessionNetworkingKit/SOGS/Models/RoomPollInfo.swift @@ -0,0 +1,143 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Network.SOGS { + /// This only contains ephemeral data + public struct RoomPollInfo: Codable { + enum CodingKeys: String, CodingKey { + case token + case activeUsers = "active_users" + + case admin + case globalAdmin = "global_admin" + + case moderator + case globalModerator = "global_moderator" + + case read + case defaultRead = "default_read" + case defaultAccessible = "default_accessible" + case write + case defaultWrite = "default_write" + case upload + case defaultUpload = "default_upload" + + case details + } + + /// The room token as used in a URL, e.g. "sudoku" + public let token: String + + /// Number of recently active users in the room over a recent time period (as given in the active_users_cutoff value) + /// + /// Users are considered "active" if they have accessed the room (checking for new messages, etc.) at least once in the given period + /// + /// **Note:** changes to this field do not update the room's info_updates value + public let activeUsers: Int64 + + /// This flag is `true` if the current user has admin permissions in the room + public let admin: Bool + + /// This flag is `true` if the current user is a global admin + /// + /// This is not exclusive of `globalModerator`/`moderator`/`admin` (a global admin will have all four set to `true`) + public let globalAdmin: Bool + + /// This flag is `true` if the current user has moderator permissions in the room + public let moderator: Bool + + /// This flag is `true` if the current user is a global moderator + /// + /// This is not exclusive of `moderator` (a global moderator will have both flags set to `true`) + public let globalModerator: Bool + + /// This flag indicates whether the **current** user has permission to read the room's messages + /// + /// **Note:** If this value is `false` the user only has access the room metadata + public let read: Bool + + /// This field indicates whether new users have read permissions in the room + /// + /// It is included in the response only if the requesting user has moderator or admin permissions + public let defaultRead: Bool? + + /// This field indicates whether new users have access permissions in the room + /// + /// It is included in the response only if the requesting user has moderator or admin permissions + public let defaultAccessible: Bool? + + /// This flag indicates whether the **current** user has permission to post messages in the room + public let write: Bool + + /// This field indicates whether new users have write permissions in the room + /// + /// It is included in the response only if the requesting user has moderator or admin permissions + public let defaultWrite: Bool? + + /// This flag indicates whether the **current** user has permission to upload files to the room + public let upload: Bool + + /// This field indicates whether new users have upload permissions in the room + /// + /// It is included in the response only if the requesting user has moderator or admin permissions + public let defaultUpload: Bool? + + /// The full room metadata (as would be returned by the `/rooms/{roomToken}` endpoint) + /// + /// Only populated and different if the `info_updates` counter differs from the provided `info_updated` value + public let details: Room? + } +} + +// MARK: - Convenience + +public extension Network.SOGS.RoomPollInfo { + init(room: Network.SOGS.Room) { + self.init( + token: room.token, + activeUsers: room.activeUsers, + admin: room.admin, + globalAdmin: room.globalAdmin, + moderator: room.moderator, + globalModerator: room.globalModerator, + read: room.read, + defaultRead: room.defaultRead, + defaultAccessible: room.defaultAccessible, + write: room.write, + defaultWrite: room.defaultWrite, + upload: room.upload, + defaultUpload: room.defaultUpload, + details: room + ) + } +} + +// MARK: - Decoding + +extension Network.SOGS.RoomPollInfo { + public init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + self = Network.SOGS.RoomPollInfo( + token: try container.decode(String.self, forKey: .token), + activeUsers: try container.decode(Int64.self, forKey: .activeUsers), + + admin: ((try? container.decode(Bool.self, forKey: .admin)) ?? false), + globalAdmin: ((try? container.decode(Bool.self, forKey: .globalAdmin)) ?? false), + + moderator: ((try? container.decode(Bool.self, forKey: .moderator)) ?? false), + globalModerator: ((try? container.decode(Bool.self, forKey: .globalModerator)) ?? false), + + read: try container.decode(Bool.self, forKey: .read), + defaultRead: try? container.decode(Bool.self, forKey: .defaultRead), + defaultAccessible: try? container.decode(Bool.self, forKey: .defaultAccessible), + write: try container.decode(Bool.self, forKey: .write), + defaultWrite: try? container.decode(Bool.self, forKey: .defaultWrite), + upload: try container.decode(Bool.self, forKey: .upload), + defaultUpload: try? container.decode(Bool.self, forKey: .defaultUpload), + + details: try? container.decode(Network.SOGS.Room.self, forKey: .details) + ) + } +} diff --git a/SessionNetworkingKit/SOGS/Models/SOGSMessage.swift b/SessionNetworkingKit/SOGS/Models/SOGSMessage.swift index d1beafbddb..5b902a0941 100644 --- a/SessionNetworkingKit/SOGS/Models/SOGSMessage.swift +++ b/SessionNetworkingKit/SOGS/Models/SOGSMessage.swift @@ -1,10 +1,9 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionNetworkingKit import SessionUtilitiesKit -extension OpenGroupAPI { +extension Network.SOGS { public struct Message: Codable, Equatable { enum CodingKeys: String, CodingKey { case id @@ -56,7 +55,7 @@ extension OpenGroupAPI { // MARK: - Decoder -extension OpenGroupAPI.Message { +extension Network.SOGS.Message { public init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) @@ -102,7 +101,7 @@ extension OpenGroupAPI.Message { } } - self = OpenGroupAPI.Message( + self = Network.SOGS.Message( id: try container.decode(Int64.self, forKey: .id), sender: try container.decodeIfPresent(String.self, forKey: .sender), posted: try container.decodeIfPresent(TimeInterval.self, forKey: .posted), @@ -119,11 +118,11 @@ extension OpenGroupAPI.Message { } } -extension OpenGroupAPI.Message.Reaction { +extension Network.SOGS.Message.Reaction { public init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - self = OpenGroupAPI.Message.Reaction( + self = Network.SOGS.Message.Reaction( count: try container.decode(Int64.self, forKey: .count), reactors: try container.decodeIfPresent([String].self, forKey: .reactors), you: ((try container.decodeIfPresent(Bool.self, forKey: .you)) ?? false), diff --git a/SessionNetworkingKit/SOGS/Models/SendDirectMessageRequest.swift b/SessionNetworkingKit/SOGS/Models/SendDirectMessageRequest.swift index 19df350f9e..4a72a11423 100644 --- a/SessionNetworkingKit/SOGS/Models/SendDirectMessageRequest.swift +++ b/SessionNetworkingKit/SOGS/Models/SendDirectMessageRequest.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { public struct SendDirectMessageRequest: Codable { let message: Data diff --git a/SessionNetworkingKit/SOGS/Models/SendDirectMessageResponse.swift b/SessionNetworkingKit/SOGS/Models/SendDirectMessageResponse.swift index a8e998f8ac..a076f4edd4 100644 --- a/SessionNetworkingKit/SOGS/Models/SendDirectMessageResponse.swift +++ b/SessionNetworkingKit/SOGS/Models/SendDirectMessageResponse.swift @@ -2,8 +2,8 @@ import Foundation -extension OpenGroupAPI { - public struct SendDirectMessageResponse: Codable, Equatable { +public extension Network.SOGS { + struct SendDirectMessageResponse: Codable, Equatable { enum CodingKeys: String, CodingKey { case id case sender diff --git a/SessionNetworkingKit/SOGS/Models/SendSOGSMessageRequest.swift b/SessionNetworkingKit/SOGS/Models/SendSOGSMessageRequest.swift new file mode 100644 index 0000000000..b6520ad7cd --- /dev/null +++ b/SessionNetworkingKit/SOGS/Models/SendSOGSMessageRequest.swift @@ -0,0 +1,78 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Network.SOGS { + public struct SendSOGSMessageRequest: Codable { + enum CodingKeys: String, CodingKey { + case data + case signature + case whisperTo = "whisper_to" + case whisperMods = "whisper_mods" + case fileIds = "files" + } + + /// The serialized message body (encoded in base64 when encoding) + let data: Data + + /// A 64-byte Ed25519 signature of the message body, signed by the current user's keys (encoded in base64 when + /// encoding - ie. 88 base64 chars) + let signature: Data + + /// If present this indicates that this message is a whisper that should only be shown to the given user (via their sessionId) + let whisperTo: String? + + /// If `true`, then this message will be visible to moderators but not ordinary users + /// + /// If this and `whisper_to` are used together then the message will be visible to the given user and any room + /// moderators (this can be used, for instance, to issue a warning to a user that only the user and other mods can see) + /// + /// **Note:** Only moderators may set this flag + let whisperMods: Bool? + + /// Array of file IDs of new files uploaded as attachments of this post + /// + /// This is required to preserve uploads for the default expiry period (15 days, unless otherwise configured by the SOGS + /// administrator); uploaded files that are not attached to a post will be deleted much sooner + /// + /// If any of the given file ids are already associated with another message then the association is ignored (i.e. the files remain + /// associated with the original message) + /// + /// When submitting a message edit this field must contain the IDs of any newly uploaded files that are part of the edit; existing + /// attachment IDs may also be included, but are not required + /// + /// **Note:** The SOGS API actually expects an array of Int64 (ie. what is returned when uploading a file to SOGS) but + /// when uploading direct to the FileServer we get a string id back. In order to avoid supporting both cases we convert + /// the id returned by SOGS to a string and send those through - luckily SOGS converts the values to ints so supports + /// receipving an array of String values + let fileIds: [String]? + + // MARK: - Initialization + + init( + data: Data, + signature: Data, + whisperTo: String? = nil, + whisperMods: Bool? = nil, + fileIds: [String]? = nil + ) { + self.data = data + self.signature = signature + self.whisperTo = whisperTo + self.whisperMods = whisperMods + self.fileIds = fileIds + } + + // MARK: - Encodable + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(data.base64EncodedString(), forKey: .data) + try container.encode(signature.base64EncodedString(), forKey: .signature) + try container.encodeIfPresent(whisperTo, forKey: .whisperTo) + try container.encodeIfPresent(whisperMods, forKey: .whisperMods) + try container.encodeIfPresent(fileIds, forKey: .fileIds) + } + } +} diff --git a/SessionNetworkingKit/SOGS/Models/UpdateMessageRequest.swift b/SessionNetworkingKit/SOGS/Models/UpdateMessageRequest.swift new file mode 100644 index 0000000000..640a5d92d8 --- /dev/null +++ b/SessionNetworkingKit/SOGS/Models/UpdateMessageRequest.swift @@ -0,0 +1,35 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Network.SOGS { + public struct UpdateMessageRequest: Codable { + /// The serialized message body (encoded in base64 when encoding) + let data: Data + + /// A 64-byte Ed25519 signature of the message body, signed by the current user's keys (encoded in base64 when + /// encoding - ie. 88 base64 chars) + let signature: Data + + /// Array of file IDs of new files uploaded as attachments of this post + /// + /// This is required to preserve uploads for the default expiry period (15 days, unless otherwise configured by the SOGS + /// administrator); uploaded files that are not attached to a post will be deleted much sooner + /// + /// If any of the given file ids are already associated with another message then the association is ignored (i.e. the files remain + /// associated with the original message) + /// + /// This field must contain the IDs of any newly uploaded files that are part of the edit; existing attachment IDs may also be + /// included, but are not required + let fileIds: [Int64]? + + // MARK: - Encodable + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(data.base64EncodedString(), forKey: .data) + try container.encode(signature.base64EncodedString(), forKey: .signature) + } + } +} diff --git a/SessionNetworkingKit/SOGS/Models/UserBanRequest.swift b/SessionNetworkingKit/SOGS/Models/UserBanRequest.swift index caff1a17de..249bf8db0c 100644 --- a/SessionNetworkingKit/SOGS/Models/UserBanRequest.swift +++ b/SessionNetworkingKit/SOGS/Models/UserBanRequest.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { struct UserBanRequest: Codable { /// List of one or more room tokens from which the user should be banned (the invoking user must be a `moderator` /// of all of the given rooms diff --git a/SessionNetworkingKit/SOGS/Models/UserModeratorRequest.swift b/SessionNetworkingKit/SOGS/Models/UserModeratorRequest.swift index ece21d2baa..8151ede9ee 100644 --- a/SessionNetworkingKit/SOGS/Models/UserModeratorRequest.swift +++ b/SessionNetworkingKit/SOGS/Models/UserModeratorRequest.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { struct UserModeratorRequest: Codable { /// List of room tokens to which the moderator status should be applied. The invoking user must be an admin of all of the given rooms. /// diff --git a/SessionNetworkingKit/SOGS/Models/UserUnbanRequest.swift b/SessionNetworkingKit/SOGS/Models/UserUnbanRequest.swift index b0e8a2ab99..d1524d3e4a 100644 --- a/SessionNetworkingKit/SOGS/Models/UserUnbanRequest.swift +++ b/SessionNetworkingKit/SOGS/Models/UserUnbanRequest.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { struct UserUnbanRequest: Codable { /// List of one or more room tokens from which the user should be banned (the invoking user must be a `moderator` /// of all of the given rooms diff --git a/SessionNetworkingKit/SOGS/SOGS.swift b/SessionNetworkingKit/SOGS/SOGS.swift index e69de29bb2..0c5e5ce75e 100644 --- a/SessionNetworkingKit/SOGS/SOGS.swift +++ b/SessionNetworkingKit/SOGS/SOGS.swift @@ -0,0 +1,15 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension Network { + enum SOGS { + public static let legacyDefaultServerIP = "116.203.70.33" + public static let defaultServer = "https://open.getsession.org" + public static let defaultServerPublicKey = "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238" + public static let validTimestampVarianceThreshold: TimeInterval = (6 * 60 * 60) + internal static let maxInactivityPeriodForPolling: TimeInterval = (14 * 24 * 60 * 60) + + public static let workQueue = DispatchQueue(label: "SOGS.workQueue", qos: .userInitiated) // It's important that this is a serial queue + } +} diff --git a/SessionNetworkingKit/SOGS/SOGSAPI.swift b/SessionNetworkingKit/SOGS/SOGSAPI.swift index efe47a4c8c..fcdc911e9d 100644 --- a/SessionNetworkingKit/SOGS/SOGSAPI.swift +++ b/SessionNetworkingKit/SOGS/SOGSAPI.swift @@ -3,25 +3,15 @@ // stringlint:disable import Foundation -import SessionNetworkingKit import SessionUtilitiesKit -public enum OpenGroupAPI { - public struct RoomInfo: Codable { +public extension Network.SOGS { + struct PollRoomInfo: Codable { let roomToken: String let infoUpdates: Int64 let sequenceNumber: Int64 } - // MARK: - Settings - - public static let legacyDefaultServerIP = "116.203.70.33" - public static let defaultServer = "https://open.getsession.org" - public static let defaultServerPublicKey = "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238" - public static let validTimestampVarianceThreshold: TimeInterval = (6 * 60 * 60) - - public static let workQueue = DispatchQueue(label: "OpenGroupAPI.workQueue", qos: .userInitiated) // It's important that this is a serial queue - // MARK: - Batching & Polling /// This is a convenience method which calls `/batch` with a pre-defined set of requests used to update an Open @@ -32,10 +22,11 @@ public enum OpenGroupAPI { /// - Messages (includes additions and deletions) /// - Inbox for the server /// - Outbox for the server - public static func preparedPoll( - roomInfo: [RoomInfo], + static func preparedPoll( + roomInfo: [PollRoomInfo], lastInboxMessageId: Int64, lastOutboxMessageId: Int64, + checkForCommunityMessageRequests: Bool, hasPerformedInitialPoll: Bool, timeSinceLastPoll: TimeInterval, authMethod: AuthenticationMethod, @@ -60,7 +51,7 @@ public enum OpenGroupAPI { // 'maxInactivityPeriod' then just retrieve recent messages instead // of trying to get all messages since the last one retrieved !hasPerformedInitialPoll && - timeSinceLastPoll > CommunityPoller.maxInactivityPeriod + timeSinceLastPoll > maxInactivityPeriodForPolling ) ) @@ -93,7 +84,7 @@ public enum OpenGroupAPI { !supportsBlinding ? [] : [ // Inbox (only check the inbox if the user want's community message requests) - (!dependencies.mutate(cache: .libSession) { $0.get(.checkForCommunityMessageRequests) } ? nil : + (!checkForCommunityMessageRequests ? nil : (lastInboxMessageId == 0 ? try preparedInbox(authMethod: authMethod, using: dependencies) : try preparedInboxSince( @@ -117,13 +108,13 @@ public enum OpenGroupAPI { ) ) - return try OpenGroupAPI + return try Network.SOGS .preparedBatch( requests: preparedRequests, authMethod: authMethod, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Submits multiple requests wrapped up in a single request, runs them all, then returns the result of each one @@ -133,7 +124,7 @@ public enum OpenGroupAPI { /// /// For contained subrequests that specify a body (i.e. POST or PUT requests) exactly one of `json`, `b64`, or `bytes` must be provided /// with the request body. - public static func preparedBatch( + static func preparedBatch( requests: [any ErasedPreparedRequest], authMethod: AuthenticationMethod, using dependencies: Dependencies @@ -149,7 +140,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// This is like `/batch`, except that it guarantees to perform requests sequentially in the order provided and will stop processing requests @@ -178,7 +169,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Capabilities @@ -190,20 +181,20 @@ public enum OpenGroupAPI { /// /// Eg. `GET /capabilities` could return `{"capabilities": ["sogs", "batch"]}` `GET /capabilities?required=magic,batch` /// could return: `{"capabilities": ["sogs", "batch"], "missing": ["magic"]}` - public static func preparedCapabilities( + static func preparedCapabilities( authMethod: AuthenticationMethod, using dependencies: Dependencies - ) throws -> Network.PreparedRequest { + ) throws -> Network.PreparedRequest { return try Network.PreparedRequest( request: Request( endpoint: .capabilities, authMethod: authMethod ), - responseType: Capabilities.self, + responseType: CapabilitiesResponse.self, additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Room @@ -211,7 +202,7 @@ public enum OpenGroupAPI { /// Returns a list of available rooms on the server /// /// Rooms to which the user does not have access (e.g. because they are banned, or the room has restricted access permissions) are not included - public static func preparedRooms( + static func preparedRooms( authMethod: AuthenticationMethod, using dependencies: Dependencies ) throws -> Network.PreparedRequest<[Room]> { @@ -224,11 +215,11 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Returns the details of a single room - public static func preparedRoom( + static func preparedRoom( roomToken: String, authMethod: AuthenticationMethod, using dependencies: Dependencies @@ -242,14 +233,14 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Polls a room for metadata updates /// /// The endpoint polls room metadata for this room, always including the instantaneous room details (such as the user's permission and current /// number of active users), and including the full room metadata if the room's info_updated counter has changed from the provided value - public static func preparedRoomPollInfo( + static func preparedRoomPollInfo( lastUpdated: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -264,22 +255,22 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } - public typealias CapabilitiesAndRoomResponse = ( - capabilities: (info: ResponseInfoType, data: Capabilities), + typealias CapabilitiesAndRoomResponse = ( + capabilities: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesResponse), room: (info: ResponseInfoType, data: Room) ) /// This is a convenience method which constructs a `/sequence` of the `capabilities` and `room` requests, refer to those /// methods for the documented behaviour of each method - public static func preparedCapabilitiesAndRoom( + static func preparedCapabilitiesAndRoom( roomToken: String, authMethod: AuthenticationMethod, using dependencies: Dependencies ) throws -> Network.PreparedRequest { - return try OpenGroupAPI + return try Network.SOGS .preparedSequence( requests: [ // Get the latest capabilities for the server (in case it's a new server or the @@ -290,9 +281,9 @@ public enum OpenGroupAPI { authMethod: authMethod, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) .tryMap { (info: ResponseInfoType, response: Network.BatchResponseMap) -> CapabilitiesAndRoomResponse in - let maybeCapabilities: Network.BatchSubResponse? = (response[.capabilities] as? Network.BatchSubResponse) + let maybeCapabilities: Network.BatchSubResponse? = (response[.capabilities] as? Network.BatchSubResponse) let maybeRoomResponse: Any? = response.data .first(where: { key, _ in switch key { @@ -305,7 +296,7 @@ public enum OpenGroupAPI { guard let capabilitiesInfo: ResponseInfoType = maybeCapabilities, - let capabilities: Capabilities = maybeCapabilities?.body, + let capabilities: Network.SOGS.CapabilitiesResponse = maybeCapabilities?.body, let roomInfo: ResponseInfoType = maybeRoom, let room: Room = maybeRoom?.body else { throw NetworkError.parsingFailed } @@ -317,18 +308,18 @@ public enum OpenGroupAPI { } } - public typealias CapabilitiesAndRoomsResponse = ( - capabilities: (info: ResponseInfoType, data: Capabilities), + typealias CapabilitiesAndRoomsResponse = ( + capabilities: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesResponse), rooms: (info: ResponseInfoType, data: [Room]) ) /// This is a convenience method which constructs a `/sequence` of the `capabilities` and `rooms` requests, refer to those /// methods for the documented behaviour of each method - public static func preparedCapabilitiesAndRooms( + static func preparedCapabilitiesAndRooms( authMethod: AuthenticationMethod, using dependencies: Dependencies ) throws -> Network.PreparedRequest { - return try OpenGroupAPI + return try Network.SOGS .preparedSequence( requests: [ // Get the latest capabilities for the server (in case it's a new server or the @@ -339,9 +330,9 @@ public enum OpenGroupAPI { authMethod: authMethod, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) .tryMap { (info: ResponseInfoType, response: Network.BatchResponseMap) -> CapabilitiesAndRoomsResponse in - let maybeCapabilities: Network.BatchSubResponse? = (response[.capabilities] as? Network.BatchSubResponse) + let maybeCapabilities: Network.BatchSubResponse? = (response[.capabilities] as? Network.BatchSubResponse) let maybeRooms: Network.BatchSubResponse<[Room]>? = response.data .first(where: { key, _ in switch key { @@ -353,7 +344,7 @@ public enum OpenGroupAPI { guard let capabilitiesInfo: ResponseInfoType = maybeCapabilities, - let capabilities: Capabilities = maybeCapabilities?.body, + let capabilities: Network.SOGS.CapabilitiesResponse = maybeCapabilities?.body, let roomsInfo: ResponseInfoType = maybeRooms, let roomsResponse: Network.BatchSubResponse<[Room]> = maybeRooms, !roomsResponse.failedToParseBody @@ -370,7 +361,7 @@ public enum OpenGroupAPI { // MARK: - Messages /// Posts a new message to a room - public static func preparedSend( + static func preparedSend( plaintext: Data, roomToken: String, whisperTo: String?, @@ -390,7 +381,7 @@ public enum OpenGroupAPI { request: Request( method: .post, endpoint: Endpoint.roomMessage(roomToken), - body: SendMessageRequest( + body: SendSOGSMessageRequest( data: plaintext, signature: Data(signResult.signature), whisperTo: whisperTo, @@ -403,11 +394,11 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Returns a single message by ID - public static func preparedMessage( + static func preparedMessage( id: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -422,13 +413,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Edits a message, replacing its existing content with new content and a new signature /// /// **Note:** This edit may only be initiated by the creator of the post, and the poster must currently have write permissions in the room - public static func preparedMessageUpdate( + static func preparedMessageUpdate( id: Int64, plaintext: Data, fileIds: [Int64]?, @@ -458,11 +449,11 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Remove a message by its message id - public static func preparedMessageDelete( + static func preparedMessageDelete( id: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -478,7 +469,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Retrieves recent messages posted to this room @@ -486,7 +477,7 @@ public enum OpenGroupAPI { /// Returns the most recent limit messages (100 if no limit is given). This only returns extant messages, and always returns the latest /// versions: that is, deleted message indicators and pre-editing versions of messages are not returned. Messages are returned in order /// from most recent to least recent - public static func preparedRecentMessages( + static func preparedRecentMessages( roomToken: String, authMethod: AuthenticationMethod, using dependencies: Dependencies @@ -505,7 +496,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Retrieves messages from the room preceding a given id. @@ -514,7 +505,7 @@ public enum OpenGroupAPI { /// through batches of ever-older messages. As with .../recent, messages are returned in order from most recent to least recent. /// /// As with .../recent, this endpoint does not include deleted messages and always returns the current version, for edited messages. - public static func preparedMessagesBefore( + static func preparedMessagesBefore( messageId: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -534,7 +525,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Retrieves message updates from a room. This is the main message polling endpoint in SOGS. @@ -543,7 +534,7 @@ public enum OpenGroupAPI { /// sequence counter. Returns limit messages at a time (100 if no limit is given). Returned messages include any new messages, updates /// to existing messages (i.e. edits), and message deletions made to the room since the given update id. Messages are returned in "update" /// order, that is, in the order in which the change was applied to the room, from oldest the newest. - public static func preparedMessagesSince( + static func preparedMessagesSince( seqNo: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -563,7 +554,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Deletes all messages from a given sessionId within the provided rooms (or globally) on a server @@ -579,7 +570,7 @@ public enum OpenGroupAPI { /// - server: The server to delete messages from /// /// - dependencies: Injected dependencies (used for unit testing) - public static func preparedMessagesDeleteAll( + static func preparedMessagesDeleteAll( sessionId: String, roomToken: String, authMethod: AuthenticationMethod, @@ -595,13 +586,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Reactions /// Returns the list of all reactors who have added a particular reaction to a particular message. - public static func preparedReactors( + static func preparedReactors( emoji: String, id: Int64, roomToken: String, @@ -611,7 +602,7 @@ public enum OpenGroupAPI { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - throw OpenGroupAPIError.invalidEmoji + throw SOGSError.invalidEmoji } return try Network.PreparedRequest( @@ -624,14 +615,14 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Adds a reaction to the given message in this room. The user must have read access in the room. /// /// Reactions are short strings of 1-12 unicode codepoints, typically emoji (or character sequences to produce an emoji variant, /// such as 👨🏿‍🦰, which is composed of 4 unicode "characters" but usually renders as a single emoji "Man: Dark Skin Tone, Red Hair"). - public static func preparedReactionAdd( + static func preparedReactionAdd( emoji: String, id: Int64, roomToken: String, @@ -641,7 +632,7 @@ public enum OpenGroupAPI { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - throw OpenGroupAPIError.invalidEmoji + throw SOGSError.invalidEmoji } return try Network.PreparedRequest( @@ -654,12 +645,12 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Removes a reaction from a post this room. The user must have read access in the room. This only removes the user's own reaction /// but does not affect the reactions of other users. - public static func preparedReactionDelete( + static func preparedReactionDelete( emoji: String, id: Int64, roomToken: String, @@ -669,7 +660,7 @@ public enum OpenGroupAPI { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - throw OpenGroupAPIError.invalidEmoji + throw SOGSError.invalidEmoji } return try Network.PreparedRequest( @@ -682,13 +673,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Removes all reactions of all users from a post in this room. The calling must have moderator permissions in the room. This endpoint /// can either remove a single reaction (e.g. remove all 🍆 reactions) by specifying it after the message id (following a /), or remove all /// reactions from the post by not including the / suffix of the URL. - public static func preparedReactionDeleteAll( + static func preparedReactionDeleteAll( emoji: String, id: Int64, roomToken: String, @@ -698,7 +689,7 @@ public enum OpenGroupAPI { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - throw OpenGroupAPIError.invalidEmoji + throw SOGSError.invalidEmoji } return try Network.PreparedRequest( @@ -711,7 +702,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Pinning @@ -726,7 +717,7 @@ public enum OpenGroupAPI { /// Pinned messages that are already pinned will be re-pinned (that is, their pin timestamp and pinning admin user will be updated) - because pinned /// messages are returned in pinning-order this allows admins to order multiple pinned messages in a room by re-pinning (via this endpoint) in the /// order in which pinned messages should be displayed - public static func preparedPinMessage( + static func preparedPinMessage( id: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -742,13 +733,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Remove a message from this room's pinned message list /// /// The user must have `admin` (not just `moderator`) permissions in the room - public static func preparedUnpinMessage( + static func preparedUnpinMessage( id: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -764,13 +755,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Removes _all_ pinned messages from this room /// /// The user must have `admin` (not just `moderator`) permissions in the room - public static func preparedUnpinAll( + static func preparedUnpinAll( roomToken: String, authMethod: AuthenticationMethod, using dependencies: Dependencies @@ -785,12 +776,12 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Files - public static func preparedUpload( + static func preparedUpload( data: Data, roomToken: String, fileName: String? = nil, @@ -816,10 +807,10 @@ public enum OpenGroupAPI { requestTimeout: Network.fileUploadTimeout, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } - public static func downloadUrlString( + static func downloadUrlString( for fileId: String, server: String, roomToken: String @@ -827,18 +818,20 @@ public enum OpenGroupAPI { return "\(server)/\(Endpoint.roomFileIndividual(roomToken, fileId).path)" } - public static func preparedDownload( + static func preparedDownload( url: URL, roomToken: String, authMethod: AuthenticationMethod, using dependencies: Dependencies ) throws -> Network.PreparedRequest { - guard let fileId: String = Attachment.fileId(for: url.absoluteString) else { throw NetworkError.invalidURL } + guard let fileId: String = Network.FileServer.fileId(for: url.absoluteString) else { + throw NetworkError.invalidURL + } return try preparedDownload(fileId: fileId, roomToken: roomToken, authMethod: authMethod, using: dependencies) } - public static func preparedDownload( + static func preparedDownload( fileId: String, roomToken: String, authMethod: AuthenticationMethod, @@ -854,7 +847,7 @@ public enum OpenGroupAPI { requestTimeout: Network.fileDownloadTimeout, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Inbox/Outbox (Message Requests) @@ -862,7 +855,7 @@ public enum OpenGroupAPI { /// Retrieves all of the user's current DMs (up to limit) /// /// **Note:** `inbox` will return a `304` with an empty response if no messages (hence the optional return type) - public static func preparedInbox( + static func preparedInbox( authMethod: AuthenticationMethod, using dependencies: Dependencies ) throws -> Network.PreparedRequest<[DirectMessage]?> { @@ -875,13 +868,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Polls for any DMs received since the given id, this method will return a `304` with an empty response if there are no messages /// /// **Note:** `inboxSince` will return a `304` with an empty response if no messages (hence the optional return type) - public static func preparedInboxSince( + static func preparedInboxSince( id: Int64, authMethod: AuthenticationMethod, using dependencies: Dependencies @@ -895,11 +888,11 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Remove all message requests from inbox, this methrod will return the number of messages deleted - public static func preparedClearInbox( + static func preparedClearInbox( requestTimeout: TimeInterval = Network.defaultTimeout, requestAndPathBuildTimeout: TimeInterval? = nil, authMethod: AuthenticationMethod, @@ -917,13 +910,13 @@ public enum OpenGroupAPI { requestAndPathBuildTimeout: requestAndPathBuildTimeout, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Delivers a direct message to a user via their blinded Session ID /// /// The body of this request is a JSON object containing a message key with a value of the encrypted-then-base64-encoded message to deliver - public static func preparedSend( + static func preparedSend( ciphertext: Data, toInboxFor blindedSessionId: String, authMethod: AuthenticationMethod, @@ -942,13 +935,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Retrieves all of the user's sent DMs (up to limit) /// /// **Note:** `outbox` will return a `304` with an empty response if no messages (hence the optional return type) - public static func preparedOutbox( + static func preparedOutbox( authMethod: AuthenticationMethod, using dependencies: Dependencies ) throws -> Network.PreparedRequest<[DirectMessage]?> { @@ -961,13 +954,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Polls for any DMs sent since the given id, this method will return a `304` with an empty response if there are no messages /// /// **Note:** `outboxSince` will return a `304` with an empty response if no messages (hence the optional return type) - public static func preparedOutboxSince( + static func preparedOutboxSince( id: Int64, authMethod: AuthenticationMethod, using dependencies: Dependencies @@ -981,7 +974,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Users @@ -1017,7 +1010,7 @@ public enum OpenGroupAPI { /// - server: The server to delete messages from /// /// - dependencies: Injected dependencies (used for unit testing) - public static func preparedUserBan( + static func preparedUserBan( sessionId: String, for timeout: TimeInterval? = nil, from roomTokens: [String]? = nil, @@ -1039,7 +1032,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Removes a user ban from specific rooms, or from the server globally @@ -1066,7 +1059,7 @@ public enum OpenGroupAPI { /// - server: The server to delete messages from /// /// - dependencies: Injected dependencies (used for unit testing) - public static func preparedUserUnban( + static func preparedUserUnban( sessionId: String, from roomTokens: [String]?, authMethod: AuthenticationMethod, @@ -1086,7 +1079,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Appoints or removes a moderator or admin @@ -1140,7 +1133,7 @@ public enum OpenGroupAPI { /// - server: The server to perform the permission changes on /// /// - dependencies: Injected dependencies (used for unit testing) - public static func preparedUserModeratorUpdate( + static func preparedUserModeratorUpdate( sessionId: String, moderator: Bool? = nil, admin: Bool? = nil, @@ -1170,18 +1163,18 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// This is a convenience method which constructs a `/sequence` of the `userBan` and `userDeleteMessages` requests, refer to those /// methods for the documented behaviour of each method - public static func preparedUserBanAndDeleteAllMessages( + static func preparedUserBanAndDeleteAllMessages( sessionId: String, roomToken: String, authMethod: AuthenticationMethod, using dependencies: Dependencies ) throws -> Network.PreparedRequest> { - return try OpenGroupAPI + return try Network.SOGS .preparedSequence( requests: [ preparedUserBan( @@ -1200,7 +1193,7 @@ public enum OpenGroupAPI { authMethod: authMethod, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Authentication @@ -1222,7 +1215,7 @@ public enum OpenGroupAPI { !publicKey.isEmpty, let nonce: [UInt8] = dependencies[singleton: .crypto].generate(.randomBytes(16)), let timestampBytes: [UInt8] = "\(timestamp)".data(using: .ascii).map({ Array($0) }) - else { throw OpenGroupAPIError.signingFailed } + else { throw SOGSError.signingFailed } /// Get a hash of any body content let bodyHash: [UInt8]? = { @@ -1273,7 +1266,7 @@ public enum OpenGroupAPI { !dependencies[cache: .general].ed25519SecretKey.isEmpty, !dependencies[cache: .general].ed25519Seed.isEmpty, case .community(_, let publicKey, let hasCapabilities, let supportsBlinding, let forceBlinded) = authMethod.info - else { throw OpenGroupAPIError.signingFailed } + else { throw SOGSError.signingFailed } // If we have no capabilities or if the server supports blinded keys then sign using the blinded key if forceBlinded || !hasCapabilities || supportsBlinding { @@ -1291,7 +1284,7 @@ public enum OpenGroupAPI { ed25519SecretKey: dependencies[cache: .general].ed25519SecretKey ) ) - else { throw OpenGroupAPIError.signingFailed } + else { throw SOGSError.signingFailed } return ( publicKey: SessionId(.blinded15, publicKey: blinded15KeyPair.publicKey).hexString, @@ -1313,7 +1306,7 @@ public enum OpenGroupAPI { .ed25519KeyPair(seed: dependencies[cache: .general].ed25519Seed) ), case .standard(let signatureResult) = signature - else { throw OpenGroupAPIError.signingFailed } + else { throw SOGSError.signingFailed } return ( publicKey: SessionId(.unblinded, publicKey: ed25519KeyPair.publicKey).hexString, @@ -1335,7 +1328,7 @@ public enum OpenGroupAPI { let signatureResult: [UInt8] = dependencies[singleton: .crypto].generate( .signatureXed25519(data: messageBytes, curve25519PrivateKey: x25519SecretKey) ) - else { throw OpenGroupAPIError.signingFailed } + else { throw SOGSError.signingFailed } return ( publicKey: SessionId(.standard, publicKey: x25519PublicKey).hexString, @@ -1350,7 +1343,7 @@ public enum OpenGroupAPI { using dependencies: Dependencies ) throws -> Network.Destination { guard let signingData: AdditionalSigningData = preparedRequest.additionalSignatureData as? AdditionalSigningData else { - throw OpenGroupAPIError.signingFailed + throw SOGSError.signingFailed } return try preparedRequest.destination @@ -1358,7 +1351,7 @@ public enum OpenGroupAPI { } } -private extension OpenGroupAPI { +private extension Network.SOGS { struct AdditionalSigningData { let authMethod: AuthenticationMethod @@ -1369,7 +1362,7 @@ private extension OpenGroupAPI { } private extension Network.Destination { - func signed(data: OpenGroupAPI.AdditionalSigningData, body: Data?, using dependencies: Dependencies) throws -> Network.Destination { + func signed(data: Network.SOGS.AdditionalSigningData, body: Data?, using dependencies: Dependencies) throws -> Network.Destination { switch self { case .snode, .randomSnode, .randomSnodeLatestNetworkTimeTarget: throw NetworkError.unauthorised case .cached: return self @@ -1384,8 +1377,8 @@ private extension Network.Destination { } private extension Network.Destination.ServerInfo { - func signed(_ data: OpenGroupAPI.AdditionalSigningData, _ body: Data?, using dependencies: Dependencies) throws -> Network.Destination.ServerInfo { - return updated(with: try OpenGroupAPI.signatureHeaders( + func signed(_ data: Network.SOGS.AdditionalSigningData, _ body: Data?, using dependencies: Dependencies) throws -> Network.Destination.ServerInfo { + return updated(with: try Network.SOGS.signatureHeaders( url: url, method: method, body: body, diff --git a/SessionNetworkingKit/SOGS/SOGSEndpoint.swift b/SessionNetworkingKit/SOGS/SOGSEndpoint.swift index e5e0b9bd34..218bfadbe3 100644 --- a/SessionNetworkingKit/SOGS/SOGSEndpoint.swift +++ b/SessionNetworkingKit/SOGS/SOGSEndpoint.swift @@ -3,10 +3,9 @@ // stringlint:disable import Foundation -import SessionNetworkingKit -extension OpenGroupAPI { - public enum Endpoint: EndpointType { +public extension Network.SOGS { + enum Endpoint: EndpointType { // Utility case onion @@ -61,7 +60,7 @@ extension OpenGroupAPI { case userUnban(String) case userModerator(String) - public static var name: String { "OpenGroupAPI.Endpoint" } + public static var name: String { "SOGS.Endpoint" } public static var batchRequestVariant: Network.BatchRequest.Child.Variant = .sogs public static var excludedSubRequestHeaders: [HTTPHeader] = [ .sogsPubKey, .sogsTimestamp, .sogsNonce, .sogsSignature diff --git a/SessionNetworkingKit/SOGS/SOGSError.swift b/SessionNetworkingKit/SOGS/SOGSError.swift index d5ab81cbe1..bf91640de0 100644 --- a/SessionNetworkingKit/SOGS/SOGSError.swift +++ b/SessionNetworkingKit/SOGS/SOGSError.swift @@ -4,7 +4,7 @@ import Foundation -public enum OpenGroupAPIError: Error, CustomStringConvertible { +public enum SOGSError: Error, CustomStringConvertible { case decryptionFailed case signingFailed case noPublicKey diff --git a/SessionNetworkingKit/SOGS/Types/HTTPHeader+SOGS.swift b/SessionNetworkingKit/SOGS/Types/HTTPHeader+SOGS.swift index 29189d3cef..0c100cfbd1 100644 --- a/SessionNetworkingKit/SOGS/Types/HTTPHeader+SOGS.swift +++ b/SessionNetworkingKit/SOGS/Types/HTTPHeader+SOGS.swift @@ -3,7 +3,6 @@ // stringlint:disable import Foundation -import SessionNetworkingKit public extension HTTPHeader { static let sogsPubKey: HTTPHeader = "X-SOGS-Pubkey" diff --git a/SessionNetworkingKit/SOGS/Types/HTTPQueryParam+SOGS.swift b/SessionNetworkingKit/SOGS/Types/HTTPQueryParam+SOGS.swift index 4eb7f6c206..dd6c86e3c5 100644 --- a/SessionNetworkingKit/SOGS/Types/HTTPQueryParam+SOGS.swift +++ b/SessionNetworkingKit/SOGS/Types/HTTPQueryParam+SOGS.swift @@ -3,7 +3,6 @@ // stringlint:disable import Foundation -import SessionNetworkingKit public extension HTTPQueryParam { static let publicKey: HTTPQueryParam = "public_key" diff --git a/SessionNetworkingKit/SOGS/Types/Personalization.swift b/SessionNetworkingKit/SOGS/Types/Personalization.swift new file mode 100644 index 0000000000..5b24626fe9 --- /dev/null +++ b/SessionNetworkingKit/SOGS/Types/Personalization.swift @@ -0,0 +1,14 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Network.SOGS { + public enum Personalization: String { + case sharedKeys = "sogs.shared_keys" + case authHeader = "sogs.auth_header" + + var bytes: [UInt8] { + return Array(self.rawValue.utf8) + } + } +} diff --git a/SessionNetworkingKit/SOGS/Types/Request+SOGS.swift b/SessionNetworkingKit/SOGS/Types/Request+SOGS.swift index 5c8d72187f..5373174584 100644 --- a/SessionNetworkingKit/SOGS/Types/Request+SOGS.swift +++ b/SessionNetworkingKit/SOGS/Types/Request+SOGS.swift @@ -1,13 +1,9 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation -import GRDB -import SessionNetworkingKit import SessionUtilitiesKit -// MARK: Request - OpenGroupAPI - -public extension Request where Endpoint == OpenGroupAPI.Endpoint { +public extension Request where Endpoint == Network.SOGS.Endpoint { init( method: HTTPMethod = .get, endpoint: Endpoint, diff --git a/SessionNetworkingKit/SOGS/Types/UpdateTypes.swift b/SessionNetworkingKit/SOGS/Types/UpdateTypes.swift new file mode 100644 index 0000000000..61baf5ee47 --- /dev/null +++ b/SessionNetworkingKit/SOGS/Types/UpdateTypes.swift @@ -0,0 +1,9 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Network.SOGS { + enum UpdateTypes: String { + case reaction = "r" + } +} diff --git a/SessionNetworkingKit/SessionNetwork/SessionNetworkAPI.swift b/SessionNetworkingKit/SessionNetwork/SessionNetworkAPI.swift index c28f83d126..1d11aa7888 100644 --- a/SessionNetworkingKit/SessionNetwork/SessionNetworkAPI.swift +++ b/SessionNetworkingKit/SessionNetwork/SessionNetworkAPI.swift @@ -17,20 +17,20 @@ public extension Network.SessionNetwork { using dependencies: Dependencies ) throws -> Network.PreparedRequest { return try Network.PreparedRequest( - request: Request( - endpoint: Network.NetworkAPI.Endpoint.info, + request: Request( + endpoint: Network.SessionNetwork.Endpoint.info, destination: .server( method: .get, - server: Network.NetworkAPI.networkAPIServer, + server: Network.SessionNetwork.networkAPIServer, queryParameters: [:], - x25519PublicKey: Network.NetworkAPI.networkAPIServerPublicKey + x25519PublicKey: Network.SessionNetwork.networkAPIServerPublicKey ) ), responseType: Info.self, requestAndPathBuildTimeout: Network.defaultTimeout, using: dependencies ) - .signed(with: SessionNetworkAPI.signRequest, using: dependencies) + .signed(with: Network.SessionNetwork.signRequest, using: dependencies) } // MARK: - Authentication diff --git a/SessionNetworkingKit/StorageServer/Database/SnodeReceivedMessageInfo.swift b/SessionNetworkingKit/StorageServer/Database/SnodeReceivedMessageInfo.swift index a54cbc083f..eb8857f776 100644 --- a/SessionNetworkingKit/StorageServer/Database/SnodeReceivedMessageInfo.swift +++ b/SessionNetworkingKit/StorageServer/Database/SnodeReceivedMessageInfo.swift @@ -55,7 +55,7 @@ public extension SnodeReceivedMessageInfo { init( snode: LibSession.Snode, swarmPublicKey: String, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, hash: String, expirationDateMs: Int64? ) { @@ -75,7 +75,7 @@ public extension SnodeReceivedMessageInfo { static func fetchLastNotExpired( _ db: ObservingDatabase, for snode: LibSession.Snode, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, swarmPublicKey: String, using dependencies: Dependencies ) throws -> SnodeReceivedMessageInfo? { diff --git a/SessionNetworkingKit/StorageServer/Models/AppVersionResponse.swift b/SessionNetworkingKit/StorageServer/Models/AppVersionResponse.swift deleted file mode 100644 index ae5c33739b..0000000000 --- a/SessionNetworkingKit/StorageServer/Models/AppVersionResponse.swift +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -public class AppVersionResponse: AppVersionInfo { - enum CodingKeys: String, CodingKey { - case prerelease - } - - public let prerelease: AppVersionInfo? - - public init( - version: String, - updated: TimeInterval?, - name: String?, - notes: String?, - assets: [Asset]?, - prerelease: AppVersionInfo? - ) { - self.prerelease = prerelease - - super.init( - version: version, - updated: updated, - name: name, - notes: notes, - assets: assets - ) - } - - required init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - - self.prerelease = try? container.decode(AppVersionInfo?.self, forKey: .prerelease) - - try super.init(from: decoder) - } - - public override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - - var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - try container.encodeIfPresent(prerelease, forKey: .prerelease) - } -} - -// MARK: - AppVersionInfo - -public class AppVersionInfo: Codable { - enum CodingKeys: String, CodingKey { - case version = "result" - case updated - case name - case notes - case assets - } - - public struct Asset: Codable { - enum CodingKeys: String, CodingKey { - case name - case url - } - - public let name: String - public let url: String - } - - public let version: String - public let updated: TimeInterval? - public let name: String? - public let notes: String? - public let assets: [Asset]? - - public init( - version: String, - updated: TimeInterval?, - name: String?, - notes: String?, - assets: [Asset]? - ) { - self.version = version - self.updated = updated - self.name = name - self.notes = notes - self.assets = assets - } - - public func encode(to encoder: Encoder) throws { - var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(version, forKey: .version) - try container.encodeIfPresent(updated, forKey: .updated) - try container.encodeIfPresent(name, forKey: .name) - try container.encodeIfPresent(notes, forKey: .notes) - try container.encodeIfPresent(assets, forKey: .assets) - } -} diff --git a/SessionNetworkingKit/StorageServer/Models/DeleteAllBeforeRequest.swift b/SessionNetworkingKit/StorageServer/Models/DeleteAllBeforeRequest.swift index 6e6e1e64af..bd720328f5 100644 --- a/SessionNetworkingKit/StorageServer/Models/DeleteAllBeforeRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/DeleteAllBeforeRequest.swift @@ -5,22 +5,22 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public final class DeleteAllBeforeRequest: SnodeAuthenticatedRequestBody, UpdatableTimestamp { +extension Network.SnodeAPI { + final class DeleteAllBeforeRequest: SnodeAuthenticatedRequestBody, UpdatableTimestamp { enum CodingKeys: String, CodingKey { case beforeMs = "before" case namespace } let beforeMs: UInt64 - let namespace: SnodeAPI.Namespace? + let namespace: Network.SnodeAPI.Namespace? override var verificationBytes: [UInt8] { /// Ed25519 signature of `("delete_before" || namespace || before)`, signed by /// `pubkey`. Must be base64 encoded (json) or bytes (OMQ). `namespace` is the stringified /// version of the given non-default namespace parameter (i.e. "-42" or "all"), or the empty /// string for the default namespace (whether explicitly given or not). - SnodeAPI.Endpoint.deleteAllBefore.path.bytes + Network.SnodeAPI.Endpoint.deleteAllBefore.path.bytes .appending( contentsOf: (namespace == nil ? "all" : @@ -34,7 +34,7 @@ extension SnodeAPI { public init( beforeMs: UInt64, - namespace: SnodeAPI.Namespace?, + namespace: Network.SnodeAPI.Namespace?, authMethod: AuthenticationMethod, timestampMs: UInt64 ) { diff --git a/SessionNetworkingKit/StorageServer/Models/DeleteAllMessagesRequest.swift b/SessionNetworkingKit/StorageServer/Models/DeleteAllMessagesRequest.swift index 57f0c28f2b..3dc0d5bdbe 100644 --- a/SessionNetworkingKit/StorageServer/Models/DeleteAllMessagesRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/DeleteAllMessagesRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public final class DeleteAllMessagesRequest: SnodeAuthenticatedRequestBody, UpdatableTimestamp { +extension Network.SnodeAPI { + final class DeleteAllMessagesRequest: SnodeAuthenticatedRequestBody, UpdatableTimestamp { enum CodingKeys: String, CodingKey { case namespace } @@ -14,7 +14,7 @@ extension SnodeAPI { /// /// **Note:** If omitted when sending the request, messages are deleted from the default namespace /// only (namespace 0) - let namespace: SnodeAPI.Namespace + let namespace: Network.SnodeAPI.Namespace override var verificationBytes: [UInt8] { /// Ed25519 signature of `( "delete_all" || namespace || timestamp )`, where @@ -22,7 +22,7 @@ extension SnodeAPI { /// not), and otherwise the stringified version of the namespace parameter (i.e. "99" or "-42" or "all"). /// The signature must be signed by the ed25519 pubkey in `pubkey` (omitting the leading prefix). /// Must be base64 encoded for json requests; binary for OMQ requests. - SnodeAPI.Endpoint.deleteAll.path.bytes + Network.SnodeAPI.Endpoint.deleteAll.path.bytes .appending(contentsOf: namespace.verificationString.bytes) .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) } @@ -30,7 +30,7 @@ extension SnodeAPI { // MARK: - Init public init( - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, authMethod: AuthenticationMethod, timestampMs: UInt64 ) { diff --git a/SessionNetworkingKit/StorageServer/Models/DeleteMessagesRequest.swift b/SessionNetworkingKit/StorageServer/Models/DeleteMessagesRequest.swift index 4adc127240..c1736499ac 100644 --- a/SessionNetworkingKit/StorageServer/Models/DeleteMessagesRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/DeleteMessagesRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class DeleteMessagesRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class DeleteMessagesRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case messageHashes = "messages" case requireSuccessfulDeletion = "required" @@ -17,7 +17,7 @@ extension SnodeAPI { /// Ed25519 signature of `("delete" || messages...)`; this signs the value constructed /// by concatenating "delete" and all `messages` values, using `pubkey` to sign. Must be base64 /// encoded for json requests; binary for OMQ requests. - SnodeAPI.Endpoint.deleteMessages.path.bytes + Network.SnodeAPI.Endpoint.deleteMessages.path.bytes .appending(contentsOf: messageHashes.joined().bytes) } diff --git a/SessionNetworkingKit/StorageServer/Models/FileUploadResponse.swift b/SessionNetworkingKit/StorageServer/Models/FileUploadResponse.swift deleted file mode 100644 index 41ba747b0f..0000000000 --- a/SessionNetworkingKit/StorageServer/Models/FileUploadResponse.swift +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -public struct FileUploadResponse: Codable { - public let id: String - - public init(id: String) { - self.id = id - } -} - -// MARK: - Codable - -extension FileUploadResponse { - public init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - - // Note: SOGS returns an 'int' value but we want to avoid handling both cases so parse - // that and convert the value to a string so we can be consistent (SOGS is able to handle - // an array of Strings for the `files` param when posting a message just fine) - if let intValue: Int64 = try? container.decode(Int64.self, forKey: .id) { - self = FileUploadResponse(id: "\(intValue)") - return - } - - self = FileUploadResponse( - id: try container.decode(String.self, forKey: .id) - ) - } -} diff --git a/SessionNetworkingKit/StorageServer/Models/GetExpiriesRequest.swift b/SessionNetworkingKit/StorageServer/Models/GetExpiriesRequest.swift index 091729fb3b..d1f85ebf2b 100644 --- a/SessionNetworkingKit/StorageServer/Models/GetExpiriesRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/GetExpiriesRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class GetExpiriesRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class GetExpiriesRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case messageHashes = "messages" } @@ -16,7 +16,7 @@ extension SnodeAPI { override var verificationBytes: [UInt8] { /// Ed25519 signature of `("get_expiries" || timestamp || messages[0] || ... || messages[N])` /// where `timestamp` is expressed as a string (base10). The signature must be base64 encoded (json) or bytes (bt). - SnodeAPI.Endpoint.getExpiries.path.bytes + Network.SnodeAPI.Endpoint.getExpiries.path.bytes .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) .appending(contentsOf: messageHashes.joined().bytes) } diff --git a/SessionNetworkingKit/StorageServer/Models/GetMessagesRequest.swift b/SessionNetworkingKit/StorageServer/Models/GetMessagesRequest.swift index 3b7e7ca05b..c0c3f0ef7b 100644 --- a/SessionNetworkingKit/StorageServer/Models/GetMessagesRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/GetMessagesRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class GetMessagesRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class GetMessagesRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case lastHash = "last_hash" case namespace @@ -13,7 +13,7 @@ extension SnodeAPI { } let lastHash: String - let namespace: SnodeAPI.Namespace? + let namespace: Network.SnodeAPI.Namespace? let maxCount: Int64? let maxSize: Int64? @@ -22,7 +22,7 @@ extension SnodeAPI { /// namespace), or `("retrieve" || timestamp)` when fetching from the default namespace. Both /// namespace and timestamp are the base10 expressions of the relevant values. Must be base64 /// encoded for json requests; binary for OMQ requests. - SnodeAPI.Endpoint.getMessages.path.bytes + Network.SnodeAPI.Endpoint.getMessages.path.bytes .appending(contentsOf: namespace?.verificationString.bytes) .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) } @@ -31,7 +31,7 @@ extension SnodeAPI { public init( lastHash: String, - namespace: SnodeAPI.Namespace?, + namespace: Network.SnodeAPI.Namespace?, authMethod: AuthenticationMethod, timestampMs: UInt64, maxCount: Int64? = nil, diff --git a/SessionNetworkingKit/StorageServer/Models/GetNetworkTimestampResponse.swift b/SessionNetworkingKit/StorageServer/Models/GetNetworkTimestampResponse.swift index 71428bab9d..d29488ffc2 100644 --- a/SessionNetworkingKit/StorageServer/Models/GetNetworkTimestampResponse.swift +++ b/SessionNetworkingKit/StorageServer/Models/GetNetworkTimestampResponse.swift @@ -2,8 +2,8 @@ import Foundation -extension SnodeAPI { - public struct GetNetworkTimestampResponse: Decodable { +public extension Network.SnodeAPI { + struct GetNetworkTimestampResponse: Decodable { enum CodingKeys: String, CodingKey { case timestamp case version diff --git a/SessionNetworkingKit/StorageServer/Models/LegacyGetMessagesRequest.swift b/SessionNetworkingKit/StorageServer/Models/LegacyGetMessagesRequest.swift index 70dc7aa3a8..ab008a94bb 100644 --- a/SessionNetworkingKit/StorageServer/Models/LegacyGetMessagesRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/LegacyGetMessagesRequest.swift @@ -2,9 +2,9 @@ import Foundation -extension SnodeAPI { +extension Network.SnodeAPI { /// This is the legacy unauthenticated message retrieval request - public struct LegacyGetMessagesRequest: Encodable { + struct LegacyGetMessagesRequest: Encodable { enum CodingKeys: String, CodingKey { case pubkey case lastHash = "last_hash" @@ -15,7 +15,7 @@ extension SnodeAPI { let pubkey: String let lastHash: String - let namespace: SnodeAPI.Namespace? + let namespace: Network.SnodeAPI.Namespace? let maxCount: Int64? let maxSize: Int64? diff --git a/SessionNetworkingKit/StorageServer/Models/LegacySendMessageRequest.swift b/SessionNetworkingKit/StorageServer/Models/LegacySendMessageRequest.swift index 08cfe72ef6..a9c1000119 100644 --- a/SessionNetworkingKit/StorageServer/Models/LegacySendMessageRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/LegacySendMessageRequest.swift @@ -2,15 +2,15 @@ import Foundation -extension SnodeAPI { +extension Network.SnodeAPI { /// This is the legacy unauthenticated message store request - public struct LegacySendMessagesRequest: Encodable { + struct LegacySendMessagesRequest: Encodable { enum CodingKeys: String, CodingKey { case namespace } let message: SnodeMessage - let namespace: SnodeAPI.Namespace + let namespace: Network.SnodeAPI.Namespace // MARK: - Coding diff --git a/SessionNetworkingKit/StorageServer/Models/ONSResolveRequest.swift b/SessionNetworkingKit/StorageServer/Models/ONSResolveRequest.swift index eaef290853..2e0534cf18 100644 --- a/SessionNetworkingKit/StorageServer/Models/ONSResolveRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/ONSResolveRequest.swift @@ -2,8 +2,8 @@ import Foundation -extension SnodeAPI { - public struct ONSResolveRequest: Encodable { +extension Network.SnodeAPI { + struct ONSResolveRequest: Encodable { enum CodingKeys: String, CodingKey { case type case base64EncodedNameHash = "name_hash" diff --git a/SessionNetworkingKit/StorageServer/Models/ONSResolveResponse.swift b/SessionNetworkingKit/StorageServer/Models/ONSResolveResponse.swift index 8ca850a123..527efed87a 100644 --- a/SessionNetworkingKit/StorageServer/Models/ONSResolveResponse.swift +++ b/SessionNetworkingKit/StorageServer/Models/ONSResolveResponse.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class ONSResolveResponse: SnodeResponse { +public extension Network.SnodeAPI { + class ONSResolveResponse: SnodeResponse { internal struct Result: Codable { enum CodingKeys: String, CodingKey { case nonce diff --git a/SessionNetworkingKit/StorageServer/Models/OxenDaemonRPCRequest.swift b/SessionNetworkingKit/StorageServer/Models/OxenDaemonRPCRequest.swift index 1b9d6ea453..93ff3933a2 100644 --- a/SessionNetworkingKit/StorageServer/Models/OxenDaemonRPCRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/OxenDaemonRPCRequest.swift @@ -2,20 +2,22 @@ import Foundation -public struct OxenDaemonRPCRequest: Encodable { - private enum CodingKeys: String, CodingKey { - case endpoint - case body = "params" - } - - private let endpoint: String - private let body: T - - public init( - endpoint: SnodeAPI.Endpoint, - body: T - ) { - self.endpoint = endpoint.path - self.body = body +extension Network.SnodeAPI { + struct OxenDaemonRPCRequest: Encodable { + private enum CodingKeys: String, CodingKey { + case endpoint + case body = "params" + } + + private let endpoint: String + private let body: T + + public init( + endpoint: Network.SnodeAPI.Endpoint, + body: T + ) { + self.endpoint = endpoint.path + self.body = body + } } } diff --git a/SessionNetworkingKit/StorageServer/Models/RevokeSubaccountRequest.swift b/SessionNetworkingKit/StorageServer/Models/RevokeSubaccountRequest.swift index a7f1690e1a..7495a14258 100644 --- a/SessionNetworkingKit/StorageServer/Models/RevokeSubaccountRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/RevokeSubaccountRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class RevokeSubaccountRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class RevokeSubaccountRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case subaccountsToRevoke = "revoke" } @@ -14,7 +14,7 @@ extension SnodeAPI { override var verificationBytes: [UInt8] { /// Ed25519 signature of `("revoke_subaccount" || timestamp || SUBACCOUNT_TAG_BYTES...)`; this /// signs the subaccount token, using `pubkey` to sign. Must be base64 encoded for json requests; binary for OMQ requests. - SnodeAPI.Endpoint.revokeSubaccount.path.bytes + Network.SnodeAPI.Endpoint.revokeSubaccount.path.bytes .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) .appending(contentsOf: Array(subaccountsToRevoke.joined())) } diff --git a/SessionNetworkingKit/StorageServer/Models/SendMessageRequest.swift b/SessionNetworkingKit/StorageServer/Models/SendMessageRequest.swift index b97ae8d672..69063606ac 100644 --- a/SessionNetworkingKit/StorageServer/Models/SendMessageRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/SendMessageRequest.swift @@ -3,14 +3,14 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class SendMessageRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class SendMessageRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case namespace } let message: SnodeMessage - let namespace: SnodeAPI.Namespace + let namespace: Network.SnodeAPI.Namespace override var verificationBytes: [UInt8] { /// Ed25519 signature of `("store" || namespace || timestamp)`, where namespace and @@ -18,7 +18,7 @@ extension SnodeAPI { /// base64 encoded for json requests; binary for OMQ requests. For non-05 type pubkeys (i.e. non /// session ids) the signature will be verified using `pubkey`. For 05 pubkeys, see the following /// option. - SnodeAPI.Endpoint.sendMessage.path.bytes + Network.SnodeAPI.Endpoint.sendMessage.path.bytes .appending(contentsOf: namespace.verificationString.bytes) .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) } @@ -27,7 +27,7 @@ extension SnodeAPI { public init( message: SnodeMessage, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, authMethod: AuthenticationMethod, timestampMs: UInt64 ) { diff --git a/SessionNetworkingKit/StorageServer/Models/SnodeAuthenticatedRequestBody.swift b/SessionNetworkingKit/StorageServer/Models/SnodeAuthenticatedRequestBody.swift index 4bcaa17c42..307e612059 100644 --- a/SessionNetworkingKit/StorageServer/Models/SnodeAuthenticatedRequestBody.swift +++ b/SessionNetworkingKit/StorageServer/Models/SnodeAuthenticatedRequestBody.swift @@ -3,7 +3,7 @@ import Foundation import SessionUtilitiesKit -public class SnodeAuthenticatedRequestBody: Encodable { +class SnodeAuthenticatedRequestBody: Encodable { private enum CodingKeys: String, CodingKey { case pubkey case subaccount diff --git a/SessionNetworkingKit/StorageServer/Models/SnodeBatchRequest.swift b/SessionNetworkingKit/StorageServer/Models/SnodeBatchRequest.swift index e6a05c0c19..083dec184b 100644 --- a/SessionNetworkingKit/StorageServer/Models/SnodeBatchRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/SnodeBatchRequest.swift @@ -3,7 +3,7 @@ import Foundation import SessionUtilitiesKit -internal extension SnodeAPI { +extension Network.SnodeAPI { struct BatchRequest: Encodable { let requests: [Child] @@ -38,7 +38,7 @@ internal extension SnodeAPI { case params } - let endpoint: SnodeAPI.Endpoint + let endpoint: Network.SnodeAPI.Endpoint /// The `jsonBodyEncoder` is used to avoid having to make `BatchSubRequest` a generic type (haven't found /// a good way to keep `BatchSubRequest` encodable using protocols unfortunately so need this work around) diff --git a/SessionNetworkingKit/StorageServer/Models/SnodeMessage.swift b/SessionNetworkingKit/StorageServer/Models/SnodeMessage.swift deleted file mode 100644 index a9fbcdd1e3..0000000000 --- a/SessionNetworkingKit/StorageServer/Models/SnodeMessage.swift +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import SessionUtilitiesKit - -public final class SnodeMessage: Codable { - private enum CodingKeys: String, CodingKey { - case recipient = "pubkey" - case data - case ttl - case timestampMs = "timestamp" - } - - /// The hex encoded public key of the recipient. - public let recipient: String - - /// The content of the message. - public let data: String - - /// The time to live for the message in milliseconds. - public let ttl: UInt64 - - /// When the proof of work was calculated. - /// - /// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970. - public let timestampMs: UInt64 - - // MARK: - Initialization - - public init(recipient: String, data: Data, ttl: UInt64, timestampMs: UInt64) { - self.recipient = recipient - self.data = data.base64EncodedString() - self.ttl = ttl - self.timestampMs = timestampMs - } -} - -// MARK: - Codable - -extension SnodeMessage { - public convenience init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - - self.init( - recipient: try container.decode(String.self, forKey: .recipient), - data: try Data(base64Encoded: try container.decode(String.self, forKey: .data)) ?? { - throw NetworkError.parsingFailed - }(), - ttl: try container.decode(UInt64.self, forKey: .ttl), - timestampMs: try container.decode(UInt64.self, forKey: .timestampMs) - ) - } - - public func encode(to encoder: Encoder) throws { - var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(recipient, forKey: .recipient) - try container.encode(data, forKey: .data) - try container.encode(ttl, forKey: .ttl) - try container.encode(timestampMs, forKey: .timestampMs) - } -} diff --git a/SessionNetworkingKit/StorageServer/Models/SnodeReceivedMessage.swift b/SessionNetworkingKit/StorageServer/Models/SnodeReceivedMessage.swift deleted file mode 100644 index 99604b49e8..0000000000 --- a/SessionNetworkingKit/StorageServer/Models/SnodeReceivedMessage.swift +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -// -// stringlint:disable - -import Foundation -import SessionUtilitiesKit - -public struct SnodeReceivedMessage: Codable, CustomDebugStringConvertible { - /// Service nodes cache messages for 14 days so default the expiration for message hashes to '15' days - /// so we don't end up indefinitely storing records which will never be used - public static let defaultExpirationMs: Int64 = ((15 * 24 * 60 * 60) * 1000) - - /// The storage server allows the timestamp within requests to be off by `60s` before erroring - public static let serverClockToleranceMs: Int64 = ((1 * 60) * 1000) - - public let snode: LibSession.Snode? - public let swarmPublicKey: String - public let namespace: SnodeAPI.Namespace - public let hash: String - public let timestampMs: Int64 - public let expirationTimestampMs: Int64 - public let data: Data - - public var info: SnodeReceivedMessageInfo? { - snode.map { snode in - SnodeReceivedMessageInfo( - snode: snode, - swarmPublicKey: swarmPublicKey, - namespace: namespace, - hash: hash, - expirationDateMs: expirationTimestampMs - ) - } - } - - public init?( - snode: LibSession.Snode?, - publicKey: String, - namespace: SnodeAPI.Namespace, - rawMessage: GetMessagesResponse.RawMessage - ) { - guard let data: Data = Data(base64Encoded: rawMessage.base64EncodedDataString) else { - Log.error(.network, "Failed to decode data for message: \(rawMessage).") - return nil - } - - self.snode = snode - self.swarmPublicKey = publicKey - self.namespace = namespace - self.hash = rawMessage.hash - self.timestampMs = rawMessage.timestampMs - self.expirationTimestampMs = (rawMessage.expirationMs ?? SnodeReceivedMessage.defaultExpirationMs) - self.data = data - } - - public var debugDescription: String { - """ - SnodeReceivedMessage( - swarmPublicKey: \(swarmPublicKey), - namespace: \(namespace), - hash: \(hash), - expirationTimestampMs: \(expirationTimestampMs), - timestampMs: \(timestampMs), - data: \(data.base64EncodedString()) - ) - """ - } -} diff --git a/SessionNetworkingKit/StorageServer/Models/SnodeRequest.swift b/SessionNetworkingKit/StorageServer/Models/SnodeRequest.swift index ab23b427b3..c88d159c8f 100644 --- a/SessionNetworkingKit/StorageServer/Models/SnodeRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/SnodeRequest.swift @@ -9,13 +9,13 @@ public struct SnodeRequest: Encodable { case body = "params" } - internal let endpoint: SnodeAPI.Endpoint + internal let endpoint: Network.SnodeAPI.Endpoint internal let body: T // MARK: - Initialization public init( - endpoint: SnodeAPI.Endpoint, + endpoint: Network.SnodeAPI.Endpoint, body: T ) { self.endpoint = endpoint diff --git a/SessionNetworkingKit/StorageServer/Models/UnrevokeSubaccountRequest.swift b/SessionNetworkingKit/StorageServer/Models/UnrevokeSubaccountRequest.swift index 63fb0f6b40..3f8cb6f30d 100644 --- a/SessionNetworkingKit/StorageServer/Models/UnrevokeSubaccountRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/UnrevokeSubaccountRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class UnrevokeSubaccountRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class UnrevokeSubaccountRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case subaccountsToUnrevoke = "unrevoke" } @@ -14,7 +14,7 @@ extension SnodeAPI { override var verificationBytes: [UInt8] { /// Ed25519 signature of `("unrevoke_subaccount" || timestamp || subaccount)`; this signs /// the subaccount token, using `pubkey` to sign. Must be base64 encoded for json requests; binary for OMQ requests. - SnodeAPI.Endpoint.unrevokeSubaccount.path.bytes + Network.SnodeAPI.Endpoint.unrevokeSubaccount.path.bytes .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) .appending(contentsOf: Array(subaccountsToUnrevoke.joined())) } diff --git a/SessionNetworkingKit/StorageServer/Models/UpdateExpiryAllRequest.swift b/SessionNetworkingKit/StorageServer/Models/UpdateExpiryAllRequest.swift index 184e767764..dbc4fbf3ff 100644 --- a/SessionNetworkingKit/StorageServer/Models/UpdateExpiryAllRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/UpdateExpiryAllRequest.swift @@ -5,8 +5,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class UpdateExpiryAllRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class UpdateExpiryAllRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case expiryMs = "expiry" case namespace @@ -19,14 +19,14 @@ extension SnodeAPI { /// /// **Note:** If omitted when sending the request, message expiries are updated from the default namespace /// only (namespace 0) - let namespace: SnodeAPI.Namespace? + let namespace: Network.SnodeAPI.Namespace? override var verificationBytes: [UInt8] { /// Ed25519 signature of `("expire_all" || namespace || expiry)`, signed by `pubkey`. Must be /// base64 encoded (json) or bytes (OMQ). namespace should be the stringified namespace for /// non-default namespace expiries (i.e. "42", "-99", "all"), or an empty string for the default /// namespace (whether or not explicitly provided). - SnodeAPI.Endpoint.expireAll.path.bytes + Network.SnodeAPI.Endpoint.expireAll.path.bytes .appending( contentsOf: (namespace == nil ? "all" : @@ -40,7 +40,7 @@ extension SnodeAPI { public init( expiryMs: UInt64, - namespace: SnodeAPI.Namespace?, + namespace: Network.SnodeAPI.Namespace?, authMethod: AuthenticationMethod ) { self.expiryMs = expiryMs diff --git a/SessionNetworkingKit/StorageServer/Models/UpdateExpiryRequest.swift b/SessionNetworkingKit/StorageServer/Models/UpdateExpiryRequest.swift index ba3546abac..6e237557d0 100644 --- a/SessionNetworkingKit/StorageServer/Models/UpdateExpiryRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/UpdateExpiryRequest.swift @@ -5,8 +5,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class UpdateExpiryRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class UpdateExpiryRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case messageHashes = "messages" case expiryMs = "expiry" @@ -39,7 +39,7 @@ extension SnodeAPI { /// ` || messages[N])` where `expiry` is the expiry timestamp expressed as a string. /// `ShortenOrExtend` is string signature must be base64 "shorten" if the shorten option is given (and true), /// "extend" if `extend` is true, and empty otherwise. The signature must be base64 encoded (json) or bytes (bt). - SnodeAPI.Endpoint.expire.path.bytes + Network.SnodeAPI.Endpoint.expire.path.bytes .appending(contentsOf: (shorten == true ? "shorten".bytes : [])) .appending(contentsOf: (extend == true ? "extend".bytes : [])) .appending(contentsOf: "\(expiryMs)".data(using: .ascii)?.bytes) diff --git a/SessionNetworkingKit/StorageServer/SnodeAPI.swift b/SessionNetworkingKit/StorageServer/SnodeAPI.swift index d7d9ef9e3d..68e7357e4a 100644 --- a/SessionNetworkingKit/StorageServer/SnodeAPI.swift +++ b/SessionNetworkingKit/StorageServer/SnodeAPI.swift @@ -9,24 +9,788 @@ import Punycode import SessionUtilitiesKit public extension Network { - enum StorageServer { + enum SnodeAPI { + // MARK: - Settings + public static let maxRetryCount: Int = 8 + + // MARK: - Batching & Polling + + public typealias PollResponse = [SnodeAPI.Namespace: (info: ResponseInfoType, data: PreparedGetMessagesResponse?)] + + public static func preparedPoll( + _ db: ObservingDatabase, + namespaces: [SnodeAPI.Namespace], + refreshingConfigHashes: [String] = [], + from snode: LibSession.Snode, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + // Determine the maxSize each namespace in the request should take up + var requests: [any ErasedPreparedRequest] = [] + let namespaceMaxSizeMap: [SnodeAPI.Namespace: Int64] = SnodeAPI.Namespace.maxSizeMap(for: namespaces) + let fallbackSize: Int64 = (namespaceMaxSizeMap.values.min() ?? 1) + + // If we have any config hashes to refresh TTLs then add those requests first + if !refreshingConfigHashes.isEmpty { + let updatedExpiryMS: Int64 = ( + dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + + (30 * 24 * 60 * 60 * 1000) // 30 days + ) + requests.append( + try SnodeAPI.preparedUpdateExpiry( + serverHashes: refreshingConfigHashes, + updatedExpiryMs: updatedExpiryMS, + extendOnly: true, + ignoreValidationFailure: true, + explicitTargetNode: snode, + authMethod: authMethod, + using: dependencies + ) + ) + } + + // Add the various 'getMessages' requests + requests.append( + contentsOf: try namespaces.map { namespace -> any ErasedPreparedRequest in + try SnodeAPI.preparedGetMessages( + db, + namespace: namespace, + snode: snode, + maxSize: namespaceMaxSizeMap[namespace] + .defaulting(to: fallbackSize), + authMethod: authMethod, + using: dependencies + ) + } + ) + + return try preparedBatch( + requests: requests, + requireAllBatchResponses: true, + snode: snode, + swarmPublicKey: try authMethod.swarmPublicKey, + using: dependencies + ) + .map { (_: ResponseInfoType, batchResponse: Network.BatchResponse) -> [SnodeAPI.Namespace: (info: ResponseInfoType, data: PreparedGetMessagesResponse?)] in + let messageResponses: [Network.BatchSubResponse] = batchResponse + .compactMap { $0 as? Network.BatchSubResponse } + + return zip(namespaces, messageResponses) + .reduce(into: [:]) { result, next in + guard let messageResponse: PreparedGetMessagesResponse = next.1.body else { return } + + result[next.0] = (next.1, messageResponse) + } + } + } + + public static func preparedBatch( + requests: [any ErasedPreparedRequest], + requireAllBatchResponses: Bool, + snode: LibSession.Snode? = nil, + swarmPublicKey: String, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try SnodeAPI + .prepareRequest( + request: { + switch snode { + case .none: + return try Request( + endpoint: .batch, + swarmPublicKey: swarmPublicKey, + body: Network.BatchRequest(requestsKey: .requests, requests: requests) + ) + + case .some(let snode): + return try Request( + endpoint: .batch, + snode: snode, + swarmPublicKey: swarmPublicKey, + body: Network.BatchRequest(requestsKey: .requests, requests: requests) + ) + } + }(), + responseType: Network.BatchResponse.self, + requireAllBatchResponses: requireAllBatchResponses, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, + using: dependencies + ) + } + + public static func preparedSequence( + requests: [any ErasedPreparedRequest], + requireAllBatchResponses: Bool, + swarmPublicKey: String, + snodeRetrievalRetryCount: Int, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .sequence, + swarmPublicKey: swarmPublicKey, + body: Network.BatchRequest(requestsKey: .requests, requests: requests), + snodeRetrievalRetryCount: snodeRetrievalRetryCount + ), + responseType: Network.BatchResponse.self, + requireAllBatchResponses: requireAllBatchResponses, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, + using: dependencies + ) + } + + // MARK: - Retrieve + + public typealias PreparedGetMessagesResponse = (messages: [SnodeReceivedMessage], lastHash: String?) + + public static func preparedGetMessages( + _ db: ObservingDatabase, + namespace: SnodeAPI.Namespace, + snode: LibSession.Snode, + maxSize: Int64? = nil, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + let maybeLastHash: String? = try SnodeReceivedMessageInfo + .fetchLastNotExpired( + db, + for: snode, + namespace: namespace, + swarmPublicKey: try authMethod.swarmPublicKey, + using: dependencies + )? + .hash + let preparedRequest: Network.PreparedRequest = try { + // Check if this namespace requires authentication + guard namespace.requiresReadAuthentication else { + return try SnodeAPI.prepareRequest( + request: Request( + endpoint: .getMessages, + swarmPublicKey: try authMethod.swarmPublicKey, + body: LegacyGetMessagesRequest( + pubkey: try authMethod.swarmPublicKey, + lastHash: (maybeLastHash ?? ""), + namespace: namespace, + maxCount: nil, + maxSize: maxSize + ) + ), + responseType: GetMessagesResponse.self, + using: dependencies + ) + } + + return try SnodeAPI.prepareRequest( + request: Request( + endpoint: .getMessages, + swarmPublicKey: try authMethod.swarmPublicKey, + body: GetMessagesRequest( + lastHash: (maybeLastHash ?? ""), + namespace: namespace, + authMethod: authMethod, + timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs(), + maxSize: maxSize + ) + ), + responseType: GetMessagesResponse.self, + using: dependencies + ) + }() + + return preparedRequest + .tryMap { _, response -> (messages: [SnodeReceivedMessage], lastHash: String?) in + return ( + try response.messages.compactMap { rawMessage -> SnodeReceivedMessage? in + SnodeReceivedMessage( + snode: snode, + publicKey: try authMethod.swarmPublicKey, + namespace: namespace, + rawMessage: rawMessage + ) + }, + maybeLastHash + ) + } + } + + public static func getSessionID( + for onsName: String, + using dependencies: Dependencies + ) -> AnyPublisher { + let validationCount = 3 + + // The name must be lowercased + let onsName = onsName.lowercased().idnaEncoded ?? onsName.lowercased() + + // Hash the ONS name using BLAKE2b + guard + let nameHash = dependencies[singleton: .crypto].generate( + .hash(message: Array(onsName.utf8)) + ) + else { + return Fail(error: SnodeAPIError.onsHashingFailed) + .eraseToAnyPublisher() + } + + // Ask 3 different snodes for the Session ID associated with the given name hash + let base64EncodedNameHash = nameHash.toBase64() + + return dependencies[singleton: .network] + .getRandomNodes(count: validationCount) + .tryFlatMap { nodes in + Publishers.MergeMany( + try nodes.map { snode in + try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .oxenDaemonRPCCall, + snode: snode, + body: OxenDaemonRPCRequest( + endpoint: .daemonOnsResolve, + body: ONSResolveRequest( + type: 0, // type 0 means Session + base64EncodedNameHash: base64EncodedNameHash + ) + ) + ), + responseType: ONSResolveResponse.self, + using: dependencies + ) + .tryMap { _, response -> String in + try dependencies[singleton: .crypto].tryGenerate( + .sessionId(name: onsName, response: response) + ) + } + .send(using: dependencies) + .map { _, sessionId in sessionId } + .eraseToAnyPublisher() + } + ) + } + .collect() + .tryMap { results -> String in + guard results.count == validationCount, Set(results).count == 1 else { + throw SnodeAPIError.onsValidationFailed + } + + return results[0] + } + .eraseToAnyPublisher() + } + + public static func preparedGetExpiries( + of serverHashes: [String], + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .getExpiries, + swarmPublicKey: try authMethod.swarmPublicKey, + body: GetExpiriesRequest( + messageHashes: serverHashes, + authMethod: authMethod, + timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + ) + ), + responseType: GetExpiriesResponse.self, + using: dependencies + ) + } + + // MARK: - Store + + public static func preparedSendMessage( + message: SnodeMessage, + in namespace: Namespace, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + let request: Network.PreparedRequest = try { + // Check if this namespace requires authentication + guard namespace.requiresWriteAuthentication else { + return try SnodeAPI.prepareRequest( + request: Request( + endpoint: .sendMessage, + swarmPublicKey: try authMethod.swarmPublicKey, + body: LegacySendMessagesRequest( + message: message, + namespace: namespace + ), + snodeRetrievalRetryCount: 0 // The SendMessageJob already has a retry mechanism + ), + responseType: SendMessagesResponse.self, + requestAndPathBuildTimeout: Network.defaultTimeout, + using: dependencies + ) + } + + return try SnodeAPI.prepareRequest( + request: Request( + endpoint: .sendMessage, + swarmPublicKey: try authMethod.swarmPublicKey, + body: SendMessageRequest( + message: message, + namespace: namespace, + authMethod: authMethod, + timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + ), + snodeRetrievalRetryCount: 0 // The SendMessageJob already has a retry mechanism + ), + responseType: SendMessagesResponse.self, + requestAndPathBuildTimeout: Network.defaultTimeout, + using: dependencies + ) + }() + + return request + .tryMap { _, response -> SendMessagesResponse in + try response.validateResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + using: dependencies + ) + + return response + } + } + + // MARK: - Edit + + public static func preparedUpdateExpiry( + serverHashes: [String], + updatedExpiryMs: Int64, + shortenOnly: Bool? = nil, + extendOnly: Bool? = nil, + ignoreValidationFailure: Bool = false, + explicitTargetNode: LibSession.Snode? = nil, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest<[String: UpdateExpiryResponseResult]> { + // ShortenOnly and extendOnly cannot be true at the same time + guard shortenOnly == nil || extendOnly == nil else { throw NetworkError.invalidPreparedRequest } + + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .expire, + swarmPublicKey: try authMethod.swarmPublicKey, + body: UpdateExpiryRequest( + messageHashes: serverHashes, + expiryMs: UInt64(updatedExpiryMs), + shorten: shortenOnly, + extend: extendOnly, + authMethod: authMethod + ) + ), + responseType: UpdateExpiryResponse.self, + using: dependencies + ) + .tryMap { _, response -> [String: UpdateExpiryResponseResult] in + do { + return try response.validResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + validationData: serverHashes, + using: dependencies + ) + } + catch { + guard ignoreValidationFailure else { throw error } + + return [:] + } + } + .handleEvents( + receiveOutput: { _, result in + /// Since we have updated the TTL we need to make sure we also update the local + /// `SnodeReceivedMessageInfo.expirationDateMs` values so they match the updated swarm, if + /// we had a specific `snode` we we're sending the request to then we should use those values, otherwise + /// we can just grab the first value from the response and use that + let maybeTargetResult: UpdateExpiryResponseResult? = { + guard let snode: LibSession.Snode = explicitTargetNode else { + return result.first?.value + } + + return result[snode.ed25519PubkeyHex] + }() + guard + let targetResult: UpdateExpiryResponseResult = maybeTargetResult, + let groupedExpiryResult: [UInt64: [String]] = targetResult.changed + .updated(with: targetResult.unchanged) + .groupedByValue() + .nullIfEmpty + else { return } + + dependencies[singleton: .storage].writeAsync { db in + try groupedExpiryResult.forEach { updatedExpiry, hashes in + try SnodeReceivedMessageInfo + .filter(hashes.contains(SnodeReceivedMessageInfo.Columns.hash)) + .updateAll( + db, + SnodeReceivedMessageInfo.Columns.expirationDateMs + .set(to: updatedExpiry) + ) + } + } + } + ) + } + + public static func preparedRevokeSubaccounts( + subaccountsToRevoke: [[UInt8]], + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + let timestampMs: UInt64 = dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .revokeSubaccount, + swarmPublicKey: try authMethod.swarmPublicKey, + body: RevokeSubaccountRequest( + subaccountsToRevoke: subaccountsToRevoke, + authMethod: authMethod, + timestampMs: timestampMs + ) + ), + responseType: RevokeSubaccountResponse.self, + using: dependencies + ) + .tryMap { _, response -> Void in + try response.validateResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + validationData: (subaccountsToRevoke, timestampMs), + using: dependencies + ) + + return () + } + } + + public static func preparedUnrevokeSubaccounts( + subaccountsToUnrevoke: [[UInt8]], + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + let timestampMs: UInt64 = dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .unrevokeSubaccount, + swarmPublicKey: try authMethod.swarmPublicKey, + body: UnrevokeSubaccountRequest( + subaccountsToUnrevoke: subaccountsToUnrevoke, + authMethod: authMethod, + timestampMs: timestampMs + ) + ), + responseType: UnrevokeSubaccountResponse.self, + using: dependencies + ) + .tryMap { _, response -> Void in + try response.validateResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + validationData: (subaccountsToUnrevoke, timestampMs), + using: dependencies + ) + + return () + } + } + + // MARK: - Delete + + public static func preparedDeleteMessages( + serverHashes: [String], + requireSuccessfulDeletion: Bool, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest<[String: Bool]> { + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .deleteMessages, + swarmPublicKey: try authMethod.swarmPublicKey, + body: DeleteMessagesRequest( + messageHashes: serverHashes, + requireSuccessfulDeletion: requireSuccessfulDeletion, + authMethod: authMethod + ) + ), + responseType: DeleteMessagesResponse.self, + using: dependencies + ) + .tryMap { _, response -> [String: Bool] in + let validResultMap: [String: Bool] = try response.validResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + validationData: serverHashes, + using: dependencies + ) + + // If `validResultMap` didn't throw then at least one service node + // deleted successfully so we should mark the hash as invalid so we + // don't try to fetch updates using that hash going forward (if we + // do we would end up re-fetching all old messages) + dependencies[singleton: .storage].writeAsync { db in + try? SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( + db, + potentiallyInvalidHashes: serverHashes + ) + } + + return validResultMap + } + } + + + /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. + public static func preparedDeleteAllMessages( + namespace: SnodeAPI.Namespace, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest<[String: Bool]> { + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .deleteAll, + swarmPublicKey: try authMethod.swarmPublicKey, + requiresLatestNetworkTime: true, + body: DeleteAllMessagesRequest( + namespace: namespace, + authMethod: authMethod, + timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + ), + snodeRetrievalRetryCount: 0 + ), + responseType: DeleteAllMessagesResponse.self, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, + using: dependencies + ) + .tryMap { info, response -> [String: Bool] in + guard let targetInfo: LatestTimestampResponseInfo = info as? LatestTimestampResponseInfo else { + throw NetworkError.invalidResponse + } + + return try response.validResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + validationData: targetInfo.timestampMs, + using: dependencies + ) + } + } + + /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. + public static func preparedDeleteAllMessages( + beforeMs: UInt64, + namespace: SnodeAPI.Namespace, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest<[String: Bool]> { + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .deleteAllBefore, + swarmPublicKey: try authMethod.swarmPublicKey, + requiresLatestNetworkTime: true, + body: DeleteAllBeforeRequest( + beforeMs: beforeMs, + namespace: namespace, + authMethod: authMethod, + timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + ) + ), + responseType: DeleteAllMessagesResponse.self, + retryCount: maxRetryCount, + using: dependencies + ) + .tryMap { _, response -> [String: Bool] in + try response.validResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + validationData: beforeMs, + using: dependencies + ) + } + } + + // MARK: - Internal API + + public static func preparedGetNetworkTime( + from snode: LibSession.Snode, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try SnodeAPI + .prepareRequest( + request: Request, Endpoint>( + endpoint: .getInfo, + snode: snode, + body: [:] + ), + responseType: GetNetworkTimestampResponse.self, + using: dependencies + ) + .map { _, response in + // Assume we've fetched the networkTime in order to send a message to the specified snode, in + // which case we want to update the 'clockOffsetMs' value for subsequent requests + let offset = (Int64(response.timestamp) - Int64(floor(dependencies.dateNow.timeIntervalSince1970 * 1000))) + dependencies.mutate(cache: .snodeAPI) { $0.setClockOffsetMs(offset) } + + return response.timestamp + } + } + + // MARK: - Convenience + + private static func prepareRequest( + request: Request, + responseType: R.Type, + requireAllBatchResponses: Bool = true, + retryCount: Int = 0, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try Network.PreparedRequest( + request: request, + responseType: responseType, + requireAllBatchResponses: requireAllBatchResponses, + retryCount: retryCount, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, + using: dependencies + ) + .handleEvents( + receiveOutput: { _, response in + switch response { + case let snodeResponse as SnodeResponse: + // Update the network offset based on the response so subsequent requests have + // the correct network offset time + let offset = (Int64(snodeResponse.timeOffset) - Int64(floor(dependencies.dateNow.timeIntervalSince1970 * 1000))) + dependencies.mutate(cache: .snodeAPI) { + $0.setClockOffsetMs(offset) + + // Extract and store hard fork information if returned + guard snodeResponse.hardForkVersion.count > 1 else { return } + + if snodeResponse.hardForkVersion[1] > $0.softfork { + $0.softfork = snodeResponse.hardForkVersion[1] + dependencies[defaults: .standard, key: .softfork] = $0.softfork + } + + if snodeResponse.hardForkVersion[0] > $0.hardfork { + $0.hardfork = snodeResponse.hardForkVersion[0] + dependencies[defaults: .standard, key: .hardfork] = $0.hardfork + $0.softfork = snodeResponse.hardForkVersion[1] + dependencies[defaults: .standard, key: .softfork] = $0.softfork + } + } + + default: break + } + } + ) + } } } -// MARK: - Network.StorageServer.Cache +// MARK: - Publisher Convenience -public extension Cache { - static let storageServer: CacheConfig = Dependencies.create( - identifier: "storageServer", - createInstance: { dependencies in Network.StorageServer.Cache(using: dependencies) }, - mutableInstance: { $0 }, - immutableInstance: { $0 } - ) +public extension Publisher where Output == Set { + func tryMapWithRandomSnode( + using dependencies: Dependencies, + _ transform: @escaping (LibSession.Snode) throws -> T + ) -> AnyPublisher { + return self + .tryMap { swarm -> T in + var remainingSnodes: Set = swarm + let snode: LibSession.Snode = try dependencies.popRandomElement(&remainingSnodes) ?? { + throw SnodeAPIError.insufficientSnodes + }() + + return try transform(snode) + } + .eraseToAnyPublisher() + } + + func tryFlatMapWithRandomSnode( + maxPublishers: Subscribers.Demand = .unlimited, + retry retries: Int = 0, + drainBehaviour: ThreadSafeObject = .alwaysRandom, + using dependencies: Dependencies, + _ transform: @escaping (LibSession.Snode) throws -> P + ) -> AnyPublisher where T == P.Output, P: Publisher, P.Failure == Error { + return self + .mapError { $0 } + .flatMap(maxPublishers: maxPublishers) { swarm -> AnyPublisher in + // If we don't want to reuse a specific snode multiple times then just grab a + // random one from the swarm every time + var remainingSnodes: Set = drainBehaviour.performUpdateAndMap { behaviour in + switch behaviour { + case .alwaysRandom: return (behaviour, swarm) + case .limitedReuse(_, let targetSnode, _, let usedSnodes, let swarmHash): + // If we've used all of the snodes or the swarm has changed then reset the used list + guard swarmHash == swarm.hashValue && (targetSnode != nil || usedSnodes != swarm) else { + return (behaviour.reset(), swarm) + } + + return (behaviour, swarm.subtracting(usedSnodes)) + } + } + var lastError: Error? + + return Just(()) + .setFailureType(to: Error.self) + .tryFlatMap(maxPublishers: maxPublishers) { _ -> AnyPublisher in + let snode: LibSession.Snode = try drainBehaviour.performUpdateAndMap { behaviour in + switch behaviour { + case .limitedReuse(_, .some(let targetSnode), _, _, _): + return (behaviour.use(snode: targetSnode, from: swarm), targetSnode) + default: break + } + + // Select the next snode + let result: LibSession.Snode = try dependencies.popRandomElement(&remainingSnodes) ?? { + throw SnodeAPIError.ranOutOfRandomSnodes(lastError) + }() + + return (behaviour.use(snode: result, from: swarm), result) + } + + return try transform(snode) + .eraseToAnyPublisher() + } + .mapError { error in + // Prevent nesting the 'ranOutOfRandomSnodes' errors + switch error { + case SnodeAPIError.ranOutOfRandomSnodes: break + default: lastError = error + } + + return error + } + .retry(retries) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } } -public extension Network.StorageServer { - class Cache: StorageServerCacheType { +// MARK: - SnodeAPI Cache + +public extension Network.SnodeAPI { + class Cache: SnodeAPICacheType { private let dependencies: Dependencies public var hardfork: Int public var softfork: Int @@ -55,10 +819,19 @@ public extension Network.StorageServer { } } +public extension Cache { + static let snodeAPI: CacheConfig = Dependencies.create( + identifier: "snodeAPI", + createInstance: { dependencies in Network.SnodeAPI.Cache(using: dependencies) }, + mutableInstance: { $0 }, + immutableInstance: { $0 } + ) +} + // MARK: - SnodeAPICacheType /// This is a read-only version of the Cache designed to avoid unintentionally mutating the instance in a non-thread-safe way -public protocol StorageServerImmutableCacheType: ImmutableCacheType { +public protocol SnodeAPIImmutableCacheType: ImmutableCacheType { /// The last seen storage server hard fork version. var hardfork: Int { get } @@ -74,7 +847,7 @@ public protocol StorageServerImmutableCacheType: ImmutableCacheType { func currentOffsetTimestampMs() -> T } -public protocol StorageServerCacheType: StorageServerImmutableCacheType, MutableCacheType { +public protocol SnodeAPICacheType: SnodeAPIImmutableCacheType, MutableCacheType { /// The last seen storage server hard fork version. var hardfork: Int { get set } diff --git a/SessionNetworkingKit/StorageServer/SnodeAPIEndpoint.swift b/SessionNetworkingKit/StorageServer/SnodeAPIEndpoint.swift index 7707eb28a1..6bd1f1b1a9 100644 --- a/SessionNetworkingKit/StorageServer/SnodeAPIEndpoint.swift +++ b/SessionNetworkingKit/StorageServer/SnodeAPIEndpoint.swift @@ -4,7 +4,7 @@ import Foundation -public extension Network.StorageServer { +public extension Network.SnodeAPI { enum Endpoint: EndpointType { case sendMessage case getMessages diff --git a/SessionNetworkingKit/StorageServer/SnodeAPIError.swift b/SessionNetworkingKit/StorageServer/SnodeAPIError.swift index 759118a11e..c09b5ed963 100644 --- a/SessionNetworkingKit/StorageServer/SnodeAPIError.swift +++ b/SessionNetworkingKit/StorageServer/SnodeAPIError.swift @@ -4,7 +4,7 @@ import Foundation -public enum StorageServerError: Error, CustomStringConvertible { +public enum SnodeAPIError: Error, CustomStringConvertible { case clockOutOfSync case snodePoolUpdatingFailed case inconsistentSnodePools diff --git a/SessionNetworkingKit/StorageServer/SnodeAPINamespace.swift b/SessionNetworkingKit/StorageServer/SnodeAPINamespace.swift index c2c595f731..ea3399d543 100644 --- a/SessionNetworkingKit/StorageServer/SnodeAPINamespace.swift +++ b/SessionNetworkingKit/StorageServer/SnodeAPINamespace.swift @@ -6,7 +6,7 @@ import Foundation import SessionUtil import SessionUtilitiesKit -public extension Network.StorageServer { +public extension Network.SnodeAPI { enum Namespace: Int, Codable, Hashable, CustomStringConvertible { /// Messages sent to one-to-one conversations are stored in this namespace case `default` = 0 diff --git a/SessionNetworkingKit/StorageServer/Types/Request+SnodeAPI.swift b/SessionNetworkingKit/StorageServer/Types/Request+SnodeAPI.swift index 79ee173412..4f2c50e84f 100644 --- a/SessionNetworkingKit/StorageServer/Types/Request+SnodeAPI.swift +++ b/SessionNetworkingKit/StorageServer/Types/Request+SnodeAPI.swift @@ -5,7 +5,7 @@ import Foundation import SessionUtilitiesKit -public extension Request where Endpoint == Network.StorageServer.Endpoint { +public extension Request where Endpoint == Network.SnodeAPI.Endpoint { init( endpoint: Endpoint, snode: LibSession.Snode, @@ -29,7 +29,7 @@ public extension Request where Endpoint == Network.StorageServer.Endpoint { endpoint: Endpoint, swarmPublicKey: String, body: B, - snodeRetrievalRetryCount: Int = Network.StorageServer.maxRetryCount + snodeRetrievalRetryCount: Int = Network.SnodeAPI.maxRetryCount ) throws where T == SnodeRequest { self = try Request( endpoint: endpoint, @@ -49,7 +49,7 @@ public extension Request where Endpoint == Network.StorageServer.Endpoint { swarmPublicKey: String, requiresLatestNetworkTime: Bool, body: B, - snodeRetrievalRetryCount: Int = Network.StorageServer.maxRetryCount + snodeRetrievalRetryCount: Int = Network.SnodeAPI.maxRetryCount ) throws where T == SnodeRequest, B: Encodable & UpdatableTimestamp { self = try Request( endpoint: endpoint, diff --git a/SessionNetworkingKit/StorageServer/Types/SnodeReceivedMessage.swift b/SessionNetworkingKit/StorageServer/Types/SnodeReceivedMessage.swift index 99604b49e8..ad2384fbbb 100644 --- a/SessionNetworkingKit/StorageServer/Types/SnodeReceivedMessage.swift +++ b/SessionNetworkingKit/StorageServer/Types/SnodeReceivedMessage.swift @@ -15,7 +15,7 @@ public struct SnodeReceivedMessage: Codable, CustomDebugStringConvertible { public let snode: LibSession.Snode? public let swarmPublicKey: String - public let namespace: SnodeAPI.Namespace + public let namespace: Network.SnodeAPI.Namespace public let hash: String public let timestampMs: Int64 public let expirationTimestampMs: Int64 @@ -36,7 +36,7 @@ public struct SnodeReceivedMessage: Codable, CustomDebugStringConvertible { public init?( snode: LibSession.Snode?, publicKey: String, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, rawMessage: GetMessagesResponse.RawMessage ) { guard let data: Data = Data(base64Encoded: rawMessage.base64EncodedDataString) else { diff --git a/SessionNetworkingKit/Types/Network.swift b/SessionNetworkingKit/Types/Network.swift index 5913775129..a11afeb54c 100644 --- a/SessionNetworkingKit/Types/Network.swift +++ b/SessionNetworkingKit/Types/Network.swift @@ -15,22 +15,6 @@ public extension Singleton { ) } -// MARK: - NetworkType - -public protocol NetworkType { - func getSwarm(for swarmPublicKey: String) -> AnyPublisher, Error> - func getRandomNodes(count: Int) -> AnyPublisher, Error> - - func send( - _ body: Data?, - to destination: Network.Destination, - requestTimeout: TimeInterval, - requestAndPathBuildTimeout: TimeInterval? - ) -> AnyPublisher<(ResponseInfoType, Data?), Error> - - func checkClientVersion(ed25519SecretKey: [UInt8]) -> AnyPublisher<(ResponseInfoType, AppVersionResponse), Error> -} - // MARK: - Network Constants public class Network { @@ -52,118 +36,18 @@ public enum NetworkStatus { case disconnected } -// MARK: - FileServer Convenience +// MARK: - NetworkType -public extension Network { - enum NetworkAPI { - static let networkAPIServer = "http://networkv1.getsession.org" - static let networkAPIServerPublicKey = "cbf461a4431dc9174dceef4421680d743a2a0e1a3131fc794240bcb0bc3dd449" - - public enum Endpoint: EndpointType { - case info - case price - case token - - public static var name: String { "NetworkAPI.Endpoint" } - - public var path: String { - switch self { - case .info: return "info" - case .price: return "price" - case .token: return "token" - } - } - } - } - - enum FileServer { - fileprivate static let fileServer = "http://filev2.getsession.org" - fileprivate static let fileServerPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" - fileprivate static let legacyFileServer = "http://88.99.175.227" - fileprivate static let legacyFileServerPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69" - - public enum Endpoint: EndpointType { - case file - case fileIndividual(String) - case directUrl(URL) - case sessionVersion - - public static var name: String { "FileServerAPI.Endpoint" } - - public var path: String { - switch self { - case .file: return "file" - case .fileIndividual(let fileId): return "file/\(fileId)" - case .directUrl(let url): return url.path.removingPrefix("/") - case .sessionVersion: return "session_version" - } - } - } - - static func fileServerPubkey(url: String? = nil) -> String { - switch url?.contains(legacyFileServer) { - case true: return legacyFileServerPublicKey - default: return fileServerPublicKey - } - } - - static func isFileServerUrl(url: URL) -> Bool { - return ( - url.absoluteString.starts(with: fileServer) || - url.absoluteString.starts(with: legacyFileServer) - ) - } - - public static func downloadUrlString(for url: String, fileId: String) -> String { - switch url.contains(legacyFileServer) { - case true: return "\(fileServer)/\(Endpoint.fileIndividual(fileId).path)" - default: return downloadUrlString(for: fileId) - } - } - - public static func downloadUrlString(for fileId: String) -> String { - return "\(fileServer)/\(Endpoint.fileIndividual(fileId).path)" - } - } +public protocol NetworkType { + func getSwarm(for swarmPublicKey: String) -> AnyPublisher, Error> + func getRandomNodes(count: Int) -> AnyPublisher, Error> - static func preparedUpload( - data: Data, - requestAndPathBuildTimeout: TimeInterval? = nil, - using dependencies: Dependencies - ) throws -> PreparedRequest { - return try PreparedRequest( - request: Request( - endpoint: FileServer.Endpoint.file, - destination: .serverUpload( - server: FileServer.fileServer, - x25519PublicKey: FileServer.fileServerPublicKey, - fileName: nil - ), - body: data - ), - responseType: FileUploadResponse.self, - requestTimeout: Network.fileUploadTimeout, - requestAndPathBuildTimeout: requestAndPathBuildTimeout, - using: dependencies - ) - } + func send( + _ body: Data?, + to destination: Network.Destination, + requestTimeout: TimeInterval, + requestAndPathBuildTimeout: TimeInterval? + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> - static func preparedDownload( - url: URL, - using dependencies: Dependencies - ) throws -> PreparedRequest { - return try PreparedRequest( - request: Request( - endpoint: FileServer.Endpoint.directUrl(url), - destination: .serverDownload( - url: url, - x25519PublicKey: FileServer.fileServerPublicKey, - fileName: nil - ) - ), - responseType: Data.self, - requestTimeout: Network.fileUploadTimeout, - using: dependencies - ) - } + func checkClientVersion(ed25519SecretKey: [UInt8]) -> AnyPublisher<(ResponseInfoType, Network.FileServer.AppVersionResponse), Error> } diff --git a/SessionNetworkingKitTests/SOGS/Crypto/Authentication+SOGS.swift b/SessionNetworkingKitTests/SOGS/Crypto/Authentication+SOGS.swift new file mode 100644 index 0000000000..92862325b1 --- /dev/null +++ b/SessionNetworkingKitTests/SOGS/Crypto/Authentication+SOGS.swift @@ -0,0 +1,50 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +// MARK: - Authentication Types + +public extension Authentication { + /// Used when interacting with a community + struct community: AuthenticationMethod { + public let roomToken: String + public let server: String + public let publicKey: String + public let hasCapabilities: Bool + public let supportsBlinding: Bool + public let forceBlinded: Bool + + public var info: Info { + .community( + server: server, + publicKey: publicKey, + hasCapabilities: hasCapabilities, + supportsBlinding: supportsBlinding, + forceBlinded: forceBlinded + ) + } + + public init( + roomToken: String, + server: String, + publicKey: String, + hasCapabilities: Bool, + supportsBlinding: Bool, + forceBlinded: Bool = false + ) { + self.roomToken = roomToken + self.server = server + self.publicKey = publicKey + self.hasCapabilities = hasCapabilities + self.supportsBlinding = supportsBlinding + self.forceBlinded = forceBlinded + } + + // MARK: - SignatureGenerator + + public func generateSignature(with verificationBytes: [UInt8], using dependencies: Dependencies) throws -> Authentication.Signature { + throw CryptoError.signatureGenerationFailed + } + } +} diff --git a/SessionNetworkingKitTests/SOGS/Crypto/CryptoSOGSAPISpec.swift b/SessionNetworkingKitTests/SOGS/Crypto/CryptoSOGSAPISpec.swift new file mode 100644 index 0000000000..d141b5549f --- /dev/null +++ b/SessionNetworkingKitTests/SOGS/Crypto/CryptoSOGSAPISpec.swift @@ -0,0 +1,180 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +import Quick +import Nimble + +@testable import SessionNetworkingKit + +class CryptoSOGSAPISpec: QuickSpec { + override class func spec() { + // MARK: Configuration + + @TestState var dependencies: TestDependencies! = TestDependencies() + @TestState(singleton: .crypto, in: dependencies) var crypto: Crypto! = Crypto(using: dependencies) + @TestState(cache: .general, in: dependencies) var mockGeneralCache: MockGeneralCache! = MockGeneralCache( + initialSetup: { cache in + cache.when { $0.sessionId }.thenReturn(SessionId(.standard, hex: TestConstants.publicKey)) + cache.when { $0.ed25519SecretKey }.thenReturn(Array(Data(hex: TestConstants.edSecretKey))) + } + ) + + // MARK: - Crypto for SOGSAPI + describe("Crypto for SOGSAPI") { + // MARK: -- when generating a blinded15 key pair + context("when generating a blinded15 key pair") { + // MARK: ---- successfully generates + it("successfully generates") { + let result = crypto.generate( + .blinded15KeyPair( + serverPublicKey: TestConstants.serverPublicKey, + ed25519SecretKey: Data(hex: TestConstants.edSecretKey).bytes + ) + ) + + // Note: The first 64 characters of the secretKey are consistent but the chars after that always differ + expect(result?.publicKey.toHexString()).to(equal(TestConstants.blind15PublicKey)) + expect(result?.secretKey.toHexString()).to(equal(TestConstants.blind15SecretKey)) + } + + // MARK: ---- fails if the edKeyPair secret key length wrong + it("fails if the ed25519SecretKey length wrong") { + let result = crypto.generate( + .blinded15KeyPair( + serverPublicKey: TestConstants.serverPublicKey, + ed25519SecretKey: Array(Data(hex: String(TestConstants.edSecretKey.prefix(4)))) + ) + ) + + expect(result).to(beNil()) + } + } + + // MARK: -- when generating a blinded25 key pair + context("when generating a blinded25 key pair") { + // MARK: ---- successfully generates + it("successfully generates") { + let result = crypto.generate( + .blinded25KeyPair( + serverPublicKey: TestConstants.serverPublicKey, + ed25519SecretKey: Data(hex: TestConstants.edSecretKey).bytes + ) + ) + + // Note: The first 64 characters of the secretKey are consistent but the chars after that always differ + expect(result?.publicKey.toHexString()).to(equal(TestConstants.blind25PublicKey)) + expect(result?.secretKey.toHexString()).to(equal(TestConstants.blind25SecretKey)) + } + + // MARK: ---- fails if the edKeyPair secret key length wrong + it("fails if the ed25519SecretKey length wrong") { + let result = crypto.generate( + .blinded25KeyPair( + serverPublicKey: TestConstants.serverPublicKey, + ed25519SecretKey: Data(hex: String(TestConstants.edSecretKey.prefix(4))).bytes + ) + ) + + expect(result).to(beNil()) + } + } + + // MARK: -- when generating a signatureBlind15 + context("when generating a signatureBlind15") { + // MARK: ---- generates a correct signature + it("generates a correct signature") { + let result = crypto.generate( + .signatureBlind15( + message: "TestMessage".bytes, + serverPublicKey: TestConstants.serverPublicKey, + ed25519SecretKey: Array(Data(hex: TestConstants.edSecretKey)) + ) + ) + + expect(result?.toHexString()) + .to(equal( + "245003f1627ebdfc6099c32597d426ef84d1b301861a5ffbbac92dde6c608334" + + "ceb56a022a094a9a664fae034b50eed40bd1bfb262c7e542c979eec265ae3f07" + )) + } + } + + // MARK: -- when generating a signatureBlind25 + context("when generating a signatureBlind25") { + // MARK: ---- generates a correct signature + it("generates a correct signature") { + let result = crypto.generate( + .signatureBlind25( + message: "TestMessage".bytes, + serverPublicKey: TestConstants.serverPublicKey, + ed25519SecretKey: Data(hex: TestConstants.edSecretKey).bytes + ) + ) + + expect(result?.toHexString()) + .to(equal( + "9ff9b7fb7d435c7a2c0b0b2ae64963baaf394386b9f7c7f924eeac44ec0f74c7" + + "fe6304c73a9b3a65491f81e44b545e54631e83e9a412eaed5fd4db2e05ec830c" + )) + } + } + + // MARK: -- when checking if a session id matches a blinded id + context("when checking if a session id matches a blinded id") { + // MARK: ---- returns true when a blind15 id matches + it("returns true when a blind15 id matches") { + let result = crypto.verify( + .sessionId( + "05\(TestConstants.publicKey)", + matchesBlindedId: "15\(TestConstants.blind15PublicKey)", + serverPublicKey: TestConstants.serverPublicKey + ) + ) + + expect(result).to(beTrue()) + } + + // MARK: ---- returns true when a blind25 id matches + it("returns true when a blind25 id matches") { + let result = crypto.verify( + .sessionId( + "05\(TestConstants.publicKey)", + matchesBlindedId: "25\(TestConstants.blind25PublicKey)", + serverPublicKey: TestConstants.serverPublicKey + ) + ) + + expect(result).to(beTrue()) + } + + // MARK: ---- returns false if given an invalid session id + it("returns false if given an invalid session id") { + let result = crypto.verify( + .sessionId( + "AB\(TestConstants.publicKey)", + matchesBlindedId: "15\(TestConstants.blind15PublicKey)", + serverPublicKey: TestConstants.serverPublicKey + ) + ) + + expect(result).to(beFalse()) + } + + // MARK: ---- returns false if given an invalid blinded id + it("returns false if given an invalid blinded id") { + let result = crypto.verify( + .sessionId( + "05\(TestConstants.publicKey)", + matchesBlindedId: "AB\(TestConstants.blind15PublicKey)", + serverPublicKey: TestConstants.serverPublicKey + ) + ) + + expect(result).to(beFalse()) + } + } + } + } +} diff --git a/SessionNetworkingKitTests/SOGS/Models/CapabilitiesResponse.swift b/SessionNetworkingKitTests/SOGS/Models/CapabilitiesResponse.swift new file mode 100644 index 0000000000..4b6ee85475 --- /dev/null +++ b/SessionNetworkingKitTests/SOGS/Models/CapabilitiesResponse.swift @@ -0,0 +1,38 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +import Quick +import Nimble + +@testable import SessionNetworkingKit + +class CapabilitiesResponseSpec: QuickSpec { + override class func spec() { + // MARK: - CapabilitiesResponse + describe("CapabilitiesResponse") { + // MARK: -- when initializing + context("when initializing") { + // MARK: ---- assigns values correctly + it("assigns values correctly") { + let capabilities: Network.SOGS.CapabilitiesResponse = Network.SOGS.CapabilitiesResponse( + capabilities: ["sogs"], + missing: ["test"] + ) + + expect(capabilities.capabilities).to(equal(["sogs"])) + expect(capabilities.missing).to(equal(["test"])) + } + + it("defaults missing to nil") { + let capabilities: Network.SOGS.CapabilitiesResponse = Network.SOGS.CapabilitiesResponse( + capabilities: ["sogs"] + ) + + expect(capabilities.capabilities).to(equal(["sogs"])) + expect(capabilities.missing).to(beNil()) + } + } + } + } +} diff --git a/SessionMessagingKitTests/Open Groups/Models/RoomPollInfoSpec.swift b/SessionNetworkingKitTests/SOGS/Models/RoomPollInfoSpec.swift similarity index 92% rename from SessionMessagingKitTests/Open Groups/Models/RoomPollInfoSpec.swift rename to SessionNetworkingKitTests/SOGS/Models/RoomPollInfoSpec.swift index 386f2f02bc..f088a86205 100644 --- a/SessionMessagingKitTests/Open Groups/Models/RoomPollInfoSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Models/RoomPollInfoSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class RoomPollInfoSpec: QuickSpec { override class func spec() { @@ -15,7 +15,7 @@ class RoomPollInfoSpec: QuickSpec { context("when initializing with a room") { // MARK: ---- copies all the relevant values across it("copies all the relevant values across") { - let room: OpenGroupAPI.Room = OpenGroupAPI.Room( + let room: Network.SOGS.Room = Network.SOGS.Room( token: "testToken", name: "testName", roomDescription: nil, @@ -42,7 +42,7 @@ class RoomPollInfoSpec: QuickSpec { upload: true, defaultUpload: true ) - let roomPollInfo: OpenGroupAPI.RoomPollInfo = OpenGroupAPI.RoomPollInfo(room: room) + let roomPollInfo: Network.SOGS.RoomPollInfo = Network.SOGS.RoomPollInfo(room: room) expect(roomPollInfo.token).to(equal(room.token)) expect(roomPollInfo.activeUsers).to(equal(room.activeUsers)) @@ -82,7 +82,7 @@ class RoomPollInfoSpec: QuickSpec { } """ let roomData: Data = roomPollInfoJson.data(using: .utf8)! - let result: OpenGroupAPI.RoomPollInfo = try! JSONDecoder().decode(OpenGroupAPI.RoomPollInfo.self, from: roomData) + let result: Network.SOGS.RoomPollInfo = try! JSONDecoder().decode(Network.SOGS.RoomPollInfo.self, from: roomData) expect(result.admin).to(beFalse()) expect(result.globalAdmin).to(beFalse()) @@ -115,7 +115,7 @@ class RoomPollInfoSpec: QuickSpec { } """ let roomData: Data = roomPollInfoJson.data(using: .utf8)! - let result: OpenGroupAPI.RoomPollInfo = try! JSONDecoder().decode(OpenGroupAPI.RoomPollInfo.self, from: roomData) + let result: Network.SOGS.RoomPollInfo = try! JSONDecoder().decode(Network.SOGS.RoomPollInfo.self, from: roomData) expect(result.admin).to(beTrue()) expect(result.globalAdmin).to(beTrue()) diff --git a/SessionMessagingKitTests/Open Groups/Models/RoomSpec.swift b/SessionNetworkingKitTests/SOGS/Models/RoomSpec.swift similarity index 93% rename from SessionMessagingKitTests/Open Groups/Models/RoomSpec.swift rename to SessionNetworkingKitTests/SOGS/Models/RoomSpec.swift index 2fd43d679b..2238a9e021 100644 --- a/SessionMessagingKitTests/Open Groups/Models/RoomSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Models/RoomSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class RoomSpec: QuickSpec { override class func spec() { @@ -45,7 +45,7 @@ class RoomSpec: QuickSpec { } """ let roomData: Data = roomJson.data(using: .utf8)! - let result: OpenGroupAPI.Room = try! JSONDecoder().decode(OpenGroupAPI.Room.self, from: roomData) + let result: Network.SOGS.Room = try! JSONDecoder().decode(Network.SOGS.Room.self, from: roomData) expect(result.admin).to(beFalse()) expect(result.globalAdmin).to(beFalse()) @@ -89,7 +89,7 @@ class RoomSpec: QuickSpec { } """ let roomData: Data = roomJson.data(using: .utf8)! - let result: OpenGroupAPI.Room = try! JSONDecoder().decode(OpenGroupAPI.Room.self, from: roomData) + let result: Network.SOGS.Room = try! JSONDecoder().decode(Network.SOGS.Room.self, from: roomData) expect(result.admin).to(beTrue()) expect(result.globalAdmin).to(beTrue()) diff --git a/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift b/SessionNetworkingKitTests/SOGS/Models/SOGSMessageSpec.swift similarity index 92% rename from SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift rename to SessionNetworkingKitTests/SOGS/Models/SOGSMessageSpec.swift index ee3f4d94ac..315f6e0e33 100644 --- a/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Models/SOGSMessageSpec.swift @@ -1,13 +1,12 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionNetworkingKit import SessionUtilitiesKit import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class SOGSMessageSpec: QuickSpec { override class func spec() { @@ -45,7 +44,7 @@ class SOGSMessageSpec: QuickSpec { } """ messageData = messageJson.data(using: .utf8)! - let result: OpenGroupAPI.Message? = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData) + let result: Network.SOGS.Message? = try? decoder.decode(Network.SOGS.Message.self, from: messageData) expect(result).toNot(beNil()) expect(result?.whisper).to(beFalse()) @@ -66,7 +65,7 @@ class SOGSMessageSpec: QuickSpec { } """ messageData = messageJson.data(using: .utf8)! - let result: OpenGroupAPI.Message? = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData) + let result: Network.SOGS.Message? = try? decoder.decode(Network.SOGS.Message.self, from: messageData) expect(result).toNot(beNil()) expect(result?.sender).to(beNil()) @@ -94,7 +93,7 @@ class SOGSMessageSpec: QuickSpec { messageData = messageJson.data(using: .utf8)! expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(NetworkError.parsingFailed)) } @@ -117,7 +116,7 @@ class SOGSMessageSpec: QuickSpec { messageData = messageJson.data(using: .utf8)! expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(NetworkError.parsingFailed)) } @@ -140,7 +139,7 @@ class SOGSMessageSpec: QuickSpec { messageData = messageJson.data(using: .utf8)! expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(NetworkError.parsingFailed)) } @@ -150,7 +149,7 @@ class SOGSMessageSpec: QuickSpec { decoder = JSONDecoder() expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(DependenciesError.missingDependencies)) } @@ -173,7 +172,7 @@ class SOGSMessageSpec: QuickSpec { messageData = messageJson.data(using: .utf8)! expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(NetworkError.parsingFailed)) } @@ -204,7 +203,7 @@ class SOGSMessageSpec: QuickSpec { .thenReturn(true) expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .toNot(beNil()) } @@ -215,7 +214,7 @@ class SOGSMessageSpec: QuickSpec { .when { $0.verify(.signature(message: .any, publicKey: .any, signature: .any)) } .thenReturn(true) - _ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData) + _ = try? decoder.decode(Network.SOGS.Message.self, from: messageData) expect(mockCrypto) .to(call(matchingParameters: .all) { @@ -236,7 +235,7 @@ class SOGSMessageSpec: QuickSpec { .thenReturn(false) expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(NetworkError.parsingFailed)) } @@ -251,7 +250,7 @@ class SOGSMessageSpec: QuickSpec { .thenReturn(true) expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .toNot(beNil()) } @@ -262,7 +261,7 @@ class SOGSMessageSpec: QuickSpec { .when { $0.verify(.signatureXed25519(.any, curve25519PublicKey: .any, data: .any)) } .thenReturn(true) - _ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData) + _ = try? decoder.decode(Network.SOGS.Message.self, from: messageData) expect(mockCrypto) .to(call(matchingParameters: .all) { @@ -283,7 +282,7 @@ class SOGSMessageSpec: QuickSpec { .thenReturn(false) expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(NetworkError.parsingFailed)) } diff --git a/SessionMessagingKitTests/Open Groups/Models/SendDirectMessageRequestSpec.swift b/SessionNetworkingKitTests/SOGS/Models/SendDirectMessageRequestSpec.swift similarity index 86% rename from SessionMessagingKitTests/Open Groups/Models/SendDirectMessageRequestSpec.swift rename to SessionNetworkingKitTests/SOGS/Models/SendDirectMessageRequestSpec.swift index 27e96dd206..c7c462f307 100644 --- a/SessionMessagingKitTests/Open Groups/Models/SendDirectMessageRequestSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Models/SendDirectMessageRequestSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class SendDirectMessageRequestSpec: QuickSpec { override class func spec() { @@ -15,7 +15,7 @@ class SendDirectMessageRequestSpec: QuickSpec { context("when encoding") { // MARK: ---- encodes the data as a base64 string it("encodes the data as a base64 string") { - let request: OpenGroupAPI.SendDirectMessageRequest = OpenGroupAPI.SendDirectMessageRequest( + let request: Network.SOGS.SendDirectMessageRequest = Network.SOGS.SendDirectMessageRequest( message: "TestData".data(using: .utf8)! ) let requestData: Data = try! JSONEncoder().encode(request) diff --git a/SessionMessagingKitTests/Open Groups/Models/SendMessageRequestSpec.swift b/SessionNetworkingKitTests/SOGS/Models/SendSOGSMessageRequestSpec.swift similarity index 82% rename from SessionMessagingKitTests/Open Groups/Models/SendMessageRequestSpec.swift rename to SessionNetworkingKitTests/SOGS/Models/SendSOGSMessageRequestSpec.swift index ec0c15d380..9c00a94671 100644 --- a/SessionMessagingKitTests/Open Groups/Models/SendMessageRequestSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Models/SendSOGSMessageRequestSpec.swift @@ -5,17 +5,17 @@ import Foundation import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit -class SendMessageRequestSpec: QuickSpec { +class SendSOGSMessageRequestSpec: QuickSpec { override class func spec() { - // MARK: - a SendMessageRequest - describe("a SendMessageRequest") { + // MARK: - a SendSOGSMessageRequest + describe("a SendSOGSMessageRequest") { // MARK: -- when initializing context("when initializing") { // MARK: ---- defaults the optional values to nil it("defaults the optional values to nil") { - let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest( + let request: Network.SOGS.SendSOGSMessageRequest = Network.SOGS.SendSOGSMessageRequest( data: "TestData".data(using: .utf8)!, signature: "TestSignature".data(using: .utf8)! ) @@ -30,7 +30,7 @@ class SendMessageRequestSpec: QuickSpec { context("when encoding") { // MARK: ---- encodes the data as a base64 string it("encodes the data as a base64 string") { - let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest( + let request: Network.SOGS.SendSOGSMessageRequest = Network.SOGS.SendSOGSMessageRequest( data: "TestData".data(using: .utf8)!, signature: "TestSignature".data(using: .utf8)!, whisperTo: nil, @@ -46,7 +46,7 @@ class SendMessageRequestSpec: QuickSpec { // MARK: ---- encodes the signature as a base64 string it("encodes the signature as a base64 string") { - let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest( + let request: Network.SOGS.SendSOGSMessageRequest = Network.SOGS.SendSOGSMessageRequest( data: "TestData".data(using: .utf8)!, signature: "TestSignature".data(using: .utf8)!, whisperTo: nil, diff --git a/SessionMessagingKitTests/Open Groups/Models/UpdateMessageRequestSpec.swift b/SessionNetworkingKitTests/SOGS/Models/UpdateMessageRequestSpec.swift similarity index 87% rename from SessionMessagingKitTests/Open Groups/Models/UpdateMessageRequestSpec.swift rename to SessionNetworkingKitTests/SOGS/Models/UpdateMessageRequestSpec.swift index 106bd04c52..847cdf7ac2 100644 --- a/SessionMessagingKitTests/Open Groups/Models/UpdateMessageRequestSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Models/UpdateMessageRequestSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class UpdateMessageRequestSpec: QuickSpec { override class func spec() { @@ -15,7 +15,7 @@ class UpdateMessageRequestSpec: QuickSpec { context("when encoding") { // MARK: ---- encodes the data as a base64 string it("encodes the data as a base64 string") { - let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest( + let request: Network.SOGS.UpdateMessageRequest = Network.SOGS.UpdateMessageRequest( data: "TestData".data(using: .utf8)!, signature: "TestSignature".data(using: .utf8)!, fileIds: nil @@ -29,7 +29,7 @@ class UpdateMessageRequestSpec: QuickSpec { // MARK: ---- encodes the signature as a base64 string it("encodes the signature as a base64 string") { - let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest( + let request: Network.SOGS.UpdateMessageRequest = Network.SOGS.UpdateMessageRequest( data: "TestData".data(using: .utf8)!, signature: "TestSignature".data(using: .utf8)!, fileIds: nil diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift similarity index 74% rename from SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift rename to SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift index 73ae2a3cc6..877b6f31da 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift @@ -3,15 +3,14 @@ import Foundation import Combine import GRDB -import SessionNetworkingKit import SessionUtilitiesKit import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit -class OpenGroupAPISpec: QuickSpec { +class SOGSAPISpec: QuickSpec { override class func spec() { // MARK: Configuration @@ -73,24 +72,21 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(Array(Array(Data(hex: TestConstants.edSecretKey)).prefix(upTo: 32))) } ) - @TestState(cache: .libSession, in: dependencies) var mockLibSessionCache: MockLibSessionCache! = MockLibSessionCache( - initialSetup: { $0.defaultInitialSetup() } - ) @TestState var disposables: [AnyCancellable]! = [] @TestState var error: Error? - // MARK: - an OpenGroupAPI - describe("an OpenGroupAPI") { + // MARK: - a SOGSAPI + describe("a SOGSAPI") { // MARK: -- when preparing a poll request context("when preparing a poll request") { - @TestState var preparedRequest: Network.PreparedRequest>? + @TestState var preparedRequest: Network.PreparedRequest>? // MARK: ---- generates the correct request it("generates the correct request") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -98,15 +94,15 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: false, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -116,20 +112,20 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.path).to(equal("/batch")) expect(preparedRequest?.method.rawValue).to(equal("POST")) expect(preparedRequest?.batchEndpoints.count).to(equal(3)) - expect(preparedRequest?.batchEndpoints[test: 0].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 0].asType(Network.SOGS.Endpoint.self)) .to(equal(.capabilities)) - expect(preparedRequest?.batchEndpoints[test: 1].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 1].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomPollInfo("testRoom", 0))) - expect(preparedRequest?.batchEndpoints[test: 2].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 2].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomMessagesRecent("testRoom"))) } // MARK: ---- retrieves recent messages if there was no last message it("retrieves recent messages if there was no last message") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -137,31 +133,31 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: false, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints[test: 2].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 2].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomMessagesRecent("testRoom"))) } // MARK: ---- retrieves recent messages if there was a last message and it has not performed the initial poll and the last message was too long ago it("retrieves recent messages if there was a last message and it has not performed the initial poll and the last message was too long ago") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 121 @@ -169,31 +165,31 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: false, - timeSinceLastPoll: (CommunityPoller.maxInactivityPeriod + 1), + timeSinceLastPoll: (Network.SOGS.maxInactivityPeriodForPolling + 1), authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints[test: 2].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 2].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomMessagesRecent("testRoom"))) } // MARK: ---- retrieves recent messages if there was a last message and it has performed an initial poll but it was not too long ago it("retrieves recent messages if there was a last message and it has performed an initial poll but it was not too long ago") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 122 @@ -201,31 +197,31 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: false, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints[test: 2].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 2].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomMessagesSince("testRoom", seqNo: 122))) } // MARK: ---- retrieves recent messages if there was a last message and there has already been a poll this session it("retrieves recent messages if there was a last message and there has already been a poll this session") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 123 @@ -233,22 +229,22 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints[test: 2].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 2].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomMessagesSince("testRoom", seqNo: 123))) } @@ -257,9 +253,9 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ does not call the inbox and outbox endpoints it("does not call the inbox and outbox endpoints") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -267,38 +263,34 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).toNot(contain(.inbox)) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).toNot(contain(.outbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).toNot(contain(.inbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).toNot(contain(.outbox)) } } // MARK: ---- when blinded and checking for message requests context("when blinded and checking for message requests") { - beforeEach { - mockLibSessionCache.when { $0.get(.checkForCommunityMessageRequests) }.thenReturn(true) - } - // MARK: ------ includes the inbox and outbox endpoints it("includes the inbox and outbox endpoints") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -306,31 +298,31 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: true, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).to(contain(.inbox)) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).to(contain(.outbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).to(contain(.inbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).to(contain(.outbox)) } // MARK: ------ retrieves recent inbox messages if there was no last message it("retrieves recent inbox messages if there was no last message") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -338,30 +330,30 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: true, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).to(contain(.inbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).to(contain(.inbox)) } // MARK: ------ retrieves inbox messages since the last message if there was one it("retrieves inbox messages since the last message if there was one") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -369,30 +361,30 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 124, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: true, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).to(contain(.inboxSince(id: 124))) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).to(contain(.inboxSince(id: 124))) } // MARK: ------ retrieves recent outbox messages if there was no last message it("retrieves recent outbox messages if there was no last message") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -400,30 +392,30 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: true, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).to(contain(.outbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).to(contain(.outbox)) } // MARK: ------ retrieves outbox messages since the last message if there was one it("retrieves outbox messages since the last message if there was one") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -431,37 +423,33 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 125, + checkForCommunityMessageRequests: true, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).to(contain(.outboxSince(id: 125))) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).to(contain(.outboxSince(id: 125))) } } // MARK: ---- when blinded and not checking for message requests context("when blinded and not checking for message requests") { - beforeEach { - mockLibSessionCache.when { $0.get(.checkForCommunityMessageRequests) }.thenReturn(false) - } - // MARK: ------ includes the inbox and outbox endpoints it("does not include the inbox endpoint") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -469,30 +457,30 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).toNot(contain(.inbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).toNot(contain(.inbox)) } // MARK: ------ does not retrieve recent inbox messages if there was no last message it("does not retrieve recent inbox messages if there was no last message") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -500,30 +488,30 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).toNot(contain(.inbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).toNot(contain(.inbox)) } // MARK: ------ does not retrieve inbox messages since the last message if there was one it("does not retrieve inbox messages since the last message if there was one") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -531,22 +519,22 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 124, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).toNot(contain(.inboxSince(id: 124))) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).toNot(contain(.inboxSince(id: 124))) } } } @@ -555,16 +543,15 @@ class OpenGroupAPISpec: QuickSpec { context("when preparing a capabilities request") { // MARK: ---- generates the request correctly it("generates the request and handles the response correctly") { - var preparedRequest: Network.PreparedRequest? + var preparedRequest: Network.PreparedRequest? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilities( + preparedRequest = try Network.SOGS.preparedCapabilities( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -580,16 +567,15 @@ class OpenGroupAPISpec: QuickSpec { context("when preparing a rooms request") { // MARK: ---- generates the request correctly it("generates the request correctly") { - var preparedRequest: Network.PreparedRequest<[OpenGroupAPI.Room]>? + var preparedRequest: Network.PreparedRequest<[Network.SOGS.Room]>? expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -603,20 +589,19 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing a capabilitiesAndRoom request context("when preparing a capabilitiesAndRoom request") { - @TestState var preparedRequest: Network.PreparedRequest? + @TestState var preparedRequest: Network.PreparedRequest? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRoom( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRoom( roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -624,9 +609,9 @@ class OpenGroupAPISpec: QuickSpec { }.toNot(throwError()) expect(preparedRequest?.batchEndpoints.count).to(equal(2)) - expect(preparedRequest?.batchEndpoints[test: 0].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 0].asType(Network.SOGS.Endpoint.self)) .to(equal(.capabilities)) - expect(preparedRequest?.batchEndpoints[test: 1].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 1].asType(Network.SOGS.Endpoint.self)) .to(equal(.room("testRoom"))) expect(preparedRequest?.path).to(equal("/sequence")) @@ -639,18 +624,17 @@ class OpenGroupAPISpec: QuickSpec { .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } .thenReturn(Network.BatchResponse.mockCapabilitiesAndRoomResponse) - var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomResponse)? + var response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesAndRoomResponse)? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRoom( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRoom( roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -675,18 +659,17 @@ class OpenGroupAPISpec: QuickSpec { .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } .thenReturn(Network.BatchResponse.mockCapabilitiesAndBanResponse) - var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomResponse)? + var response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesAndRoomResponse)? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRoom( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRoom( roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -709,18 +692,17 @@ class OpenGroupAPISpec: QuickSpec { .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } .thenReturn(Network.BatchResponse.mockBanAndRoomResponse) - var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomResponse)? + var response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesAndRoomResponse)? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRoom( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRoom( roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -740,22 +722,21 @@ class OpenGroupAPISpec: QuickSpec { } } - describe("an OpenGroupAPI") { + describe("an Network.SOGS") { // MARK: -- when preparing a capabilitiesAndRooms request context("when preparing a capabilitiesAndRooms request") { - @TestState var preparedRequest: Network.PreparedRequest? + @TestState var preparedRequest: Network.PreparedRequest? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRooms( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -763,9 +744,9 @@ class OpenGroupAPISpec: QuickSpec { }.toNot(throwError()) expect(preparedRequest?.batchEndpoints.count).to(equal(2)) - expect(preparedRequest?.batchEndpoints[test: 0].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 0].asType(Network.SOGS.Endpoint.self)) .to(equal(.capabilities)) - expect(preparedRequest?.batchEndpoints[test: 1].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 1].asType(Network.SOGS.Endpoint.self)) .to(equal(.rooms)) expect(preparedRequest?.path).to(equal("/sequence")) @@ -778,17 +759,16 @@ class OpenGroupAPISpec: QuickSpec { .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } .thenReturn(Network.BatchResponse.mockCapabilitiesAndRoomsResponse) - var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomsResponse)? + var response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesAndRoomsResponse)? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRooms( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -813,25 +793,24 @@ class OpenGroupAPISpec: QuickSpec { .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } .thenReturn( MockNetwork.batchResponseData(with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), ( - OpenGroupAPI.Endpoint.userBan(""), - OpenGroupAPI.DirectMessage.mockBatchSubResponse() + Network.SOGS.Endpoint.userBan(""), + Network.SOGS.DirectMessage.mockBatchSubResponse() ) ]) ) - var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomsResponse)? + var response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesAndRoomsResponse)? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRooms( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -854,17 +833,16 @@ class OpenGroupAPISpec: QuickSpec { .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } .thenReturn(Network.BatchResponse.mockBanAndRoomsResponse) - var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomsResponse)? + var response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesAndRoomsResponse)? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRooms( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -885,24 +863,23 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing a send message request context("when preparing a send message request") { - @TestState var preparedRequest: Network.PreparedRequest? + @TestState var preparedRequest: Network.PreparedRequest? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -918,27 +895,26 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ signs the message correctly it("signs the message correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.SendMessageRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.SendMessageRequest.self, using: dependencies) + let requestBody: Network.SOGS.SendSOGSMessageRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.SendSOGSMessageRequest.self, using: dependencies) expect(requestBody?.data).to(equal("test".data(using: .utf8))) expect(requestBody?.signature).to(equal("TestStandardSignature".data(using: .utf8))) } @@ -948,24 +924,23 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519SecretKey }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -975,24 +950,23 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519Seed }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1004,24 +978,23 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1032,27 +1005,26 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ signs the message correctly it("signs the message correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.SendMessageRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.SendMessageRequest.self, using: dependencies) + let requestBody: Network.SOGS.SendSOGSMessageRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.SendSOGSMessageRequest.self, using: dependencies) expect(requestBody?.data).to(equal("test".data(using: .utf8))) expect(requestBody?.signature).to(equal("TestSogsSignature".data(using: .utf8))) } @@ -1062,24 +1034,23 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519SecretKey }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1089,24 +1060,23 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519Seed }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1118,24 +1088,23 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1144,21 +1113,20 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing an individual message request context("when preparing an individual message request") { - var preparedRequest: Network.PreparedRequest? + var preparedRequest: Network.PreparedRequest? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedMessage( + preparedRequest = try Network.SOGS.preparedMessage( id: 123, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1177,18 +1145,17 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1204,26 +1171,25 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ signs the message correctly it("signs the message correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UpdateMessageRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UpdateMessageRequest.self, using: dependencies) + let requestBody: Network.SOGS.UpdateMessageRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UpdateMessageRequest.self, using: dependencies) expect(requestBody?.data).to(equal("test".data(using: .utf8))) expect(requestBody?.signature).to(equal("TestStandardSignature".data(using: .utf8))) } @@ -1233,23 +1199,22 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519SecretKey }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1259,23 +1224,22 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519Seed }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1287,23 +1251,22 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1314,26 +1277,25 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ signs the message correctly it("signs the message correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UpdateMessageRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UpdateMessageRequest.self, using: dependencies) + let requestBody: Network.SOGS.UpdateMessageRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UpdateMessageRequest.self, using: dependencies) expect(requestBody?.data).to(equal("test".data(using: .utf8))) expect(requestBody?.signature).to(equal("TestSogsSignature".data(using: .utf8))) } @@ -1343,23 +1305,22 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519SecretKey }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1369,23 +1330,22 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519Seed }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1397,23 +1357,22 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1427,16 +1386,15 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedMessageDelete( + preparedRequest = try Network.SOGS.preparedMessageDelete( id: 123, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1455,16 +1413,15 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedMessagesDeleteAll( + preparedRequest = try Network.SOGS.preparedMessagesDeleteAll( sessionId: "testUserId", roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1483,16 +1440,15 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedPinMessage( + preparedRequest = try Network.SOGS.preparedPinMessage( id: 123, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1511,16 +1467,15 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUnpinMessage( + preparedRequest = try Network.SOGS.preparedUnpinMessage( id: 123, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1539,15 +1494,14 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUnpinAll( + preparedRequest = try Network.SOGS.preparedUnpinAll( roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1566,16 +1520,15 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUpload( + preparedRequest = try Network.SOGS.preparedUpload( data: Data([1, 2, 3]), roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1593,23 +1546,22 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the download url string correctly it("generates the download url string correctly") { - expect(OpenGroupAPI.downloadUrlString(for: "1", server: "testserver", roomToken: "roomToken")) + expect(Network.SOGS.downloadUrlString(for: "1", server: "testserver", roomToken: "roomToken")) .to(equal("testserver/room/roomToken/file/1")) } // MARK: ---- generates the download destination correctly when given an id it("generates the download destination correctly when given an id") { expect { - preparedRequest = try OpenGroupAPI.preparedDownload( + preparedRequest = try Network.SOGS.preparedDownload( fileId: "1", roomToken: "roomToken", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1629,16 +1581,15 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the download request correctly when given a URL it("generates the download request correctly when given a URL") { expect { - preparedRequest = try OpenGroupAPI.preparedDownload( + preparedRequest = try Network.SOGS.preparedDownload( url: URL(string: "http://oxen.io/room/roomToken/file/1")!, roomToken: "roomToken", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1658,19 +1609,18 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing an inbox request context("when preparing an inbox request") { - @TestState var preparedRequest: Network.PreparedRequest<[OpenGroupAPI.DirectMessage]?>? + @TestState var preparedRequest: Network.PreparedRequest<[Network.SOGS.DirectMessage]?>? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedInbox( + preparedRequest = try Network.SOGS.preparedInbox( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1684,20 +1634,19 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing an inbox since request context("when preparing an inbox since request") { - @TestState var preparedRequest: Network.PreparedRequest<[OpenGroupAPI.DirectMessage]?>? + @TestState var preparedRequest: Network.PreparedRequest<[Network.SOGS.DirectMessage]?>? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedInboxSince( + preparedRequest = try Network.SOGS.preparedInboxSince( id: 1, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1711,19 +1660,18 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing a clear inbox request context("when preparing an inbox since request") { - @TestState var preparedRequest: Network.PreparedRequest? + @TestState var preparedRequest: Network.PreparedRequest? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedClearInbox( + preparedRequest = try Network.SOGS.preparedClearInbox( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1737,21 +1685,20 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing a send direct message request context("when preparing a send direct message request") { - @TestState var preparedRequest: Network.PreparedRequest? + @TestState var preparedRequest: Network.PreparedRequest? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( ciphertext: "test".data(using: .utf8)!, toInboxFor: "testUserId", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1764,7 +1711,7 @@ class OpenGroupAPISpec: QuickSpec { } } - describe("an OpenGroupAPI") { + describe("an Network.SOGS") { // MARK: -- when preparing a ban user request context("when preparing a ban user request") { @TestState var preparedRequest: Network.PreparedRequest? @@ -1772,17 +1719,16 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUserBan( + preparedRequest = try Network.SOGS.preparedUserBan( sessionId: "testUserId", for: nil, from: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1796,25 +1742,24 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- does a global ban if no room tokens are provided it("does a global ban if no room tokens are provided") { expect { - preparedRequest = try OpenGroupAPI.preparedUserBan( + preparedRequest = try Network.SOGS.preparedUserBan( sessionId: "testUserId", for: nil, from: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UserBanRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UserBanRequest.self, using: dependencies) + let requestBody: Network.SOGS.UserBanRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UserBanRequest.self, using: dependencies) expect(requestBody?.global).to(beTrue()) expect(requestBody?.rooms).to(beNil()) } @@ -1822,25 +1767,24 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- does room specific bans if room tokens are provided it("does room specific bans if room tokens are provided") { expect { - preparedRequest = try OpenGroupAPI.preparedUserBan( + preparedRequest = try Network.SOGS.preparedUserBan( sessionId: "testUserId", for: nil, from: ["testRoom"], authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UserBanRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UserBanRequest.self, using: dependencies) + let requestBody: Network.SOGS.UserBanRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UserBanRequest.self, using: dependencies) expect(requestBody?.global).to(beNil()) expect(requestBody?.rooms).to(equal(["testRoom"])) } @@ -1853,16 +1797,15 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUserUnban( + preparedRequest = try Network.SOGS.preparedUserUnban( sessionId: "testUserId", from: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1876,24 +1819,23 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- does a global unban if no room tokens are provided it("does a global unban if no room tokens are provided") { expect { - preparedRequest = try OpenGroupAPI.preparedUserUnban( + preparedRequest = try Network.SOGS.preparedUserUnban( sessionId: "testUserId", from: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UserUnbanRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UserUnbanRequest.self, using: dependencies) + let requestBody: Network.SOGS.UserUnbanRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UserUnbanRequest.self, using: dependencies) expect(requestBody?.global).to(beTrue()) expect(requestBody?.rooms).to(beNil()) } @@ -1901,24 +1843,23 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- does room specific unbans if room tokens are provided it("does room specific unbans if room tokens are provided") { expect { - preparedRequest = try OpenGroupAPI.preparedUserUnban( + preparedRequest = try Network.SOGS.preparedUserUnban( sessionId: "testUserId", from: ["testRoom"], authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UserUnbanRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UserUnbanRequest.self, using: dependencies) + let requestBody: Network.SOGS.UserUnbanRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UserUnbanRequest.self, using: dependencies) expect(requestBody?.global).to(beNil()) expect(requestBody?.rooms).to(equal(["testRoom"])) } @@ -1931,19 +1872,18 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUserModeratorUpdate( + preparedRequest = try Network.SOGS.preparedUserModeratorUpdate( sessionId: "testUserId", moderator: true, admin: nil, visible: true, for: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1957,27 +1897,26 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- does a global update if no room tokens are provided it("does a global update if no room tokens are provided") { expect { - preparedRequest = try OpenGroupAPI.preparedUserModeratorUpdate( + preparedRequest = try Network.SOGS.preparedUserModeratorUpdate( sessionId: "testUserId", moderator: true, admin: nil, visible: true, for: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UserModeratorRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UserModeratorRequest.self, using: dependencies) + let requestBody: Network.SOGS.UserModeratorRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UserModeratorRequest.self, using: dependencies) expect(requestBody?.global).to(beTrue()) expect(requestBody?.rooms).to(beNil()) } @@ -1985,27 +1924,26 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- does room specific updates if room tokens are provided it("does room specific updates if room tokens are provided") { expect { - preparedRequest = try OpenGroupAPI.preparedUserModeratorUpdate( + preparedRequest = try Network.SOGS.preparedUserModeratorUpdate( sessionId: "testUserId", moderator: true, admin: nil, visible: true, for: ["testRoom"], authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UserModeratorRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UserModeratorRequest.self, using: dependencies) + let requestBody: Network.SOGS.UserModeratorRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UserModeratorRequest.self, using: dependencies) expect(requestBody?.global).to(beNil()) expect(requestBody?.rooms).to(equal(["testRoom"])) } @@ -2013,19 +1951,18 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- fails if neither moderator or admin are set it("fails if neither moderator or admin are set") { expect { - preparedRequest = try OpenGroupAPI.preparedUserModeratorUpdate( + preparedRequest = try Network.SOGS.preparedUserModeratorUpdate( sessionId: "testUserId", moderator: nil, admin: nil, visible: true, for: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -2038,21 +1975,20 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing a ban and delete all request context("when preparing a ban and delete all request") { - @TestState var preparedRequest: Network.PreparedRequest>? + @TestState var preparedRequest: Network.PreparedRequest>? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUserBanAndDeleteAllMessages( + preparedRequest = try Network.SOGS.preparedUserBanAndDeleteAllMessages( sessionId: "testUserId", roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -2062,37 +1998,36 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.path).to(equal("/sequence")) expect(preparedRequest?.method.rawValue).to(equal("POST")) expect(preparedRequest?.batchEndpoints.count).to(equal(2)) - expect(preparedRequest?.batchEndpoints[test: 0].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 0].asType(Network.SOGS.Endpoint.self)) .to(equal(.userBan("testUserId"))) - expect(preparedRequest?.batchEndpoints[test: 1].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 1].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomDeleteMessages("testRoom", sessionId: "testUserId"))) } } } - describe("an OpenGroupAPI") { + describe("an Network.SOGS") { // MARK: -- when signing context("when signing") { - @TestState var preparedRequest: Network.PreparedRequest<[OpenGroupAPI.Room]>? + @TestState var preparedRequest: Network.PreparedRequest<[Network.SOGS.Room]>? // MARK: ---- fails when there is no ed25519SecretKey it("fails when there is no ed25519SecretKey") { mockGeneralCache.when { $0.ed25519SecretKey }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -2102,14 +2037,13 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ signs correctly it("signs correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -2138,19 +2072,18 @@ class OpenGroupAPISpec: QuickSpec { .thenThrow(CryptoError.failedToGenerateOutput) expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -2161,14 +2094,13 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ signs correctly it("signs correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies @@ -2197,19 +2129,18 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -2221,19 +2152,18 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -2242,27 +2172,26 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when sending context("when sending") { - @TestState var preparedRequest: Network.PreparedRequest<[OpenGroupAPI.Room]>? + @TestState var preparedRequest: Network.PreparedRequest<[Network.SOGS.Room]>? beforeEach { mockNetwork .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } - .thenReturn(MockNetwork.response(type: [OpenGroupAPI.Room].self)) + .thenReturn(MockNetwork.response(type: [Network.SOGS.Room].self)) } // MARK: ---- triggers sending correctly it("triggers sending correctly") { - var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])? + var response: (info: ResponseInfoType, data: [Network.SOGS.Room])? expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -2290,15 +2219,15 @@ extension Network.BatchResponse { static let mockCapabilitiesAndRoomResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.room("testRoom"), OpenGroupAPI.Room.mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.room("testRoom"), Network.SOGS.Room.mockBatchSubResponse()) ] ) static let mockCapabilitiesAndRoomsResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.rooms, [OpenGroupAPI.Room].mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.rooms, [Network.SOGS.Room].mockBatchSubResponse()) ] ) @@ -2306,22 +2235,22 @@ extension Network.BatchResponse { static let mockCapabilitiesAndBanResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()) ] ) static let mockBanAndRoomResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.room("testRoom"), OpenGroupAPI.Room.mockBatchSubResponse()) + (Network.SOGS.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.room("testRoom"), Network.SOGS.Room.mockBatchSubResponse()) ] ) static let mockBanAndRoomsResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.rooms, [OpenGroupAPI.Room].mockBatchSubResponse()) + (Network.SOGS.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.rooms, [Network.SOGS.Room].mockBatchSubResponse()) ] ) } diff --git a/SessionMessagingKitTests/Open Groups/Types/PersonalizationSpec.swift b/SessionNetworkingKitTests/SOGS/Types/PersonalizationSpec.swift similarity index 78% rename from SessionMessagingKitTests/Open Groups/Types/PersonalizationSpec.swift rename to SessionNetworkingKitTests/SOGS/Types/PersonalizationSpec.swift index ab6c201a14..5b24ce72de 100644 --- a/SessionMessagingKitTests/Open Groups/Types/PersonalizationSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Types/PersonalizationSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class PersonalizationSpec: QuickSpec { override class func spec() { @@ -13,9 +13,9 @@ class PersonalizationSpec: QuickSpec { describe("a Personalization") { // MARK: -- generates bytes correctly it("generates bytes correctly") { - expect(OpenGroupAPI.Personalization.sharedKeys.bytes) + expect(Network.SOGS.Personalization.sharedKeys.bytes) .to(equal([115, 111, 103, 115, 46, 115, 104, 97, 114, 101, 100, 95, 107, 101, 121, 115])) - expect(OpenGroupAPI.Personalization.authHeader.bytes) + expect(Network.SOGS.Personalization.authHeader.bytes) .to(equal([115, 111, 103, 115, 46, 97, 117, 116, 104, 95, 104, 101, 97, 100, 101, 114])) } } diff --git a/SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift b/SessionNetworkingKitTests/SOGS/Types/SOGSEndpointSpec.swift similarity index 56% rename from SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift rename to SessionNetworkingKitTests/SOGS/Types/SOGSEndpointSpec.swift index b9e37aca22..33b0929de7 100644 --- a/SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Types/SOGSEndpointSpec.swift @@ -1,13 +1,11 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionNetworkingKit -import SessionUtilitiesKit import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class SOGSEndpointSpec: QuickSpec { override class func spec() { @@ -15,12 +13,12 @@ class SOGSEndpointSpec: QuickSpec { describe("a SOGSEndpoint") { // MARK: -- provides the correct batch request variant it("provides the correct batch request variant") { - expect(OpenGroupAPI.Endpoint.batchRequestVariant).to(equal(.sogs)) + expect(Network.SOGS.Endpoint.batchRequestVariant).to(equal(.sogs)) } // MARK: -- excludes the correct headers from batch sub request it("excludes the correct headers from batch sub request") { - expect(OpenGroupAPI.Endpoint.excludedSubRequestHeaders).to(equal([ + expect(Network.SOGS.Endpoint.excludedSubRequestHeaders).to(equal([ HTTPHeader.sogsPubKey, HTTPHeader.sogsTimestamp, HTTPHeader.sogsNonce, @@ -32,53 +30,53 @@ class SOGSEndpointSpec: QuickSpec { it("generates the path value correctly") { // Utility - expect(OpenGroupAPI.Endpoint.onion.path).to(equal("oxen/v4/lsrpc")) - expect(OpenGroupAPI.Endpoint.batch.path).to(equal("batch")) - expect(OpenGroupAPI.Endpoint.sequence.path).to(equal("sequence")) - expect(OpenGroupAPI.Endpoint.capabilities.path).to(equal("capabilities")) + expect(Network.SOGS.Endpoint.onion.path).to(equal("oxen/v4/lsrpc")) + expect(Network.SOGS.Endpoint.batch.path).to(equal("batch")) + expect(Network.SOGS.Endpoint.sequence.path).to(equal("sequence")) + expect(Network.SOGS.Endpoint.capabilities.path).to(equal("capabilities")) // Rooms - expect(OpenGroupAPI.Endpoint.rooms.path).to(equal("rooms")) - expect(OpenGroupAPI.Endpoint.room("test").path).to(equal("room/test")) - expect(OpenGroupAPI.Endpoint.roomPollInfo("test", 123).path).to(equal("room/test/pollInfo/123")) + expect(Network.SOGS.Endpoint.rooms.path).to(equal("rooms")) + expect(Network.SOGS.Endpoint.room("test").path).to(equal("room/test")) + expect(Network.SOGS.Endpoint.roomPollInfo("test", 123).path).to(equal("room/test/pollInfo/123")) // Messages - expect(OpenGroupAPI.Endpoint.roomMessage("test").path).to(equal("room/test/message")) - expect(OpenGroupAPI.Endpoint.roomMessageIndividual("test", id: 123).path).to(equal("room/test/message/123")) - expect(OpenGroupAPI.Endpoint.roomMessagesRecent("test").path).to(equal("room/test/messages/recent")) - expect(OpenGroupAPI.Endpoint.roomMessagesBefore("test", id: 123).path).to(equal("room/test/messages/before/123")) - expect(OpenGroupAPI.Endpoint.roomMessagesSince("test", seqNo: 123).path) + expect(Network.SOGS.Endpoint.roomMessage("test").path).to(equal("room/test/message")) + expect(Network.SOGS.Endpoint.roomMessageIndividual("test", id: 123).path).to(equal("room/test/message/123")) + expect(Network.SOGS.Endpoint.roomMessagesRecent("test").path).to(equal("room/test/messages/recent")) + expect(Network.SOGS.Endpoint.roomMessagesBefore("test", id: 123).path).to(equal("room/test/messages/before/123")) + expect(Network.SOGS.Endpoint.roomMessagesSince("test", seqNo: 123).path) .to(equal("room/test/messages/since/123")) - expect(OpenGroupAPI.Endpoint.roomDeleteMessages("test", sessionId: "testId").path) + expect(Network.SOGS.Endpoint.roomDeleteMessages("test", sessionId: "testId").path) .to(equal("room/test/all/testId")) // Pinning - expect(OpenGroupAPI.Endpoint.roomPinMessage("test", id: 123).path).to(equal("room/test/pin/123")) - expect(OpenGroupAPI.Endpoint.roomUnpinMessage("test", id: 123).path).to(equal("room/test/unpin/123")) - expect(OpenGroupAPI.Endpoint.roomUnpinAll("test").path).to(equal("room/test/unpin/all")) + expect(Network.SOGS.Endpoint.roomPinMessage("test", id: 123).path).to(equal("room/test/pin/123")) + expect(Network.SOGS.Endpoint.roomUnpinMessage("test", id: 123).path).to(equal("room/test/unpin/123")) + expect(Network.SOGS.Endpoint.roomUnpinAll("test").path).to(equal("room/test/unpin/all")) // Files - expect(OpenGroupAPI.Endpoint.roomFile("test").path).to(equal("room/test/file")) - expect(OpenGroupAPI.Endpoint.roomFileIndividual("test", "123").path).to(equal("room/test/file/123")) + expect(Network.SOGS.Endpoint.roomFile("test").path).to(equal("room/test/file")) + expect(Network.SOGS.Endpoint.roomFileIndividual("test", "123").path).to(equal("room/test/file/123")) // Inbox/Outbox (Message Requests) - expect(OpenGroupAPI.Endpoint.inbox.path).to(equal("inbox")) - expect(OpenGroupAPI.Endpoint.inboxSince(id: 123).path).to(equal("inbox/since/123")) - expect(OpenGroupAPI.Endpoint.inboxFor(sessionId: "test").path).to(equal("inbox/test")) + expect(Network.SOGS.Endpoint.inbox.path).to(equal("inbox")) + expect(Network.SOGS.Endpoint.inboxSince(id: 123).path).to(equal("inbox/since/123")) + expect(Network.SOGS.Endpoint.inboxFor(sessionId: "test").path).to(equal("inbox/test")) - expect(OpenGroupAPI.Endpoint.outbox.path).to(equal("outbox")) - expect(OpenGroupAPI.Endpoint.outboxSince(id: 123).path).to(equal("outbox/since/123")) + expect(Network.SOGS.Endpoint.outbox.path).to(equal("outbox")) + expect(Network.SOGS.Endpoint.outboxSince(id: 123).path).to(equal("outbox/since/123")) // Users - expect(OpenGroupAPI.Endpoint.userBan("test").path).to(equal("user/test/ban")) - expect(OpenGroupAPI.Endpoint.userUnban("test").path).to(equal("user/test/unban")) - expect(OpenGroupAPI.Endpoint.userModerator("test").path).to(equal("user/test/moderator")) + expect(Network.SOGS.Endpoint.userBan("test").path).to(equal("user/test/ban")) + expect(Network.SOGS.Endpoint.userUnban("test").path).to(equal("user/test/unban")) + expect(Network.SOGS.Endpoint.userModerator("test").path).to(equal("user/test/moderator")) } } } diff --git a/SessionNetworkingKitTests/SOGS/Types/SOGSErrorSpec.swift b/SessionNetworkingKitTests/SOGS/Types/SOGSErrorSpec.swift new file mode 100644 index 0000000000..e994dea0b3 --- /dev/null +++ b/SessionNetworkingKitTests/SOGS/Types/SOGSErrorSpec.swift @@ -0,0 +1,24 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +import Quick +import Nimble + +@testable import SessionNetworkingKit + +class SOGSErrorSpec: QuickSpec { + override class func spec() { + // MARK: - a SOGSError + describe("a SOGSError") { + // MARK: -- generates the error description correctly + it("generates the error description correctly") { + expect(SOGSError.decryptionFailed.description).to(equal("Couldn't decrypt response.")) + expect(SOGSError.signingFailed.description).to(equal("Couldn't sign message.")) + expect(SOGSError.noPublicKey.description).to(equal("Couldn't find server public key.")) + expect(SOGSError.invalidEmoji.description).to(equal("The emoji is invalid.")) + expect(SOGSError.invalidPoll.description).to(equal("Poller in invalid state.")) + } + } + } +} diff --git a/SessionNetworkingKitTests/_TestUtilities/CommonSSKMockExtensions.swift b/SessionNetworkingKitTests/_TestUtilities/CommonSSKMockExtensions.swift index d99134ceba..bbd0834908 100644 --- a/SessionNetworkingKitTests/_TestUtilities/CommonSSKMockExtensions.swift +++ b/SessionNetworkingKitTests/_TestUtilities/CommonSSKMockExtensions.swift @@ -39,3 +39,94 @@ extension Network.Destination: Mocked { x25519PublicKey: "" ).withGeneratedUrl(for: MockEndpoint.mock) } + +extension Network.SOGS.CapabilitiesResponse: Mocked { + static var mock: Network.SOGS.CapabilitiesResponse = Network.SOGS.CapabilitiesResponse(capabilities: [], missing: nil) +} + +extension Network.SOGS.Room: Mocked { + static var mock: Network.SOGS.Room = Network.SOGS.Room( + token: "test", + name: "testRoom", + roomDescription: nil, + infoUpdates: 1, + messageSequence: 1, + created: 1, + activeUsers: 1, + activeUsersCutoff: 1, + imageId: nil, + pinnedMessages: nil, + admin: false, + globalAdmin: false, + admins: [], + hiddenAdmins: nil, + moderator: false, + globalModerator: false, + moderators: [], + hiddenModerators: nil, + read: true, + defaultRead: nil, + defaultAccessible: nil, + write: true, + defaultWrite: nil, + upload: true, + defaultUpload: nil + ) +} + +extension Network.SOGS.RoomPollInfo: Mocked { + static var mock: Network.SOGS.RoomPollInfo = Network.SOGS.RoomPollInfo( + token: "test", + activeUsers: 1, + admin: false, + globalAdmin: false, + moderator: false, + globalModerator: false, + read: true, + defaultRead: nil, + defaultAccessible: nil, + write: true, + defaultWrite: nil, + upload: true, + defaultUpload: false, + details: .mock + ) +} + +extension Network.SOGS.Message: Mocked { + static var mock: Network.SOGS.Message = Network.SOGS.Message( + id: 100, + sender: TestConstants.blind15PublicKey, + posted: 1, + edited: nil, + deleted: nil, + seqNo: 1, + whisper: false, + whisperMods: false, + whisperTo: nil, + base64EncodedData: nil, + base64EncodedSignature: nil, + reactions: nil + ) +} + +extension Network.SOGS.SendDirectMessageResponse: Mocked { + static var mock: Network.SOGS.SendDirectMessageResponse = Network.SOGS.SendDirectMessageResponse( + id: 1, + sender: TestConstants.blind15PublicKey, + recipient: "testRecipient", + posted: 1122, + expires: 2233 + ) +} + +extension Network.SOGS.DirectMessage: Mocked { + static var mock: Network.SOGS.DirectMessage = Network.SOGS.DirectMessage( + id: 101, + sender: TestConstants.blind15PublicKey, + recipient: "testRecipient", + posted: 1212, + expires: 2323, + base64EncodedMessage: "TestMessage".data(using: .utf8)!.base64EncodedString() + ) +} diff --git a/SessionNetworkingKitTests/_TestUtilities/MockNetwork.swift b/SessionNetworkingKitTests/_TestUtilities/MockNetwork.swift index 1412423914..eff21161ac 100644 --- a/SessionNetworkingKitTests/_TestUtilities/MockNetwork.swift +++ b/SessionNetworkingKitTests/_TestUtilities/MockNetwork.swift @@ -47,7 +47,7 @@ class MockNetwork: Mock, NetworkType { return mock(args: [body, destination, requestTimeout, requestAndPathBuildTimeout]) } - func checkClientVersion(ed25519SecretKey: [UInt8]) -> AnyPublisher<(ResponseInfoType, AppVersionResponse), Error> { + func checkClientVersion(ed25519SecretKey: [UInt8]) -> AnyPublisher<(ResponseInfoType, Network.FileServer.AppVersionResponse), Error> { return mock(args: [ed25519SecretKey]) } } diff --git a/SessionNotificationServiceExtension/NotificationResolution.swift b/SessionNotificationServiceExtension/NotificationResolution.swift index aef0805a55..c291628c84 100644 --- a/SessionNotificationServiceExtension/NotificationResolution.swift +++ b/SessionNotificationServiceExtension/NotificationResolution.swift @@ -4,11 +4,12 @@ import Foundation import SessionUIKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit enum NotificationResolution: CustomStringConvertible { - case success(PushNotificationAPI.NotificationMetadata) + case success(Network.PushNotification.NotificationMetadata) case successCall case ignoreDueToMainAppRunning @@ -20,15 +21,15 @@ enum NotificationResolution: CustomStringConvertible { case ignoreDueToMessageRequest case ignoreDueToDuplicateMessage case ignoreDueToDuplicateCall - case ignoreDueToContentSize(PushNotificationAPI.NotificationMetadata) + case ignoreDueToContentSize(Network.PushNotification.NotificationMetadata) case errorTimeout case errorNotReadyForExtensions case errorLegacyPushNotification case errorCallFailure - case errorNoContent(PushNotificationAPI.NotificationMetadata) - case errorProcessing(PushNotificationAPI.ProcessResult) - case errorMessageHandling(MessageReceiverError, PushNotificationAPI.NotificationMetadata) + case errorNoContent(Network.PushNotification.NotificationMetadata) + case errorProcessing(Network.PushNotification.ProcessResult) + case errorMessageHandling(MessageReceiverError, Network.PushNotification.NotificationMetadata) case errorOther(Error) public var description: String { @@ -88,7 +89,7 @@ enum NotificationResolution: CustomStringConvertible { } } -internal extension PushNotificationAPI.NotificationMetadata { +internal extension Network.PushNotification.NotificationMetadata { var messageOriginString: String { guard self != .invalid else { return "decryption failure" } diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 0fe6785ada..54fc5cd9a9 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -157,7 +157,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // MARK: - Notification Handling private func extractNotificationInfo(_ info: NotificationInfo) throws -> NotificationInfo { - let (maybeData, metadata, result) = PushNotificationAPI.processNotification( + let (maybeData, metadata, result) = Network.PushNotification.processNotification( notificationContent: info.content, using: dependencies ) @@ -279,7 +279,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension private func handleConfigMessage( _ notification: ProcessedNotification, swarmPublicKey: String, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, serverHash: String, serverTimestampMs: Int64, data: Data @@ -806,7 +806,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // // TODO: [Database Relocation] Need to de-database the 'preparedSubscribe' call for this to work (neeeds the AuthMethod logic to be de-databased) // /// Since this is an API call we need to wait for it to complete before we trigger the `completeSilently` logic // Log.info(.cat, "Group invitation was auto-approved, attempting to subscribe for PNs.") -// try? PushNotificationAPI +// try? Network.PushNotification // .preparedSubscribe( // db, // token: Data(hex: token), @@ -1284,7 +1284,7 @@ private extension NotificationServiceExtension { let content: UNMutableNotificationContent let requestId: String let contentHandler: ((UNNotificationContent) -> Void) - let metadata: PushNotificationAPI.NotificationMetadata + let metadata: Network.PushNotification.NotificationMetadata let data: Data let mainAppUnreadCount: Int @@ -1292,7 +1292,7 @@ private extension NotificationServiceExtension { requestId: String? = nil, content: UNMutableNotificationContent? = nil, contentHandler: ((UNNotificationContent) -> Void)? = nil, - metadata: PushNotificationAPI.NotificationMetadata? = nil, + metadata: Network.PushNotification.NotificationMetadata? = nil, mainAppUnreadCount: Int? = nil ) -> NotificationInfo { return NotificationInfo( @@ -1316,8 +1316,8 @@ private extension NotificationServiceExtension { enum NotificationError: Error { case notReadyForExtension - case processingErrorWithFallback(PushNotificationAPI.ProcessResult, PushNotificationAPI.NotificationMetadata) - case processingError(PushNotificationAPI.ProcessResult, PushNotificationAPI.NotificationMetadata) + case processingErrorWithFallback(Network.PushNotification.ProcessResult, Network.PushNotification.NotificationMetadata) + case processingError(Network.PushNotification.ProcessResult, Network.PushNotification.NotificationMetadata) case timeout } } diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index b460349b73..60295606b9 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -275,7 +275,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView dependencies[singleton: .network] .getSwarm(for: swarmPublicKey) .tryFlatMapWithRandomSnode(using: dependencies) { snode in - try SnodeAPI + try Network.SnodeAPI .preparedGetNetworkTime(from: snode, using: dependencies) .send(using: dependencies) } diff --git a/SessionTests/Onboarding/OnboardingSpec.swift b/SessionTests/Onboarding/OnboardingSpec.swift index 7cf704be92..f060ee7e98 100644 --- a/SessionTests/Onboarding/OnboardingSpec.swift +++ b/SessionTests/Onboarding/OnboardingSpec.swift @@ -121,7 +121,7 @@ class OnboardingSpec: AsyncSpec { .thenReturn(MockNetwork.batchResponseData( with: [ ( - SnodeAPI.Endpoint.getMessages, + Network.SnodeAPI.Endpoint.getMessages, GetMessagesResponse( messages: (pendingPushes? .pushData From 939ebfa72189426f3f8b1b6f987acbc0684af686 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 15 Sep 2025 15:58:30 +1000 Subject: [PATCH 50/66] Fixed a home screen refresh issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Added App, Database and Network Lifecycle ObservableEvents • Fixed a bug where log messages included the prefixes twice • Fixed an issue where the home screen wouldn't refresh after a background fetch --- Session.xcodeproj/project.pbxproj | 32 +++++++++++-------- Session/Home/HomeViewModel.swift | 17 +++++++++- ...NotificationAPI+SessionMessagingKit.swift} | 0 ...ft => Threading+SessionMessagingKit.swift} | 0 ...maryDescribable+SessionMessagingKit.swift} | 0 .../LibSession/LibSession+Networking.swift | 8 +++-- .../ObservableKey+SessionNetworkingKit.swift | 23 +++++++++++++ SessionUtilitiesKit/Database/Storage.swift | 3 ++ SessionUtilitiesKit/General/Logging.swift | 3 +- .../ObservableKey+SessionUtilitiesKit.swift | 28 ++++++++++++++++ .../Observations/ObservationManager.swift | 28 +++++++++++++++- 11 files changed, 122 insertions(+), 20 deletions(-) rename SessionMessagingKit/Sending & Receiving/Notifications/{PushNotificationAPI+SMK.swift => PushNotificationAPI+SessionMessagingKit.swift} (100%) rename SessionMessagingKit/Utilities/{Threading+SMK.swift => Threading+SessionMessagingKit.swift} (100%) rename SessionMessagingKitTests/_TestUtilities/{CustomArgSummaryDescribable+SMK.swift => CustomArgSummaryDescribable+SessionMessagingKit.swift} (100%) create mode 100644 SessionNetworkingKit/Utilities/ObservableKey+SessionNetworkingKit.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 582038180a..1980111d9a 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -586,7 +586,7 @@ FD2DD5902C6DD13C0073D9BE /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = FD2DD58F2C6DD13C0073D9BE /* DifferenceKit */; }; FD336F602CAA28CF00C0B51B /* CommonSMKMockExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F562CAA28CF00C0B51B /* CommonSMKMockExtensions.swift */; }; FD336F612CAA28CF00C0B51B /* MockNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F5C2CAA28CF00C0B51B /* MockNotificationsManager.swift */; }; - FD336F622CAA28CF00C0B51B /* CustomArgSummaryDescribable+SMK.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SMK.swift */; }; + FD336F622CAA28CF00C0B51B /* CustomArgSummaryDescribable+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SessionMessagingKit.swift */; }; FD336F632CAA28CF00C0B51B /* MockOGMCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F5D2CAA28CF00C0B51B /* MockOGMCache.swift */; }; FD336F642CAA28CF00C0B51B /* MockCommunityPollerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F582CAA28CF00C0B51B /* MockCommunityPollerCache.swift */; }; FD336F652CAA28CF00C0B51B /* MockDisplayPictureCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F592CAA28CF00C0B51B /* MockDisplayPictureCache.swift */; }; @@ -671,7 +671,7 @@ FD481A972CAE0AE000ECC4CF /* MockAppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD481A932CAE0ADD00ECC4CF /* MockAppContext.swift */; }; FD481A992CB4CAAA00ECC4CF /* MockLibSessionCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F5B2CAA28CF00C0B51B /* MockLibSessionCache.swift */; }; FD481A9A2CB4CAE500ECC4CF /* CommonSMKMockExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F562CAA28CF00C0B51B /* CommonSMKMockExtensions.swift */; }; - FD481A9B2CB4CAF100ECC4CF /* CustomArgSummaryDescribable+SMK.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SMK.swift */; }; + FD481A9B2CB4CAF100ECC4CF /* CustomArgSummaryDescribable+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SessionMessagingKit.swift */; }; FD481A9C2CB4D58300ECC4CF /* MockSnodeAPICache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765DE2AD8F03100DC1489 /* MockSnodeAPICache.swift */; }; FD481AA32CB889AE00ECC4CF /* RetrieveDefaultOpenGroupRoomsJobSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD481AA22CB889A400ECC4CF /* RetrieveDefaultOpenGroupRoomsJobSpec.swift */; }; FD49E2462B05C1D500FFBBB5 /* MockKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD49E2452B05C1D500FFBBB5 /* MockKeychain.swift */; }; @@ -779,7 +779,7 @@ FD6B92DE2E77BDE2004463B5 /* Authentication+SOGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92DC2E77BB7E004463B5 /* Authentication+SOGS.swift */; }; FD6B92E12E77C1E1004463B5 /* PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92E02E77C1DC004463B5 /* PushNotification.swift */; }; FD6B92E22E77C21D004463B5 /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; }; - FD6B92E42E77C256004463B5 /* PushNotificationAPI+SMK.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92E32E77C250004463B5 /* PushNotificationAPI+SMK.swift */; }; + FD6B92E42E77C256004463B5 /* PushNotificationAPI+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92E32E77C250004463B5 /* PushNotificationAPI+SessionMessagingKit.swift */; }; FD6B92E62E77C5A2004463B5 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D482A16EC20007267C7 /* Service.swift */; }; FD6B92E72E77C5A2004463B5 /* Request+PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD02CC112C367761009AB976 /* Request+PushNotificationAPI.swift */; }; FD6B92E82E77C5B7004463B5 /* PushNotificationEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D4F2A16EE50007267C7 /* PushNotificationEndpoint.swift */; }; @@ -1041,6 +1041,7 @@ FDE521A22E0D23AB00061B8E /* ObservableKey+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE521A12E0D23A200061B8E /* ObservableKey+SessionMessagingKit.swift */; }; FDE521A62E0E6C8C00061B8E /* MockNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F5C2CAA28CF00C0B51B /* MockNotificationsManager.swift */; }; FDE6E99829F8E63A00F93C5D /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE6E99729F8E63A00F93C5D /* Accessibility.swift */; }; + FDE71B052E77E1AA0023F5F9 /* ObservableKey+SessionNetworkingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE71B042E77E1A00023F5F9 /* ObservableKey+SessionNetworkingKit.swift */; }; FDE7549B2C940108002A2623 /* MessageViewModel+DeletionActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7549A2C940108002A2623 /* MessageViewModel+DeletionActions.swift */; }; FDE7549D2C9961A4002A2623 /* CommunityPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7549C2C9961A4002A2623 /* CommunityPoller.swift */; }; FDE754A12C9A60A6002A2623 /* Crypto+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A02C9A60A6002A2623 /* Crypto+OpenGroup.swift */; }; @@ -1079,7 +1080,7 @@ FDE754F82C9BB0B0002A2623 /* UserNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754F32C9BB0AF002A2623 /* UserNotificationConfig.swift */; }; FDE754F92C9BB0B0002A2623 /* NotificationActionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754F42C9BB0AF002A2623 /* NotificationActionHandler.swift */; }; FDE754FA2C9BB0B0002A2623 /* NotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754F52C9BB0AF002A2623 /* NotificationPresenter.swift */; }; - FDE754FE2C9BB0D0002A2623 /* Threading+SMK.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754FD2C9BB0D0002A2623 /* Threading+SMK.swift */; }; + FDE754FE2C9BB0D0002A2623 /* Threading+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754FD2C9BB0D0002A2623 /* Threading+SessionMessagingKit.swift */; }; FDE755002C9BB0FA002A2623 /* SessionEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754FF2C9BB0FA002A2623 /* SessionEnvironment.swift */; }; FDE755022C9BB122002A2623 /* _025_AddPendingReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755012C9BB122002A2623 /* _025_AddPendingReadReceipts.swift */; }; FDE755052C9BB4EE002A2623 /* BencodeDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755032C9BB4ED002A2623 /* BencodeDecoder.swift */; }; @@ -1934,7 +1935,7 @@ FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronousStorage.swift; sourceTree = ""; }; FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueryInterfaceRequest+Utilities.swift"; sourceTree = ""; }; FD336F562CAA28CF00C0B51B /* CommonSMKMockExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonSMKMockExtensions.swift; sourceTree = ""; }; - FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SMK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CustomArgSummaryDescribable+SMK.swift"; sourceTree = ""; }; + FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CustomArgSummaryDescribable+SessionMessagingKit.swift"; sourceTree = ""; }; FD336F582CAA28CF00C0B51B /* MockCommunityPollerCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCommunityPollerCache.swift; sourceTree = ""; }; FD336F592CAA28CF00C0B51B /* MockDisplayPictureCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDisplayPictureCache.swift; sourceTree = ""; }; FD336F5A2CAA28CF00C0B51B /* MockGroupPollerCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGroupPollerCache.swift; sourceTree = ""; }; @@ -2066,7 +2067,7 @@ FD6B92DA2E77B592004463B5 /* CryptoSOGSAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSOGSAPISpec.swift; sourceTree = ""; }; FD6B92DC2E77BB7E004463B5 /* Authentication+SOGS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Authentication+SOGS.swift"; sourceTree = ""; }; FD6B92E02E77C1DC004463B5 /* PushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotification.swift; sourceTree = ""; }; - FD6B92E32E77C250004463B5 /* PushNotificationAPI+SMK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PushNotificationAPI+SMK.swift"; sourceTree = ""; }; + FD6B92E32E77C250004463B5 /* PushNotificationAPI+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PushNotificationAPI+SessionMessagingKit.swift"; sourceTree = ""; }; FD6B92F62E77C6D3004463B5 /* Crypto+PushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+PushNotification.swift"; sourceTree = ""; }; FD6C67232CF6E72900B350A7 /* NoopSessionCallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopSessionCallManager.swift; sourceTree = ""; }; FD6DF00A2ACFE40D0084BA4C /* _021_AddSnodeReveivedMessageInfoPrimaryKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _021_AddSnodeReveivedMessageInfoPrimaryKey.swift; sourceTree = ""; }; @@ -2307,6 +2308,7 @@ FDE5219F2E0D22FD00061B8E /* ObservationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservationManager.swift; sourceTree = ""; }; FDE521A12E0D23A200061B8E /* ObservableKey+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObservableKey+SessionMessagingKit.swift"; sourceTree = ""; }; FDE6E99729F8E63A00F93C5D /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = ""; }; + FDE71B042E77E1A00023F5F9 /* ObservableKey+SessionNetworkingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObservableKey+SessionNetworkingKit.swift"; sourceTree = ""; }; FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = ""; }; FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintLocalizableStrings.swift; sourceTree = ""; }; FDE7549A2C940108002A2623 /* MessageViewModel+DeletionActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageViewModel+DeletionActions.swift"; sourceTree = ""; }; @@ -2348,7 +2350,7 @@ FDE754F32C9BB0AF002A2623 /* UserNotificationConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNotificationConfig.swift; sourceTree = ""; }; FDE754F42C9BB0AF002A2623 /* NotificationActionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationActionHandler.swift; sourceTree = ""; }; FDE754F52C9BB0AF002A2623 /* NotificationPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationPresenter.swift; sourceTree = ""; }; - FDE754FD2C9BB0D0002A2623 /* Threading+SMK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Threading+SMK.swift"; sourceTree = ""; }; + FDE754FD2C9BB0D0002A2623 /* Threading+SessionMessagingKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Threading+SessionMessagingKit.swift"; sourceTree = ""; }; FDE754FF2C9BB0FA002A2623 /* SessionEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionEnvironment.swift; sourceTree = ""; }; FDE755012C9BB122002A2623 /* _025_AddPendingReadReceipts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _025_AddPendingReadReceipts.swift; sourceTree = ""; }; FDE755032C9BB4ED002A2623 /* BencodeDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BencodeDecoder.swift; sourceTree = ""; }; @@ -3581,7 +3583,7 @@ children = ( FDC13D4E2A16EE41007267C7 /* Types */, FDF0B7502807BA56004C14C5 /* NotificationsManagerType.swift */, - FD6B92E32E77C250004463B5 /* PushNotificationAPI+SMK.swift */, + FD6B92E32E77C250004463B5 /* PushNotificationAPI+SessionMessagingKit.swift */, ); path = Notifications; sourceTree = ""; @@ -3674,7 +3676,7 @@ C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */, FDE754FF2C9BB0FA002A2623 /* SessionEnvironment.swift */, C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */, - FDE754FD2C9BB0D0002A2623 /* Threading+SMK.swift */, + FDE754FD2C9BB0D0002A2623 /* Threading+SessionMessagingKit.swift */, FD72BDA02BE368C800CF6CF6 /* UIWindowLevel+Utilities.swift */, ); path = Utilities; @@ -3711,6 +3713,7 @@ isa = PBXGroup; children = ( C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */, + FDE71B042E77E1A00023F5F9 /* ObservableKey+SessionNetworkingKit.swift */, FD2272BD2C34B710004D8A6C /* Publisher+Utilities.swift */, FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */, C3C2A5D22553860900C340D1 /* String+Trimming.swift */, @@ -5019,7 +5022,7 @@ isa = PBXGroup; children = ( FD336F562CAA28CF00C0B51B /* CommonSMKMockExtensions.swift */, - FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SMK.swift */, + FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SessionMessagingKit.swift */, FD336F6E2CAA37CB00C0B51B /* MockCommunityPoller.swift */, FD336F582CAA28CF00C0B51B /* MockCommunityPollerCache.swift */, FD336F592CAA28CF00C0B51B /* MockDisplayPictureCache.swift */, @@ -6371,6 +6374,7 @@ FD6B92B32E77AA03004463B5 /* Personalization.swift in Sources */, FD6B929B2E77A084004463B5 /* NetworkInfo.swift in Sources */, FD47E0B52AA6D7AA00A55E41 /* Request+SnodeAPI.swift in Sources */, + FDE71B052E77E1AA0023F5F9 /* ObservableKey+SessionNetworkingKit.swift in Sources */, FD5E93D12C100FD70038C25A /* FileUploadResponse.swift in Sources */, FDF848D629405C5B007DCAE5 /* SnodeMessage.swift in Sources */, FD02CC162C3681EF009AB976 /* RevokeSubaccountResponse.swift in Sources */, @@ -6803,12 +6807,12 @@ FDF71EA32B072C2800A8D6B5 /* LibSessionMessage.swift in Sources */, FDAA167D2AC528A200DDBF77 /* Preferences+Sound.swift in Sources */, FDD23AED2E4590A10057E853 /* _041_RenameTableSettingToKeyValueStore.swift in Sources */, - FDE754FE2C9BB0D0002A2623 /* Threading+SMK.swift in Sources */, + FDE754FE2C9BB0D0002A2623 /* Threading+SessionMessagingKit.swift in Sources */, FDF0B75E280AAF35004C14C5 /* Preferences.swift in Sources */, FDF2F0222DAE1AF500491E8A /* MessageReceiver+LegacyClosedGroups.swift in Sources */, FD05594E2E012D2700DC48CE /* _043_RenameAttachments.swift in Sources */, FD22726E2C32911C004D8A6C /* FailedMessageSendsJob.swift in Sources */, - FD6B92E42E77C256004463B5 /* PushNotificationAPI+SMK.swift in Sources */, + FD6B92E42E77C256004463B5 /* PushNotificationAPI+SessionMessagingKit.swift in Sources */, FDB5DAD42A9483F3002C8721 /* GroupUpdateInviteMessage.swift in Sources */, FDB5DAE22A95D8A0002C8721 /* GroupUpdateInviteResponseMessage.swift in Sources */, FDB11A522DCC6B0000BEF49F /* OpenGroupUrlInfo.swift in Sources */, @@ -7075,7 +7079,7 @@ FD71161528D00D6700B47552 /* ThreadDisappearingMessagesViewModelSpec.swift in Sources */, FD01503A2CA24328005B08A1 /* MockJobRunner.swift in Sources */, FD23EA5E28ED00FD0058676E /* NimbleExtensions.swift in Sources */, - FD481A9B2CB4CAF100ECC4CF /* CustomArgSummaryDescribable+SMK.swift in Sources */, + FD481A9B2CB4CAF100ECC4CF /* CustomArgSummaryDescribable+SessionMessagingKit.swift in Sources */, FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */, FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */, FDFE75B52ABD46B700655640 /* MockUserDefaults.swift in Sources */, @@ -7203,7 +7207,7 @@ FD981BCD2DC81ABF00564172 /* MockExtensionHelper.swift in Sources */, FD336F602CAA28CF00C0B51B /* CommonSMKMockExtensions.swift in Sources */, FD336F612CAA28CF00C0B51B /* MockNotificationsManager.swift in Sources */, - FD336F622CAA28CF00C0B51B /* CustomArgSummaryDescribable+SMK.swift in Sources */, + FD336F622CAA28CF00C0B51B /* CustomArgSummaryDescribable+SessionMessagingKit.swift in Sources */, FD336F632CAA28CF00C0B51B /* MockOGMCache.swift in Sources */, FD481A922CAD17DE00ECC4CF /* LibSessionGroupMembersSpec.swift in Sources */, FD336F642CAA28CF00C0B51B /* MockCommunityPollerCache.swift in Sources */, diff --git a/Session/Home/HomeViewModel.swift b/Session/Home/HomeViewModel.swift index 6ec7b202bb..98d29d990e 100644 --- a/Session/Home/HomeViewModel.swift +++ b/Session/Home/HomeViewModel.swift @@ -104,6 +104,8 @@ public class HomeViewModel: NavigatableStateHolder { public var observedKeys: Set { var result: Set = [ + .appLifecycle(.willEnterForeground), + .databaseLifecycle(.resumed), .loadPage(HomeViewModel.self), .messageRequestAccepted, .messageRequestDeleted, @@ -245,7 +247,7 @@ public class HomeViewModel: NavigatableStateHolder { } /// Handle database events first - if let databaseEvents: Set = splitEvents[.databaseQuery], !databaseEvents.isEmpty { + if !dependencies[singleton: .storage].isSuspended, let databaseEvents: Set = splitEvents[.databaseQuery], !databaseEvents.isEmpty { do { var fetchedConversations: [SessionThreadViewModel] = [] let idsNeedingRequery: Set = self.extractIdsNeedingRequery( @@ -354,6 +356,9 @@ public class HomeViewModel: NavigatableStateHolder { Log.critical(.homeViewModel, "Failed to fetch state for events [\(eventList)], due to error: \(error)") } } + else if let databaseEvents: Set = splitEvents[.databaseQuery], !databaseEvents.isEmpty { + Log.warn(.homeViewModel, "Ignored \(databaseEvents.count) database event(s) sent while storage was suspended.") + } /// Then handle non-database events let groupedOtherEvents: [GenericObservableKey: Set]? = splitEvents[.other]? @@ -445,6 +450,15 @@ public class HomeViewModel: NavigatableStateHolder { events: Set, cache: [String: SessionThreadViewModel] ) -> Set { + let requireFullRefresh: Bool = events.contains(where: { event in + event.key == .appLifecycle(.willEnterForeground) || + event.key == .databaseLifecycle(.resumed) + }) + + guard !requireFullRefresh else { + return Set(cache.keys) + } + return events.reduce(into: []) { result, event in switch (event.key.generic, event.value) { case (.conversationUpdated, let event as ConversationEvent): result.insert(event.id) @@ -741,6 +755,7 @@ private extension ObservedEvent { case (.feature(.forceOffline), _): return .other case (.setting(.hasViewedSeed), _): return .other + case (.appLifecycle(.willEnterForeground), _): return .databaseQuery case (.messageRequestUnreadMessageReceived, _), (.messageRequestAccepted, _), (.messageRequestDeleted, _), (.messageRequestMessageRead, _): return .databaseQuery diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI+SMK.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI+SessionMessagingKit.swift similarity index 100% rename from SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI+SMK.swift rename to SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI+SessionMessagingKit.swift diff --git a/SessionMessagingKit/Utilities/Threading+SMK.swift b/SessionMessagingKit/Utilities/Threading+SessionMessagingKit.swift similarity index 100% rename from SessionMessagingKit/Utilities/Threading+SMK.swift rename to SessionMessagingKit/Utilities/Threading+SessionMessagingKit.swift diff --git a/SessionMessagingKitTests/_TestUtilities/CustomArgSummaryDescribable+SMK.swift b/SessionMessagingKitTests/_TestUtilities/CustomArgSummaryDescribable+SessionMessagingKit.swift similarity index 100% rename from SessionMessagingKitTests/_TestUtilities/CustomArgSummaryDescribable+SMK.swift rename to SessionMessagingKitTests/_TestUtilities/CustomArgSummaryDescribable+SessionMessagingKit.swift diff --git a/SessionNetworkingKit/LibSession/LibSession+Networking.swift b/SessionNetworkingKit/LibSession/LibSession+Networking.swift index a0a2be6d27..53fabef8b7 100644 --- a/SessionNetworkingKit/LibSession/LibSession+Networking.swift +++ b/SessionNetworkingKit/LibSession/LibSession+Networking.swift @@ -704,23 +704,27 @@ public extension LibSession { // MARK: - Functions public func suspendNetworkAccess() { - Log.info(.network, "Network access suspended.") isSuspended = true + Log.info(.network, "Network access suspended.") switch network { case .none: break case .some(let network): network_suspend(network) } + + dependencies.notifyAsync(key: .networkLifecycle(.suspended)) } public func resumeNetworkAccess() { isSuspended = false - Log.info(.network, "Network access resumed.") switch network { case .none: break case .some(let network): network_resume(network) } + + Log.info(.network, "Network access resumed.") + dependencies.notifyAsync(key: .networkLifecycle(.resumed)) } public func getOrCreateNetwork() -> AnyPublisher?, Error> { diff --git a/SessionNetworkingKit/Utilities/ObservableKey+SessionNetworkingKit.swift b/SessionNetworkingKit/Utilities/ObservableKey+SessionNetworkingKit.swift new file mode 100644 index 0000000000..372aaf717d --- /dev/null +++ b/SessionNetworkingKit/Utilities/ObservableKey+SessionNetworkingKit.swift @@ -0,0 +1,23 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import SessionUtilitiesKit + +public extension ObservableKey { + static func networkLifecycle(_ event: NetworkLifecycle) -> ObservableKey { + ObservableKey("networkLifecycle-\(event)", .networkLifecycle) + } +} + +public extension GenericObservableKey { + static let networkLifecycle: GenericObservableKey = "networkLifecycle" +} + +// MARK: - NetworkLifecycle + +public enum NetworkLifecycle: String, Sendable { + case suspended + case resumed +} diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index e9b4cfbc4f..09817afb74 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -453,6 +453,8 @@ open class Storage { .defaulting(to: "N/A") Log.verbose(.storage, "Database suspended successfully for \(id) (db: \(dbFileSize), shm: \(dbShmFileSize), wal: \(dbWalFileSize)).") } + + dependencies.notifyAsync(key: .databaseLifecycle(.suspended)) } /// This method reverses the database suspension used to prevent the `0xdead10cc` exception (see `suspendDatabaseAccess()` @@ -462,6 +464,7 @@ open class Storage { isSuspended = false Log.info(.storage, "Database access resumed.") + dependencies.notifyAsync(key: .databaseLifecycle(.resumed)) } public func checkpoint(_ mode: Database.CheckpointMode) throws { diff --git a/SessionUtilitiesKit/General/Logging.swift b/SessionUtilitiesKit/General/Logging.swift index 9bd2646f9e..67b529d8ec 100644 --- a/SessionUtilitiesKit/General/Logging.swift +++ b/SessionUtilitiesKit/General/Logging.swift @@ -730,8 +730,7 @@ public actor Logger: LoggerType { }() /// Clean up the message if needed (replace double periods with single, trim whitespace, truncate pubkeys) - let cleanedMessage: String = logPrefix - .appending(message) + let cleanedMessage: String = message .replacingOccurrences(of: "...", with: "|||") .replacingOccurrences(of: "..", with: ".") .replacingOccurrences(of: "|||", with: "...") diff --git a/SessionUtilitiesKit/Observations/ObservableKey+SessionUtilitiesKit.swift b/SessionUtilitiesKit/Observations/ObservableKey+SessionUtilitiesKit.swift index a2c475978f..e26fa09e6d 100644 --- a/SessionUtilitiesKit/Observations/ObservableKey+SessionUtilitiesKit.swift +++ b/SessionUtilitiesKit/Observations/ObservableKey+SessionUtilitiesKit.swift @@ -5,6 +5,14 @@ import Foundation public extension ObservableKey { + static func appLifecycle(_ event: AppLifecycle) -> ObservableKey { + ObservableKey("appLifecycle-\(event)", .appLifecycle) + } + + static func databaseLifecycle(_ event: DatabaseLifecycle) -> ObservableKey { + ObservableKey("databaseLifecycle-\(event)", .databaseLifecycle) + } + static func feature(_ key: FeatureConfig) -> ObservableKey { ObservableKey(key.identifier, .feature) } @@ -17,6 +25,26 @@ public extension ObservableKey { } public extension GenericObservableKey { + static let appLifecycle: GenericObservableKey = "appLifecycle" + static let databaseLifecycle: GenericObservableKey = "databaseLifecycle" static let feature: GenericObservableKey = "feature" static let featureGroup: GenericObservableKey = "featureGroup" } + +// MARK: - AppLifecycle + +public enum AppLifecycle: String, Sendable { + case didEnterBackground + case willEnterForeground + case didBecomeActive + case willResignActive + case didReceiveMemoryWarning + case willTerminate +} + +// MARK: - DatabaseLifecycle + +public enum DatabaseLifecycle: String, Sendable { + case suspended + case resumed +} diff --git a/SessionUtilitiesKit/Observations/ObservationManager.swift b/SessionUtilitiesKit/Observations/ObservationManager.swift index 4bff64a107..e0b8388818 100644 --- a/SessionUtilitiesKit/Observations/ObservationManager.swift +++ b/SessionUtilitiesKit/Observations/ObservationManager.swift @@ -1,22 +1,48 @@ // Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. import Foundation +import UIKit.UIApplication // MARK: - Singleton public extension Singleton { static let observationManager: SingletonConfig = Dependencies.create( identifier: "observationManager", - createInstance: { dependencies in ObservationManager() } + createInstance: { dependencies in ObservationManager(using: dependencies) } ) } // MARK: - ObservationManager public actor ObservationManager { + private let lifecycleObservations: [any NSObjectProtocol] private var store: [ObservableKey: [UUID: AsyncStream<(event: ObservedEvent, priority: Priority)>.Continuation]] = [:] + // MARK: - Initialization + + init(using dependencies: Dependencies) { + let notifications: [Notification.Name: AppLifecycle] = [ + UIApplication.didEnterBackgroundNotification: .didEnterBackground, + UIApplication.willEnterForegroundNotification: .willEnterForeground, + UIApplication.didBecomeActiveNotification: .didBecomeActive, + UIApplication.willResignActiveNotification: .willResignActive, + UIApplication.didReceiveMemoryWarningNotification: .didReceiveMemoryWarning, + UIApplication.willTerminateNotification: .willTerminate + ] + + lifecycleObservations = notifications.reduce(into: []) { [dependencies] result, next in + let value: AppLifecycle = next.value + + result.append( + NotificationCenter.default.addObserver(forName: next.key, object: nil, queue: .current) { [dependencies] _ in + dependencies.notifyAsync(key: .appLifecycle(value)) + } + ) + } + } + deinit { + NotificationCenter.default.removeObserver(self) store.values.forEach { $0.values.forEach { $0.finish() } } } From 1ccd99b4abfaf2006f477c8e1fe413ec6478d646 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 15 Sep 2025 16:12:09 +1000 Subject: [PATCH 51/66] Fixed a recovery password string which didn't have styling applied --- Session/Settings/RecoveryPasswordScreen.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Session/Settings/RecoveryPasswordScreen.swift b/Session/Settings/RecoveryPasswordScreen.swift index 9afd6dcd93..1fd3034852 100644 --- a/Session/Settings/RecoveryPasswordScreen.swift +++ b/Session/Settings/RecoveryPasswordScreen.swift @@ -66,11 +66,15 @@ struct RecoveryPasswordScreen: View { } .padding(.bottom, Values.smallSpacing) - Text("recoveryPasswordDescription".localized()) - .font(.system(size: Values.smallFontSize)) - .foregroundColor(themeColor: .textPrimary) - .padding(.bottom, Values.mediumSpacing) - .fixedSize(horizontal: false, vertical: true) + AttributedText( + "recoveryPasswordDescription".localizedFormatted( + baseFont: .systemFont(ofSize: Values.smallFontSize) + ) + ) + .font(.system(size: Values.smallFontSize)) + .foregroundColor(themeColor: .textPrimary) + .padding(.bottom, Values.mediumSpacing) + .fixedSize(horizontal: false, vertical: true) if self.showQRCode { QRCodeView( From ade137aecf22c5d4b76911944dabf6821846e1c3 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 16 Sep 2025 11:23:05 +1000 Subject: [PATCH 52/66] Bumped build and version numbers --- Session.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 1980111d9a..ac08c3f795 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -8300,7 +8300,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COMPILE_LIB_SESSION = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 626; + CURRENT_PROJECT_VERSION = 632; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -8340,7 +8340,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.6; LIB_SESSION_SOURCE_DIR = "${SRCROOT}/../LibSession-Util"; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; - MARKETING_VERSION = 2.14.2; + MARKETING_VERSION = 2.14.3; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-Werror=protocol"; OTHER_SWIFT_FLAGS = "-D DEBUG -Xfrontend -warn-long-expression-type-checking=100"; @@ -8381,7 +8381,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; COMPILE_LIB_SESSION = ""; - CURRENT_PROJECT_VERSION = 626; + CURRENT_PROJECT_VERSION = 632; ENABLE_BITCODE = NO; ENABLE_MODULE_VERIFIER = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -8416,7 +8416,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.6; LIB_SESSION_SOURCE_DIR = "${SRCROOT}/../LibSession-Util"; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; - MARKETING_VERSION = 2.14.2; + MARKETING_VERSION = 2.14.3; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "-DNS_BLOCK_ASSERTIONS=1", @@ -8862,7 +8862,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COMPILE_LIB_SESSION = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 626; + CURRENT_PROJECT_VERSION = 632; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -8901,7 +8901,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.6; LIB_SESSION_SOURCE_DIR = "${SRCROOT}/../LibSession-Util"; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; - MARKETING_VERSION = 2.14.2; + MARKETING_VERSION = 2.14.3; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( "-fobjc-arc-exceptions", @@ -9449,7 +9449,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; COMPILE_LIB_SESSION = YES; - CURRENT_PROJECT_VERSION = 626; + CURRENT_PROJECT_VERSION = 632; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -9482,7 +9482,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.6; LIB_SESSION_SOURCE_DIR = "${SRCROOT}/../LibSession-Util"; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; - MARKETING_VERSION = 2.14.2; + MARKETING_VERSION = 2.14.3; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "-DNS_BLOCK_ASSERTIONS=1", From 4202b5451ca1fb01b3df986dff25ab49e4bc1ad5 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 16 Sep 2025 12:11:29 +1000 Subject: [PATCH 53/66] Resolved a TODO that was missed in the last release --- Session/Home/App Review/AppReviewPromptModel.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Session/Home/App Review/AppReviewPromptModel.swift b/Session/Home/App Review/AppReviewPromptModel.swift index 9d9d82e2c8..d7c766e66c 100644 --- a/Session/Home/App Review/AppReviewPromptModel.swift +++ b/Session/Home/App Review/AppReviewPromptModel.swift @@ -17,8 +17,7 @@ struct AppReviewPromptModel { extension AppReviewPromptModel { // Base version where app review prompt became available - // TODO: Update this once a version to include app review prompt is decided - static private let reviewPromptAvailabilityVersion = "2.14.1" // stringlint:ignore + static private let reviewPromptAvailabilityVersion = "2.14.2" // stringlint:ignore /// Determines the initial state of the app review prompt. static func loadInitialAppReviewPromptState(using dependencies: Dependencies) -> AppReviewPromptState? { From d56923a7e58c5240f5ed525d935925cff5dd5fca Mon Sep 17 00:00:00 2001 From: mikoldin Date: Tue, 16 Sep 2025 11:15:40 +0800 Subject: [PATCH 54/66] Fix missing `session_foundation` value for voice call dialog --- Session/Settings/PrivacySettingsViewModel.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Session/Settings/PrivacySettingsViewModel.swift b/Session/Settings/PrivacySettingsViewModel.swift index 86f4dc37b7..89e7efc94c 100644 --- a/Session/Settings/PrivacySettingsViewModel.swift +++ b/Session/Settings/PrivacySettingsViewModel.swift @@ -233,7 +233,9 @@ class PrivacySettingsViewModel: SessionTableViewModel, NavigationItemSource, Nav ), confirmationInfo: ConfirmationModal.Info( title: "callsVoiceAndVideoBeta".localized(), - body: .text("callsVoiceAndVideoModalDescription".localized()), + body: .text("callsVoiceAndVideoModalDescription" + .put(key: "session_foundation", value: Constants.session_foundation) + .localized()), showCondition: .disabled, confirmTitle: "theContinue".localized(), confirmStyle: .danger, From 3f7dba7bd9b9a1c37bed76cc71b60c75ead45ff8 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 16 Sep 2025 17:09:26 +1000 Subject: [PATCH 55/66] Started working on adding dev settings to test subscriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Split group and pro developer settings into their own screens • Tweaked the SessionCell so it would respect theming included in subtitles • Added a few more themes for attributed strings (mostly to make dev settings a bit nicer) --- Session.xcodeproj/project.pbxproj | 20 +- .../DeveloperSettingsGroupsViewModel.swift | 391 ++++++++++++++++ .../DeveloperSettingsProViewModel.swift | 439 ++++++++++++++++++ .../DeveloperSettingsViewModel+Testing.swift | 0 .../DeveloperSettingsViewModel.swift | 413 ++-------------- Session/Shared/Views/SessionCell.swift | 4 +- .../Utilities/Localization+Style.swift | 9 + 7 files changed, 899 insertions(+), 377 deletions(-) create mode 100644 Session/Settings/DeveloperSettings/DeveloperSettingsGroupsViewModel.swift create mode 100644 Session/Settings/DeveloperSettings/DeveloperSettingsProViewModel.swift rename Session/Settings/{ => DeveloperSettings}/DeveloperSettingsViewModel+Testing.swift (100%) rename Session/Settings/{ => DeveloperSettings}/DeveloperSettingsViewModel.swift (79%) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 582038180a..e6fc67b80f 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -1041,6 +1041,8 @@ FDE521A22E0D23AB00061B8E /* ObservableKey+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE521A12E0D23A200061B8E /* ObservableKey+SessionMessagingKit.swift */; }; FDE521A62E0E6C8C00061B8E /* MockNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F5C2CAA28CF00C0B51B /* MockNotificationsManager.swift */; }; FDE6E99829F8E63A00F93C5D /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE6E99729F8E63A00F93C5D /* Accessibility.swift */; }; + FDE71B0B2E79352D0023F5F9 /* DeveloperSettingsGroupsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE71B0A2E7935250023F5F9 /* DeveloperSettingsGroupsViewModel.swift */; }; + FDE71B0D2E793B250023F5F9 /* DeveloperSettingsProViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE71B0C2E793B1F0023F5F9 /* DeveloperSettingsProViewModel.swift */; }; FDE7549B2C940108002A2623 /* MessageViewModel+DeletionActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7549A2C940108002A2623 /* MessageViewModel+DeletionActions.swift */; }; FDE7549D2C9961A4002A2623 /* CommunityPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7549C2C9961A4002A2623 /* CommunityPoller.swift */; }; FDE754A12C9A60A6002A2623 /* Crypto+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A02C9A60A6002A2623 /* Crypto+OpenGroup.swift */; }; @@ -2307,6 +2309,8 @@ FDE5219F2E0D22FD00061B8E /* ObservationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservationManager.swift; sourceTree = ""; }; FDE521A12E0D23A200061B8E /* ObservableKey+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObservableKey+SessionMessagingKit.swift"; sourceTree = ""; }; FDE6E99729F8E63A00F93C5D /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = ""; }; + FDE71B0A2E7935250023F5F9 /* DeveloperSettingsGroupsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsGroupsViewModel.swift; sourceTree = ""; }; + FDE71B0C2E793B1F0023F5F9 /* DeveloperSettingsProViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsProViewModel.swift; sourceTree = ""; }; FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = ""; }; FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintLocalizableStrings.swift; sourceTree = ""; }; FDE7549A2C940108002A2623 /* MessageViewModel+DeletionActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageViewModel+DeletionActions.swift"; sourceTree = ""; }; @@ -3456,6 +3460,7 @@ C360969125AD1765008B62B2 /* Settings */ = { isa = PBXGroup; children = ( + FDE71B092E7934DC0023F5F9 /* DeveloperSettings */, FD8A5B002DBEFBF9004C689B /* SessionNetworkScreen */, FD37E9CD28A1E682003AE748 /* Views */, 9422569A2C23F8F000C0FDBF /* QRCodeScreen.swift */, @@ -3471,8 +3476,6 @@ FD860CB72D66BC9500BBE29C /* AppIconViewModel.swift */, FD37EA0228A9FDCC003AE748 /* HelpViewModel.swift */, B86BD08523399CEF000F5AE3 /* SeedModal.swift */, - FDC1BD672CFE6EEA002CDC71 /* DeveloperSettingsViewModel.swift */, - FD860CBD2D6E7DA000BBE29C /* DeveloperSettingsViewModel+Testing.swift */, B894D0742339EDCF00B4D94D /* NukeDataModal.swift */, FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */, ); @@ -5059,6 +5062,17 @@ path = JobRunner; sourceTree = ""; }; + FDE71B092E7934DC0023F5F9 /* DeveloperSettings */ = { + isa = PBXGroup; + children = ( + FDC1BD672CFE6EEA002CDC71 /* DeveloperSettingsViewModel.swift */, + FD860CBD2D6E7DA000BBE29C /* DeveloperSettingsViewModel+Testing.swift */, + FDE71B0A2E7935250023F5F9 /* DeveloperSettingsGroupsViewModel.swift */, + FDE71B0C2E793B1F0023F5F9 /* DeveloperSettingsProViewModel.swift */, + ); + path = DeveloperSettings; + sourceTree = ""; + }; FDE7214E287E50D50093DF33 /* Scripts */ = { isa = PBXGroup; children = ( @@ -6922,6 +6936,7 @@ FDC498B92AC15FE300EDD897 /* AppNotificationAction.swift in Sources */, FD7443402D07A25C00862443 /* PushRegistrationManager.swift in Sources */, 7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */, + FDE71B0B2E79352D0023F5F9 /* DeveloperSettingsGroupsViewModel.swift in Sources */, 7B71A98F2925E2A600E54854 /* SessionFooterView.swift in Sources */, C374EEEB25DA3CA70073A857 /* ConversationTitleView.swift in Sources */, FDE754E52C9BB012002A2623 /* BezierPathView.swift in Sources */, @@ -6989,6 +7004,7 @@ 45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */, 7B81FB5A2AB01B17002FB267 /* LoadingIndicatorView.swift in Sources */, 7B9F71D42852EEE2006DFE7B /* Emoji+Name.swift in Sources */, + FDE71B0D2E793B250023F5F9 /* DeveloperSettingsProViewModel.swift in Sources */, 4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */, C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */, 9479981C2DD44ADC008F5CD5 /* ThreadNotificationSettingsViewModel.swift in Sources */, diff --git a/Session/Settings/DeveloperSettings/DeveloperSettingsGroupsViewModel.swift b/Session/Settings/DeveloperSettings/DeveloperSettingsGroupsViewModel.swift new file mode 100644 index 0000000000..61d62d1034 --- /dev/null +++ b/Session/Settings/DeveloperSettings/DeveloperSettingsGroupsViewModel.swift @@ -0,0 +1,391 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import Combine +import GRDB +import DifferenceKit +import SessionUIKit +import SessionNetworkingKit +import SessionMessagingKit +import SessionUtilitiesKit + +class DeveloperSettingsGroupsViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource { + public let dependencies: Dependencies + public let navigatableState: NavigatableState = NavigatableState() + public let state: TableDataState = TableDataState() + public let observableState: ObservableTableSourceState = ObservableTableSourceState() + + /// This value is the current state of the view + @MainActor @Published private(set) var internalState: State + private var observationTask: Task? + + // MARK: - Initialization + + @MainActor init(using dependencies: Dependencies) { + self.dependencies = dependencies + self.internalState = State.initialState(using: dependencies) + + /// Bind the state + self.observationTask = ObservationBuilder + .initialValue(self.internalState) + .debounce(for: .never) + .using(dependencies: dependencies) + .query(DeveloperSettingsGroupsViewModel.queryState) + .assign { [weak self] updatedState in + guard let self = self else { return } + + // FIXME: To slightly reduce the size of the changes this new observation mechanism is currently wired into the old SessionTableViewController observation mechanism, we should refactor it so everything uses the new mechanism + let oldState: State = self.internalState + self.internalState = updatedState + self.pendingTableDataSubject.send(updatedState.sections(viewModel: self, previousState: oldState)) + } + } + + // MARK: - Config + + public enum Section: SessionTableSection { + case general + + var title: String? { + switch self { + case .general: return nil + } + } + + var style: SessionTableSectionStyle { + switch self { + case .general: return .padding + } + } + } + + public enum TableItem: Hashable, Differentiable, CaseIterable { + case updatedGroupsDisableAutoApprove + case updatedGroupsRemoveMessagesOnKick + case updatedGroupsAllowHistoricAccessOnInvite + case updatedGroupsAllowDisplayPicture + case updatedGroupsAllowDescriptionEditing + case updatedGroupsAllowPromotions + case updatedGroupsAllowInviteById + case updatedGroupsDeleteBeforeNow + case updatedGroupsDeleteAttachmentsBeforeNow + + // MARK: - Conformance + + public typealias DifferenceIdentifier = String + + public var differenceIdentifier: String { + switch self { + case .updatedGroupsDisableAutoApprove: return "updatedGroupsDisableAutoApprove" + case .updatedGroupsRemoveMessagesOnKick: return "updatedGroupsRemoveMessagesOnKick" + case .updatedGroupsAllowHistoricAccessOnInvite: return "updatedGroupsAllowHistoricAccessOnInvite" + case .updatedGroupsAllowDisplayPicture: return "updatedGroupsAllowDisplayPicture" + case .updatedGroupsAllowDescriptionEditing: return "updatedGroupsAllowDescriptionEditing" + case .updatedGroupsAllowPromotions: return "updatedGroupsAllowPromotions" + case .updatedGroupsAllowInviteById: return "updatedGroupsAllowInviteById" + case .updatedGroupsDeleteBeforeNow: return "updatedGroupsDeleteBeforeNow" + case .updatedGroupsDeleteAttachmentsBeforeNow: return "updatedGroupsDeleteAttachmentsBeforeNow" + } + } + + public func isContentEqual(to source: TableItem) -> Bool { + self.differenceIdentifier == source.differenceIdentifier + } + + public static var allCases: [TableItem] { + var result: [TableItem] = [] + switch TableItem.updatedGroupsDisableAutoApprove { + case .updatedGroupsDisableAutoApprove: result.append(.updatedGroupsDisableAutoApprove); fallthrough + case .updatedGroupsRemoveMessagesOnKick: result.append(.updatedGroupsRemoveMessagesOnKick); fallthrough + case .updatedGroupsAllowHistoricAccessOnInvite: + result.append(.updatedGroupsAllowHistoricAccessOnInvite); fallthrough + case .updatedGroupsAllowDisplayPicture: result.append(.updatedGroupsAllowDisplayPicture); fallthrough + case .updatedGroupsAllowDescriptionEditing: result.append(.updatedGroupsAllowDescriptionEditing); fallthrough + case .updatedGroupsAllowPromotions: result.append(.updatedGroupsAllowPromotions); fallthrough + case .updatedGroupsAllowInviteById: result.append(.updatedGroupsAllowInviteById); fallthrough + case .updatedGroupsDeleteBeforeNow: result.append(.updatedGroupsDeleteBeforeNow); fallthrough + case .updatedGroupsDeleteAttachmentsBeforeNow: result.append(.updatedGroupsDeleteAttachmentsBeforeNow) + } + + return result + } + } + + // MARK: - Content + + public struct State: Equatable, ObservableKeyProvider { + let updatedGroupsDisableAutoApprove: Bool + let updatedGroupsRemoveMessagesOnKick: Bool + let updatedGroupsAllowHistoricAccessOnInvite: Bool + let updatedGroupsAllowDisplayPicture: Bool + let updatedGroupsAllowDescriptionEditing: Bool + let updatedGroupsAllowPromotions: Bool + let updatedGroupsAllowInviteById: Bool + let updatedGroupsDeleteBeforeNow: Bool + let updatedGroupsDeleteAttachmentsBeforeNow: Bool + + @MainActor public func sections(viewModel: DeveloperSettingsGroupsViewModel, previousState: State) -> [SectionModel] { + DeveloperSettingsGroupsViewModel.sections( + state: self, + previousState: previousState, + viewModel: viewModel + ) + } + + public let observedKeys: Set = [ + .feature(.updatedGroupsDisableAutoApprove), + .feature(.updatedGroupsRemoveMessagesOnKick), + .feature(.updatedGroupsAllowHistoricAccessOnInvite), + .feature(.updatedGroupsAllowDisplayPicture), + .feature(.updatedGroupsAllowDescriptionEditing), + .feature(.updatedGroupsAllowPromotions), + .feature(.updatedGroupsAllowInviteById), + .feature(.updatedGroupsDeleteBeforeNow), + .feature(.updatedGroupsDeleteAttachmentsBeforeNow) + ] + + static func initialState(using dependencies: Dependencies) -> State { + return State( + updatedGroupsDisableAutoApprove: dependencies[feature: .updatedGroupsDisableAutoApprove], + updatedGroupsRemoveMessagesOnKick: dependencies[feature: .updatedGroupsRemoveMessagesOnKick], + updatedGroupsAllowHistoricAccessOnInvite: dependencies[feature: .updatedGroupsAllowHistoricAccessOnInvite], + updatedGroupsAllowDisplayPicture: dependencies[feature: .updatedGroupsAllowDisplayPicture], + updatedGroupsAllowDescriptionEditing: dependencies[feature: .updatedGroupsAllowDescriptionEditing], + updatedGroupsAllowPromotions: dependencies[feature: .updatedGroupsAllowPromotions], + updatedGroupsAllowInviteById: dependencies[feature: .updatedGroupsAllowInviteById], + updatedGroupsDeleteBeforeNow: dependencies[feature: .updatedGroupsDeleteBeforeNow], + updatedGroupsDeleteAttachmentsBeforeNow: dependencies[feature: .updatedGroupsDeleteAttachmentsBeforeNow] + ) + } + } + + let title: String = "Developer Group Settings" + + @Sendable private static func queryState( + previousState: State, + events: [ObservedEvent], + isInitialQuery: Bool, + using dependencies: Dependencies + ) async -> State { + return State( + updatedGroupsDisableAutoApprove: dependencies[feature: .updatedGroupsDisableAutoApprove], + updatedGroupsRemoveMessagesOnKick: dependencies[feature: .updatedGroupsRemoveMessagesOnKick], + updatedGroupsAllowHistoricAccessOnInvite: dependencies[feature: .updatedGroupsAllowHistoricAccessOnInvite], + updatedGroupsAllowDisplayPicture: dependencies[feature: .updatedGroupsAllowDisplayPicture], + updatedGroupsAllowDescriptionEditing: dependencies[feature: .updatedGroupsAllowDescriptionEditing], + updatedGroupsAllowPromotions: dependencies[feature: .updatedGroupsAllowPromotions], + updatedGroupsAllowInviteById: dependencies[feature: .updatedGroupsAllowInviteById], + updatedGroupsDeleteBeforeNow: dependencies[feature: .updatedGroupsDeleteBeforeNow], + updatedGroupsDeleteAttachmentsBeforeNow: dependencies[feature: .updatedGroupsDeleteAttachmentsBeforeNow] + ) + } + + private static func sections( + state: State, + previousState: State, + viewModel: DeveloperSettingsGroupsViewModel + ) -> [SectionModel] { + let general: SectionModel = SectionModel( + model: .general, + elements: [ + SessionCell.Info( + id: .updatedGroupsDisableAutoApprove, + title: "Disable Auto Approve", + subtitle: """ + Prevents a group from automatically getting approved if the admin is already approved. + + Note: The default behaviour is to automatically approve new groups if the admin that sent the invitation is an approved contact. + """, + trailingAccessory: .toggle( + state.updatedGroupsDisableAutoApprove, + oldValue: previousState.updatedGroupsDisableAutoApprove + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsDisableAutoApprove, + to: !state.updatedGroupsDisableAutoApprove + ) + } + ), + SessionCell.Info( + id: .updatedGroupsRemoveMessagesOnKick, + title: "Remove Messages on Kick", + subtitle: """ + Controls whether a group members messages should be removed when they are kicked from an updated group. + + Note: In a future release we will offer this as an option when removing members but for the initial release it can be controlled via this flag for testing purposes. + """, + trailingAccessory: .toggle( + state.updatedGroupsRemoveMessagesOnKick, + oldValue: previousState.updatedGroupsRemoveMessagesOnKick + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsRemoveMessagesOnKick, + to: !state.updatedGroupsRemoveMessagesOnKick + ) + } + ), + SessionCell.Info( + id: .updatedGroupsAllowHistoricAccessOnInvite, + title: "Allow Historic Message Access", + subtitle: """ + Controls whether members should be granted access to historic messages when invited to an updated group. + + Note: In a future release we will offer this as an option when inviting members but for the initial release it can be controlled via this flag for testing purposes. + """, + trailingAccessory: .toggle( + state.updatedGroupsAllowHistoricAccessOnInvite, + oldValue: previousState.updatedGroupsAllowHistoricAccessOnInvite + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsAllowHistoricAccessOnInvite, + to: !state.updatedGroupsAllowHistoricAccessOnInvite + ) + } + ), + SessionCell.Info( + id: .updatedGroupsAllowDisplayPicture, + title: "Custom Display Pictures", + subtitle: """ + Controls whether the UI allows group admins to set a custom display picture for a group. + + Note: In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes. + """, + trailingAccessory: .toggle( + state.updatedGroupsAllowDisplayPicture, + oldValue: previousState.updatedGroupsAllowDisplayPicture + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsAllowDisplayPicture, + to: !state.updatedGroupsAllowDisplayPicture + ) + } + ), + SessionCell.Info( + id: .updatedGroupsAllowDescriptionEditing, + title: "Edit Group Descriptions", + subtitle: """ + Controls whether the UI allows group admins to modify the descriptions of updated groups. + + Note: In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes. + """, + trailingAccessory: .toggle( + state.updatedGroupsAllowDescriptionEditing, + oldValue: previousState.updatedGroupsAllowDescriptionEditing + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsAllowDescriptionEditing, + to: !state.updatedGroupsAllowDescriptionEditing + ) + } + ), + SessionCell.Info( + id: .updatedGroupsAllowPromotions, + title: "Allow Group Promotions", + subtitle: """ + Controls whether the UI allows group admins to promote other group members to admin within an updated group. + + Note: In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes. + """, + trailingAccessory: .toggle( + state.updatedGroupsAllowPromotions, + oldValue: previousState.updatedGroupsAllowPromotions + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsAllowPromotions, + to: !state.updatedGroupsAllowPromotions + ) + } + ), + SessionCell.Info( + id: .updatedGroupsAllowInviteById, + title: "Allow Invite by ID", + subtitle: """ + Controls whether the UI allows group admins to invite other group members directly by their Account ID. + + Note: In a future release we will offer this functionality but it's not included in the initial release. + """, + trailingAccessory: .toggle( + state.updatedGroupsAllowInviteById, + oldValue: previousState.updatedGroupsAllowInviteById + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsAllowInviteById, + to: !state.updatedGroupsAllowInviteById + ) + } + ), + SessionCell.Info( + id: .updatedGroupsDeleteBeforeNow, + title: "Show button to delete messages before now", + subtitle: """ + Controls whether the UI allows group admins to delete all messages in the group that were sent before the button was pressed. + + Note: In a future release we will offer this functionality but it's not included in the initial release. + """, + trailingAccessory: .toggle( + state.updatedGroupsDeleteBeforeNow, + oldValue: previousState.updatedGroupsDeleteBeforeNow + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsDeleteBeforeNow, + to: !state.updatedGroupsDeleteBeforeNow + ) + } + ), + SessionCell.Info( + id: .updatedGroupsDeleteAttachmentsBeforeNow, + title: "Show button to delete attachments before now", + subtitle: """ + Controls whether the UI allows group admins to delete all attachments (and their associated messages) in the group that were sent before the button was pressed. + + Note: In a future release we will offer this functionality but it's not included in the initial release. + """, + trailingAccessory: .toggle( + state.updatedGroupsDeleteAttachmentsBeforeNow, + oldValue: previousState.updatedGroupsDeleteAttachmentsBeforeNow + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsDeleteAttachmentsBeforeNow, + to: !state.updatedGroupsDeleteAttachmentsBeforeNow + ) + } + ) + ] + ) + + return [general] + } + + // MARK: - Functions + + public static func disableDeveloperMode(using dependencies: Dependencies) { + let features: [FeatureConfig] = [ + .updatedGroupsDisableAutoApprove, + .updatedGroupsRemoveMessagesOnKick, + .updatedGroupsAllowHistoricAccessOnInvite, + .updatedGroupsAllowDisplayPicture, + .updatedGroupsAllowDescriptionEditing, + .updatedGroupsAllowPromotions, + .updatedGroupsAllowInviteById, + .updatedGroupsDeleteBeforeNow, + .updatedGroupsDeleteAttachmentsBeforeNow + ] + + features.forEach { feature in + guard dependencies.hasSet(feature: feature) else { return } + + dependencies.set(feature: feature, to: nil) + } + } +} diff --git a/Session/Settings/DeveloperSettings/DeveloperSettingsProViewModel.swift b/Session/Settings/DeveloperSettings/DeveloperSettingsProViewModel.swift new file mode 100644 index 0000000000..0eb37764ab --- /dev/null +++ b/Session/Settings/DeveloperSettings/DeveloperSettingsProViewModel.swift @@ -0,0 +1,439 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import StoreKit +import Combine +import GRDB +import DifferenceKit +import SessionUIKit +import SessionNetworkingKit +import SessionMessagingKit +import SessionUtilitiesKit + +class DeveloperSettingsProViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource { + public let dependencies: Dependencies + public let navigatableState: NavigatableState = NavigatableState() + public let state: TableDataState = TableDataState() + public let observableState: ObservableTableSourceState = ObservableTableSourceState() + + /// This value is the current state of the view + @MainActor @Published private(set) var internalState: State + private var observationTask: Task? + + // MARK: - Initialization + + @MainActor init(using dependencies: Dependencies) { + self.dependencies = dependencies + self.internalState = State.initialState(using: dependencies) + + /// Bind the state + self.observationTask = ObservationBuilder + .initialValue(self.internalState) + .debounce(for: .never) + .using(dependencies: dependencies) + .query(DeveloperSettingsProViewModel.queryState) + .assign { [weak self] updatedState in + guard let self = self else { return } + + // FIXME: To slightly reduce the size of the changes this new observation mechanism is currently wired into the old SessionTableViewController observation mechanism, we should refactor it so everything uses the new mechanism + let oldState: State = self.internalState + self.internalState = updatedState + self.pendingTableDataSubject.send(updatedState.sections(viewModel: self, previousState: oldState)) + } + } + + // MARK: - Config + + public enum Section: SessionTableSection { + case general + case subscriptions + case features + + var title: String? { + switch self { + case .general: return nil + case .subscriptions: return "Subscriptions" + case .features: return "Features" + } + } + + var style: SessionTableSectionStyle { + switch self { + case .general: return .padding + default: return .titleRoundedContent + } + } + } + + public enum TableItem: Hashable, Differentiable, CaseIterable { + case enableSessionPro + + case purchaseProSubscription + case manageProSubscriptions + case restoreProSubscription + + case proStatus + case proIncomingMessages + + // MARK: - Conformance + + public typealias DifferenceIdentifier = String + + public var differenceIdentifier: String { + switch self { + case .enableSessionPro: return "enableSessionPro" + + case .purchaseProSubscription: return "purchaseProSubscription" + case .manageProSubscriptions: return "manageProSubscriptions" + case .restoreProSubscription: return "restoreProSubscription" + + case .proStatus: return "proStatus" + case .proIncomingMessages: return "proIncomingMessages" + } + } + + public func isContentEqual(to source: TableItem) -> Bool { + self.differenceIdentifier == source.differenceIdentifier + } + + public static var allCases: [TableItem] { + var result: [TableItem] = [] + switch TableItem.enableSessionPro { + case .enableSessionPro: result.append(.enableSessionPro); fallthrough + + case .purchaseProSubscription: result.append(.purchaseProSubscription); fallthrough + case .manageProSubscriptions: result.append(.manageProSubscriptions); fallthrough + case .restoreProSubscription: result.append(.restoreProSubscription); fallthrough + + case .proStatus: result.append(.proStatus); fallthrough + case .proIncomingMessages: result.append(.proIncomingMessages) + } + + return result + } + } + + public enum DeveloperSettingsProEvent: Hashable { + case purchasedProduct([Product], Product?, String?, String?, UInt64?) + } + + // MARK: - Content + + public struct State: Equatable, ObservableKeyProvider { + let sessionProEnabled: Bool + + let products: [Product] + let purchasedProduct: Product? + let purchaseError: String? + let purchaseStatus: String? + let purchaseTransactionId: String? + + let mockCurrentUserSessionPro: Bool + let treatAllIncomingMessagesAsProMessages: Bool + + @MainActor public func sections(viewModel: DeveloperSettingsProViewModel, previousState: State) -> [SectionModel] { + DeveloperSettingsProViewModel.sections( + state: self, + previousState: previousState, + viewModel: viewModel + ) + } + + public let observedKeys: Set = [ + .feature(.sessionProEnabled), + .updateScreen(DeveloperSettingsProViewModel.self), + .feature(.mockCurrentUserSessionPro), + .feature(.treatAllIncomingMessagesAsProMessages) + ] + + static func initialState(using dependencies: Dependencies) -> State { + return State( + sessionProEnabled: dependencies[feature: .sessionProEnabled], + + products: [], + purchasedProduct: nil, + purchaseError: nil, + purchaseStatus: nil, + purchaseTransactionId: nil, + + mockCurrentUserSessionPro: dependencies[feature: .mockCurrentUserSessionPro], + treatAllIncomingMessagesAsProMessages: dependencies[feature: .treatAllIncomingMessagesAsProMessages] + ) + } + } + + let title: String = "Developer Pro Settings" + + @Sendable private static func queryState( + previousState: State, + events: [ObservedEvent], + isInitialQuery: Bool, + using dependencies: Dependencies + ) async -> State { + var products: [Product] = previousState.products + var purchasedProduct: Product? = previousState.purchasedProduct + var purchaseError: String? = previousState.purchaseError + var purchaseStatus: String? = previousState.purchaseStatus + var purchaseTransactionId: String? = previousState.purchaseTransactionId + + events.forEach { event in + guard let eventValue: DeveloperSettingsProEvent = event.value as? DeveloperSettingsProEvent else { return } + + switch eventValue { + case .purchasedProduct(let receivedProducts, let purchased, let error, let status, let id): + products = receivedProducts + purchasedProduct = purchased + purchaseError = error + purchaseStatus = status + purchaseTransactionId = id.map { "\($0)" } + } + } + + return State( + sessionProEnabled: dependencies[feature: .sessionProEnabled], + products: products, + purchasedProduct: purchasedProduct, + purchaseError: purchaseError, + purchaseStatus: purchaseStatus, + purchaseTransactionId: purchaseTransactionId, + mockCurrentUserSessionPro: dependencies[feature: .mockCurrentUserSessionPro], + treatAllIncomingMessagesAsProMessages: dependencies[feature: .treatAllIncomingMessagesAsProMessages] + ) + } + + private static func sections( + state: State, + previousState: State, + viewModel: DeveloperSettingsProViewModel + ) -> [SectionModel] { + let general: SectionModel = SectionModel( + model: .general, + elements: [ + SessionCell.Info( + id: .enableSessionPro, + title: "Enable Session Pro", + subtitle: """ + Enable Post Pro Release mode. + Turning on this Settings will show Pro badge and CTA if needed. + """, + trailingAccessory: .toggle( + state.sessionProEnabled, + oldValue: previousState.sessionProEnabled + ), + onTap: { [weak viewModel] in + viewModel?.updateSessionProEnabled(current: state.sessionProEnabled) + } + ) + ] + ) + + guard state.sessionProEnabled else { return [general] } + + let purchaseStatus: String = { + switch (state.purchaseError, state.purchaseStatus) { + case (.some(let error), _): return "\(error)" + case (_, .some(let status)): return "\(status)" + case (.none, .none): return "None" + } + }() + let productName: String = ( + state.purchasedProduct.map { "\($0.displayName)" } ?? + "N/A" + ) + let transactionId: String = ( + state.purchaseTransactionId.map { "\($0)" } ?? + "N/A" + ) + let subscriptions: SectionModel = SectionModel( + model: .subscriptions, + elements: [ + SessionCell.Info( + id: .purchaseProSubscription, + title: "Purchase Subscription", + subtitle: """ + Purchase Session Pro via the App Store. + + Status: \(purchaseStatus) + Product Name: \(productName) + TransactionId: \(transactionId) + """, + trailingAccessory: .highlightingBackgroundLabel(title: "Purchase"), + onTap: { [weak viewModel] in + Task { await viewModel?.purchaseSubscription() } + } + ), + SessionCell.Info( + id: .manageProSubscriptions, + title: "Manage Subscriptions", + subtitle: """ + Manage subscriptions for Session Pro via the App Store. + + Note: You must purchase a Session Pro subscription before you can manage it. + """, + trailingAccessory: .highlightingBackgroundLabel(title: "Manage"), + onTap: { [weak viewModel] in + Task { await viewModel?.manageSubscriptions() } + } + ), + SessionCell.Info( + id: .restoreProSubscription, + title: "Restore Subscriptions", + subtitle: """ + Restore a Session Pro subscription via the App Store. + """, + trailingAccessory: .highlightingBackgroundLabel(title: "Restore"), + onTap: { [weak viewModel] in + Task { await viewModel?.restoreSubscriptions() } + } + ) + ] + ) + + let features: SectionModel = SectionModel( + model: .features, + elements: [ + SessionCell.Info( + id: .proStatus, + title: "Pro Status", + subtitle: """ + Mock current user a Session Pro user locally. + """, + trailingAccessory: .toggle( + state.mockCurrentUserSessionPro, + oldValue: previousState.mockCurrentUserSessionPro + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .mockCurrentUserSessionPro, + to: !state.mockCurrentUserSessionPro + ) + } + ), + SessionCell.Info( + id: .proIncomingMessages, + title: "All Pro Incoming Messages", + subtitle: """ + Treat all incoming messages as Pro messages. + """, + trailingAccessory: .toggle( + state.treatAllIncomingMessagesAsProMessages, + oldValue: previousState.treatAllIncomingMessagesAsProMessages + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .treatAllIncomingMessagesAsProMessages, + to: !state.treatAllIncomingMessagesAsProMessages + ) + } + ) + ] + ) + + return [general, subscriptions, features] + } + + // MARK: - Functions + + public static func disableDeveloperMode(using dependencies: Dependencies) { + let features: [FeatureConfig] = [ + .sessionProEnabled, + .mockCurrentUserSessionPro, + .treatAllIncomingMessagesAsProMessages + ] + + features.forEach { feature in + guard dependencies.hasSet(feature: feature) else { return } + + dependencies.set(feature: feature, to: nil) + } + } + + private func updateSessionProEnabled(current: Bool) { + dependencies.set(feature: .sessionProEnabled, to: !current) + + if dependencies.hasSet(feature: .mockCurrentUserSessionPro) { + dependencies.set(feature: .mockCurrentUserSessionPro, to: nil) + } + + if dependencies.hasSet(feature: .treatAllIncomingMessagesAsProMessages) { + dependencies.set(feature: .treatAllIncomingMessagesAsProMessages, to: nil) + } + } + + private func purchaseSubscription() async { + do { + let products: [Product] = try await Product.products(for: ["com.getsession.org.pro_sub"]) + + guard let product: Product = products.first else { + Log.error("[DevSettings] Unable to purchase subscription due to error: No products found") + dependencies.notifyAsync( + key: .updateScreen(DeveloperSettingsProViewModel.self), + value: DeveloperSettingsProEvent.purchasedProduct([], nil, "No products found", nil, nil) + ) + return + } + + let result = try await product.purchase() + switch result { + case .success(let verificationResult): + let transaction = try verificationResult.payloadValue + dependencies.notifyAsync( + key: .updateScreen(DeveloperSettingsProViewModel.self), + value: DeveloperSettingsProEvent.purchasedProduct(products, product, nil, "Successful", transaction.id) + ) + await transaction.finish() + + case .pending: + dependencies.notifyAsync( + key: .updateScreen(DeveloperSettingsProViewModel.self), + value: DeveloperSettingsProEvent.purchasedProduct(products, product, nil, "Pending approval", nil) + ) + + case .userCancelled: + dependencies.notifyAsync( + key: .updateScreen(DeveloperSettingsProViewModel.self), + value: DeveloperSettingsProEvent.purchasedProduct(products, product, nil, "User cancelled", nil) + ) + + @unknown default: + dependencies.notifyAsync( + key: .updateScreen(DeveloperSettingsProViewModel.self), + value: DeveloperSettingsProEvent.purchasedProduct(products, product, "Unknown Error", nil, nil) + ) + } + + } + catch { + Log.error("[DevSettings] Unable to purchase subscription due to error: \(error)") + dependencies.notifyAsync( + key: .updateScreen(DeveloperSettingsProViewModel.self), + value: DeveloperSettingsProEvent.purchasedProduct([], nil, "Failed: \(error)", nil, nil) + ) + } + } + + private func manageSubscriptions() async { + guard let scene: UIWindowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { + return Log.error("[DevSettings] Unable to show manage subscriptions: Unable to get UIWindowScene") + } + + do { + try await AppStore.showManageSubscriptions(in: scene) + print("AS") + } + catch { + Log.error("[DevSettings] Unable to show manage subscriptions: \(error)") + } + } + + private func restoreSubscriptions() async { + do { + try await AppStore.sync() + } + catch { + Log.error("[DevSettings] Unable to show manage subscriptions: \(error)") + } + } +} diff --git a/Session/Settings/DeveloperSettingsViewModel+Testing.swift b/Session/Settings/DeveloperSettings/DeveloperSettingsViewModel+Testing.swift similarity index 100% rename from Session/Settings/DeveloperSettingsViewModel+Testing.swift rename to Session/Settings/DeveloperSettings/DeveloperSettingsViewModel+Testing.swift diff --git a/Session/Settings/DeveloperSettingsViewModel.swift b/Session/Settings/DeveloperSettings/DeveloperSettingsViewModel.swift similarity index 79% rename from Session/Settings/DeveloperSettingsViewModel.swift rename to Session/Settings/DeveloperSettings/DeveloperSettingsViewModel.swift index 78ea7b150c..05b772057e 100644 --- a/Session/Settings/DeveloperSettingsViewModel.swift +++ b/Session/Settings/DeveloperSettings/DeveloperSettingsViewModel.swift @@ -71,13 +71,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, public enum TableItem: Hashable, Differentiable, CaseIterable { case developerMode - case enableSessionPro - case proStatus - case proIncomingMessages - - case versionBlindedID - case scheduleLocalNotification - case animationsEnabled case showStringKeys case truncatePubkeysInLogs @@ -99,15 +92,11 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, case communityPollLimit - case updatedGroupsDisableAutoApprove - case updatedGroupsRemoveMessagesOnKick - case updatedGroupsAllowHistoricAccessOnInvite - case updatedGroupsAllowDisplayPicture - case updatedGroupsAllowDescriptionEditing - case updatedGroupsAllowPromotions - case updatedGroupsAllowInviteById - case updatedGroupsDeleteBeforeNow - case updatedGroupsDeleteAttachmentsBeforeNow + case groupConfig + case proConfig + + case versionBlindedID + case scheduleLocalNotification case createMockContacts case forceSlowDatabaseQueries @@ -142,22 +131,11 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, case .communityPollLimit: return "communityPollLimit" - case .updatedGroupsDisableAutoApprove: return "updatedGroupsDisableAutoApprove" - case .updatedGroupsRemoveMessagesOnKick: return "updatedGroupsRemoveMessagesOnKick" - case .updatedGroupsAllowHistoricAccessOnInvite: return "updatedGroupsAllowHistoricAccessOnInvite" - case .updatedGroupsAllowDisplayPicture: return "updatedGroupsAllowDisplayPicture" - case .updatedGroupsAllowDescriptionEditing: return "updatedGroupsAllowDescriptionEditing" - case .updatedGroupsAllowPromotions: return "updatedGroupsAllowPromotions" - case .updatedGroupsAllowInviteById: return "updatedGroupsAllowInviteById" - case .updatedGroupsDeleteBeforeNow: return "updatedGroupsDeleteBeforeNow" - case .updatedGroupsDeleteAttachmentsBeforeNow: return "updatedGroupsDeleteAttachmentsBeforeNow" + case .groupConfig: return "groupConfig" + case .proConfig: return "proConfig" case .versionBlindedID: return "versionBlindedID" case .scheduleLocalNotification: return "scheduleLocalNotification" - - case .enableSessionPro: return "enableSessionPro" - case .proStatus: return "proStatus" - case .proIncomingMessages: return "proIncomingMessages" case .createMockContacts: return "createMockContacts" case .forceSlowDatabaseQueries: return "forceSlowDatabaseQueries" @@ -195,24 +173,12 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, case .communityPollLimit: result.append(.communityPollLimit); fallthrough - case .updatedGroupsDisableAutoApprove: result.append(.updatedGroupsDisableAutoApprove); fallthrough - case .updatedGroupsRemoveMessagesOnKick: result.append(.updatedGroupsRemoveMessagesOnKick); fallthrough - case .updatedGroupsAllowHistoricAccessOnInvite: - result.append(.updatedGroupsAllowHistoricAccessOnInvite); fallthrough - case .updatedGroupsAllowDisplayPicture: result.append(.updatedGroupsAllowDisplayPicture); fallthrough - case .updatedGroupsAllowDescriptionEditing: result.append(.updatedGroupsAllowDescriptionEditing); fallthrough - case .updatedGroupsAllowPromotions: result.append(.updatedGroupsAllowPromotions); fallthrough - case .updatedGroupsAllowInviteById: result.append(.updatedGroupsAllowInviteById); fallthrough - case .updatedGroupsDeleteBeforeNow: result.append(.updatedGroupsDeleteBeforeNow); fallthrough - case .updatedGroupsDeleteAttachmentsBeforeNow: result.append(.updatedGroupsDeleteAttachmentsBeforeNow); fallthrough + case .groupConfig: result.append(.groupConfig); fallthrough + case .proConfig: result.append(.proConfig); fallthrough case .versionBlindedID: result.append(.versionBlindedID); fallthrough case .scheduleLocalNotification: result.append(.scheduleLocalNotification); fallthrough - case .enableSessionPro: result.append(.enableSessionPro); fallthrough - case .proStatus: result.append(.proStatus); fallthrough - case .proIncomingMessages: result.append(.proIncomingMessages); fallthrough - case .createMockContacts: result.append(.createMockContacts); fallthrough case .forceSlowDatabaseQueries: result.append(.forceSlowDatabaseQueries); fallthrough case .exportDatabase: result.append(.exportDatabase); fallthrough @@ -245,20 +211,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, let communityPollLimit: Int - let updatedGroupsDisableAutoApprove: Bool - let updatedGroupsRemoveMessagesOnKick: Bool - let updatedGroupsAllowHistoricAccessOnInvite: Bool - let updatedGroupsAllowDisplayPicture: Bool - let updatedGroupsAllowDescriptionEditing: Bool - let updatedGroupsAllowPromotions: Bool - let updatedGroupsAllowInviteById: Bool - let updatedGroupsDeleteBeforeNow: Bool - let updatedGroupsDeleteAttachmentsBeforeNow: Bool - - let sessionProEnabled: Bool - let mockCurrentUserSessionPro: Bool - let treatAllIncomingMessagesAsProMessages: Bool - let forceSlowDatabaseQueries: Bool let updateSimulateAppReviewLimit: Bool @@ -302,20 +254,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, communityPollLimit: dependencies[feature: .communityPollLimit], - updatedGroupsDisableAutoApprove: dependencies[feature: .updatedGroupsDisableAutoApprove], - updatedGroupsRemoveMessagesOnKick: dependencies[feature: .updatedGroupsRemoveMessagesOnKick], - updatedGroupsAllowHistoricAccessOnInvite: dependencies[feature: .updatedGroupsAllowHistoricAccessOnInvite], - updatedGroupsAllowDisplayPicture: dependencies[feature: .updatedGroupsAllowDisplayPicture], - updatedGroupsAllowDescriptionEditing: dependencies[feature: .updatedGroupsAllowDescriptionEditing], - updatedGroupsAllowPromotions: dependencies[feature: .updatedGroupsAllowPromotions], - updatedGroupsAllowInviteById: dependencies[feature: .updatedGroupsAllowInviteById], - updatedGroupsDeleteBeforeNow: dependencies[feature: .updatedGroupsDeleteBeforeNow], - updatedGroupsDeleteAttachmentsBeforeNow: dependencies[feature: .updatedGroupsDeleteAttachmentsBeforeNow], - - sessionProEnabled: dependencies[feature: .sessionProEnabled], - mockCurrentUserSessionPro: dependencies[feature: .mockCurrentUserSessionPro], - treatAllIncomingMessagesAsProMessages: dependencies[feature: .treatAllIncomingMessagesAsProMessages], - forceSlowDatabaseQueries: dependencies[feature: .forceSlowDatabaseQueries], updateSimulateAppReviewLimit: dependencies[feature: .simulateAppReviewLimit] ) @@ -663,173 +601,39 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, model: .groups, elements: [ SessionCell.Info( - id: .updatedGroupsDisableAutoApprove, - title: "Disable Auto Approve", - subtitle: """ - Prevents a group from automatically getting approved if the admin is already approved. - - Note: The default behaviour is to automatically approve new groups if the admin that sent the invitation is an approved contact. - """, - trailingAccessory: .toggle( - current.updatedGroupsDisableAutoApprove, - oldValue: previous?.updatedGroupsDisableAutoApprove - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsDisableAutoApprove, - to: !current.updatedGroupsDisableAutoApprove - ) - } - ), - SessionCell.Info( - id: .updatedGroupsRemoveMessagesOnKick, - title: "Remove Messages on Kick", - subtitle: """ - Controls whether a group members messages should be removed when they are kicked from an updated group. - - Note: In a future release we will offer this as an option when removing members but for the initial release it can be controlled via this flag for testing purposes. - """, - trailingAccessory: .toggle( - current.updatedGroupsRemoveMessagesOnKick, - oldValue: previous?.updatedGroupsRemoveMessagesOnKick - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsRemoveMessagesOnKick, - to: !current.updatedGroupsRemoveMessagesOnKick - ) - } - ), - SessionCell.Info( - id: .updatedGroupsAllowHistoricAccessOnInvite, - title: "Allow Historic Message Access", - subtitle: """ - Controls whether members should be granted access to historic messages when invited to an updated group. - - Note: In a future release we will offer this as an option when inviting members but for the initial release it can be controlled via this flag for testing purposes. - """, - trailingAccessory: .toggle( - current.updatedGroupsAllowHistoricAccessOnInvite, - oldValue: previous?.updatedGroupsAllowHistoricAccessOnInvite - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsAllowHistoricAccessOnInvite, - to: !current.updatedGroupsAllowHistoricAccessOnInvite - ) - } - ), - SessionCell.Info( - id: .updatedGroupsAllowDisplayPicture, - title: "Custom Display Pictures", - subtitle: """ - Controls whether the UI allows group admins to set a custom display picture for a group. - - Note: In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes. - """, - trailingAccessory: .toggle( - current.updatedGroupsAllowDisplayPicture, - oldValue: previous?.updatedGroupsAllowDisplayPicture - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsAllowDisplayPicture, - to: !current.updatedGroupsAllowDisplayPicture - ) - } - ), - SessionCell.Info( - id: .updatedGroupsAllowDescriptionEditing, - title: "Edit Group Descriptions", - subtitle: """ - Controls whether the UI allows group admins to modify the descriptions of updated groups. - - Note: In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes. - """, - trailingAccessory: .toggle( - current.updatedGroupsAllowDescriptionEditing, - oldValue: previous?.updatedGroupsAllowDescriptionEditing - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsAllowDescriptionEditing, - to: !current.updatedGroupsAllowDescriptionEditing - ) - } - ), - SessionCell.Info( - id: .updatedGroupsAllowPromotions, - title: "Allow Group Promotions", - subtitle: """ - Controls whether the UI allows group admins to promote other group members to admin within an updated group. - - Note: In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes. - """, - trailingAccessory: .toggle( - current.updatedGroupsAllowPromotions, - oldValue: previous?.updatedGroupsAllowPromotions - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsAllowPromotions, - to: !current.updatedGroupsAllowPromotions - ) - } - ), - SessionCell.Info( - id: .updatedGroupsAllowInviteById, - title: "Allow Invite by ID", - subtitle: """ - Controls whether the UI allows group admins to invite other group members directly by their Account ID. - - Note: In a future release we will offer this functionality but it's not included in the initial release. - """, - trailingAccessory: .toggle( - current.updatedGroupsAllowInviteById, - oldValue: previous?.updatedGroupsAllowInviteById - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsAllowInviteById, - to: !current.updatedGroupsAllowInviteById - ) - } - ), - SessionCell.Info( - id: .updatedGroupsDeleteBeforeNow, - title: "Show button to delete messages before now", + id: .groupConfig, + title: "Group Configuration", subtitle: """ - Controls whether the UI allows group admins to delete all messages in the group that were sent before the button was pressed. - - Note: In a future release we will offer this functionality but it's not included in the initial release. + Configure settings related to Groups. """, - trailingAccessory: .toggle( - current.updatedGroupsDeleteBeforeNow, - oldValue: previous?.updatedGroupsDeleteBeforeNow - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsDeleteBeforeNow, - to: !current.updatedGroupsDeleteBeforeNow + trailingAccessory: .icon(.chevronRight), + onTap: { [weak self, dependencies] in + self?.transitionToScreen( + SessionTableViewController( + viewModel: DeveloperSettingsGroupsViewModel(using: dependencies) + ) ) } - ), + ) + ] + ) + let sessionPro: SectionModel = SectionModel( + model: .sessionPro, + elements: [ SessionCell.Info( - id: .updatedGroupsDeleteAttachmentsBeforeNow, - title: "Show button to delete attachments before now", + id: .proConfig, + title: "Session Pro", subtitle: """ - Controls whether the UI allows group admins to delete all attachments (and their associated messages) in the group that were sent before the button was pressed. + Configure settings related to Session Pro. - Note: In a future release we will offer this functionality but it's not included in the initial release. + Session Pro: \(dependencies[feature: .sessionProEnabled] ? "Enabled" : "Disabled") """, - trailingAccessory: .toggle( - current.updatedGroupsDeleteAttachmentsBeforeNow, - oldValue: previous?.updatedGroupsDeleteAttachmentsBeforeNow - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsDeleteAttachmentsBeforeNow, - to: !current.updatedGroupsDeleteAttachmentsBeforeNow + trailingAccessory: .icon(.chevronRight), + onTap: { [weak self, dependencies] in + self?.transitionToScreen( + SessionTableViewController( + viewModel: DeveloperSettingsProViewModel(using: dependencies) + ) ) } ) @@ -898,63 +702,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ) ] ) - let sessionPro: SectionModel = SectionModel( - model: .sessionPro, - elements: [ - SessionCell.Info( - id: .enableSessionPro, - title: "Enable Session Pro", - subtitle: """ - Enable Post Pro Release mode. - Turning on this Settings will show Pro badge and CTA if needed. - """, - trailingAccessory: .toggle( - current.sessionProEnabled, - oldValue: previous?.sessionProEnabled - ), - onTap: { [weak self] in - self?.updateSessionProEnabled(current: current.sessionProEnabled) - } - ) - ].appending( - contentsOf: current.sessionProEnabled ? [ - SessionCell.Info( - id: .proStatus, - title: "Pro Status", - subtitle: """ - Mock current user a Session Pro user locally. - """, - trailingAccessory: .toggle( - current.mockCurrentUserSessionPro, - oldValue: previous?.mockCurrentUserSessionPro - ), - onTap: { [weak self] in - self?.updateFlag( - for: .mockCurrentUserSessionPro, - to: !current.mockCurrentUserSessionPro - ) - } - ), - SessionCell.Info( - id: .proIncomingMessages, - title: "All Pro Incoming Messages", - subtitle: """ - Treat all incoming messages as Pro messages. - """, - trailingAccessory: .toggle( - current.treatAllIncomingMessagesAsProMessages, - oldValue: previous?.treatAllIncomingMessagesAsProMessages - ), - onTap: { [weak self] in - self?.updateFlag( - for: .treatAllIncomingMessagesAsProMessages, - to: !current.treatAllIncomingMessagesAsProMessages - ) - } - ) - ] : nil - ) - ) let sessionNetwork: SectionModel = SectionModel( model: .sessionNetwork, elements: [ @@ -1012,9 +759,10 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, /// then we will get a compile error if it doesn't get resetting instructions added) TableItem.allCases.forEach { item in switch item { - case .developerMode: break // Not a feature - case .versionBlindedID: break // Not a feature - case .scheduleLocalNotification: break // Not a feature + case .developerMode, .versionBlindedID, .scheduleLocalNotification, .copyDocumentsPath, + .copyAppGroupPath, .resetSnodeCache, .createMockContacts, .exportDatabase, + .importDatabase, .advancedLogging, .resetAppReviewPrompt: + break /// These are actions rather than values stored as "features" so no need to do anything case .animationsEnabled: guard dependencies.hasSet(feature: .animationsEnabled) else { return } @@ -1031,18 +779,10 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, updateFlag(for: .truncatePubkeysInLogs, to: nil) - case .copyDocumentsPath: break // Not a feature - case .copyAppGroupPath: break // Not a feature - case .resetAppReviewPrompt: break case .simulateAppReviewLimit: guard dependencies.hasSet(feature: .simulateAppReviewLimit) else { return } updateFlag(for: .simulateAppReviewLimit, to: nil) - case .resetSnodeCache: break // Not a feature - case .createMockContacts: break // Not a feature - case .exportDatabase: break // Not a feature - case .importDatabase: break // Not a feature - case .advancedLogging: break // Not a feature case .defaultLogLevel: updateDefaulLogLevel(to: nil) // Always reset case .loggingCategory: resetLoggingCategories() // Always reset @@ -1073,71 +813,8 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, dependencies.set(feature: .communityPollLimit, to: nil) forceRefresh(type: .databaseQuery) - case .updatedGroupsDisableAutoApprove: - guard dependencies.hasSet(feature: .updatedGroupsDisableAutoApprove) else { return } - - updateFlag(for: .updatedGroupsDisableAutoApprove, to: nil) - - case .updatedGroupsRemoveMessagesOnKick: - guard dependencies.hasSet(feature: .updatedGroupsRemoveMessagesOnKick) else { return } - - updateFlag(for: .updatedGroupsRemoveMessagesOnKick, to: nil) - - case .updatedGroupsAllowHistoricAccessOnInvite: - guard dependencies.hasSet(feature: .updatedGroupsAllowHistoricAccessOnInvite) else { - return - } - - updateFlag(for: .updatedGroupsAllowHistoricAccessOnInvite, to: nil) - - case .updatedGroupsAllowDisplayPicture: - guard dependencies.hasSet(feature: .updatedGroupsAllowDisplayPicture) else { return } - - updateFlag(for: .updatedGroupsAllowDisplayPicture, to: nil) - - case .updatedGroupsAllowDescriptionEditing: - guard dependencies.hasSet(feature: .updatedGroupsAllowDescriptionEditing) else { return } - - updateFlag(for: .updatedGroupsAllowDescriptionEditing, to: nil) - - case .updatedGroupsAllowPromotions: - guard dependencies.hasSet(feature: .updatedGroupsAllowPromotions) else { return } - - updateFlag(for: .updatedGroupsAllowPromotions, to: nil) - - case .updatedGroupsAllowInviteById: - guard dependencies.hasSet(feature: .updatedGroupsAllowInviteById) else { return } - - updateFlag(for: .updatedGroupsAllowInviteById, to: nil) - - case .updatedGroupsDeleteBeforeNow: - guard dependencies.hasSet(feature: .updatedGroupsDeleteBeforeNow) else { return } - - updateFlag(for: .updatedGroupsDeleteBeforeNow, to: nil) - - case .updatedGroupsDeleteAttachmentsBeforeNow: - guard dependencies.hasSet(feature: .updatedGroupsDeleteAttachmentsBeforeNow) else { - return - } - - updateFlag(for: .updatedGroupsDeleteAttachmentsBeforeNow, to: nil) - - case .enableSessionPro: - guard dependencies.hasSet(feature: .sessionProEnabled) else { return } - - updateFlag(for: .sessionProEnabled, to: nil) - - case .proStatus: - guard dependencies.hasSet(feature: .mockCurrentUserSessionPro) else { return } - - updateFlag(for: .mockCurrentUserSessionPro, to: nil) - - case .proIncomingMessages: - guard dependencies.hasSet(feature: .treatAllIncomingMessagesAsProMessages) else { - return - } - - updateFlag(for: .treatAllIncomingMessagesAsProMessages, to: nil) + case .groupConfig: DeveloperSettingsGroupsViewModel.disableDeveloperMode(using: dependencies) + case .proConfig: DeveloperSettingsProViewModel.disableDeveloperMode(using: dependencies) case .forceSlowDatabaseQueries: guard dependencies.hasSet(feature: .forceSlowDatabaseQueries) else { return } @@ -1347,16 +1024,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, forceRefresh(type: .databaseQuery) } - private func updateSessionProEnabled(current: Bool) { - updateFlag(for: .sessionProEnabled, to: !current) - if dependencies.hasSet(feature: .mockCurrentUserSessionPro) { - updateFlag(for: .mockCurrentUserSessionPro, to: nil) - } - if dependencies.hasSet(feature: .treatAllIncomingMessagesAsProMessages) { - updateFlag(for: .treatAllIncomingMessagesAsProMessages, to: nil) - } - } - private func updateForceOffline(current: Bool) { updateFlag(for: .forceOffline, to: !current) diff --git a/Session/Shared/Views/SessionCell.swift b/Session/Shared/Views/SessionCell.swift index 47bdebb394..8a0a352752 100644 --- a/Session/Shared/Views/SessionCell.swift +++ b/Session/Shared/Views/SessionCell.swift @@ -575,19 +575,19 @@ public class SessionCell: UITableViewCell { titleTextField.accessibilityLabel = info.title?.accessibility?.label subtitleLabel.isUserInteractionEnabled = (info.subtitle?.interaction == .copy) subtitleLabel.font = info.subtitle?.font + subtitleLabel.themeTextColor = info.styling.subtitleTintColor subtitleLabel.themeAttributedText = info.subtitle.map { subtitle -> ThemedAttributedString? in ThemedAttributedString(stringWithHTMLTags: subtitle.text, font: subtitle.font) } - subtitleLabel.themeTextColor = info.styling.subtitleTintColor subtitleLabel.textAlignment = (info.subtitle?.textAlignment ?? .left) subtitleLabel.accessibilityIdentifier = info.subtitle?.accessibility?.identifier subtitleLabel.accessibilityLabel = info.subtitle?.accessibility?.label subtitleLabel.isHidden = (info.subtitle == nil) expandableDescriptionLabel.font = info.description?.font ?? .systemFont(ofSize: 12) + expandableDescriptionLabel.themeTextColor = info.styling.descriptionTintColor expandableDescriptionLabel.themeAttributedText = info.description.map { description -> ThemedAttributedString? in ThemedAttributedString(stringWithHTMLTags: description.text, font: description.font) } - expandableDescriptionLabel.themeTextColor = info.styling.descriptionTintColor expandableDescriptionLabel.textAlignment = (info.description?.textAlignment ?? .left) expandableDescriptionLabel.accessibilityIdentifier = info.description?.accessibility?.identifier expandableDescriptionLabel.accessibilityLabel = info.description?.accessibility?.label diff --git a/SessionUIKit/Utilities/Localization+Style.swift b/SessionUIKit/Utilities/Localization+Style.swift index 788e777c10..43dce7671e 100644 --- a/SessionUIKit/Utilities/Localization+Style.swift +++ b/SessionUIKit/Utilities/Localization+Style.swift @@ -20,6 +20,9 @@ public extension ThemedAttributedString { case strikethrough = "s" case primaryTheme = "span" case icon = "icon" + case warningTheme = "warn" + case dangerTheme = "error" + case disabledTheme = "disabled" // MARK: - Functions @@ -53,6 +56,9 @@ public extension ThemedAttributedString { case .strikethrough: return [.strikethroughStyle: NSUnderlineStyle.single.rawValue] case .primaryTheme: return [.themeForegroundColor: ThemeValue.sessionButton_text] case .icon: return Lucide.attributes(for: font) + case .warningTheme: return [.themeForegroundColor: ThemeValue.warning] + case .dangerTheme: return [.themeForegroundColor: ThemeValue.danger] + case .disabledTheme: return [.themeForegroundColor: ThemeValue.disabled] } } } @@ -184,6 +190,9 @@ private extension Collection where Element == ThemedAttributedString.HTMLTag { case .icon: result[.font] = fontWith(Lucide.font(ofSize: (font.pointSize + 1)), traits: []) result[.baselineOffset] = Lucide.defaultBaselineOffset + case .warningTheme: result[.themeForegroundColor] = ThemeValue.warning + case .dangerTheme: result[.themeForegroundColor] = ThemeValue.danger + case .disabledTheme: result[.themeForegroundColor] = ThemeValue.disabled } } } From a951f8de5959b2d556e436e8710cf6593aafaf1d Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 18 Sep 2025 09:05:32 +1000 Subject: [PATCH 56/66] Added a StoreKit config for testing, reordered some dev settings --- Session.xcodeproj/project.pbxproj | 8 ++ .../Session - Anonymous Messenger.storekit | 117 ++++++++++++++++++ .../DeveloperSettingsViewModel.swift | 115 ++++++++--------- 3 files changed, 184 insertions(+), 56 deletions(-) create mode 100644 Session/Meta/Session - Anonymous Messenger.storekit diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index e6fc67b80f..2a07fbb52f 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -1043,6 +1043,8 @@ FDE6E99829F8E63A00F93C5D /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE6E99729F8E63A00F93C5D /* Accessibility.swift */; }; FDE71B0B2E79352D0023F5F9 /* DeveloperSettingsGroupsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE71B0A2E7935250023F5F9 /* DeveloperSettingsGroupsViewModel.swift */; }; FDE71B0D2E793B250023F5F9 /* DeveloperSettingsProViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE71B0C2E793B1F0023F5F9 /* DeveloperSettingsProViewModel.swift */; }; + FDE71B0F2E7A195B0023F5F9 /* Session - Anonymous Messenger.storekit in Resources */ = {isa = PBXBuildFile; fileRef = FDE71B0E2E7A195B0023F5F9 /* Session - Anonymous Messenger.storekit */; }; + FDE71B5F2E7A73570023F5F9 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDE71B5E2E7A73560023F5F9 /* StoreKit.framework */; }; FDE7549B2C940108002A2623 /* MessageViewModel+DeletionActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7549A2C940108002A2623 /* MessageViewModel+DeletionActions.swift */; }; FDE7549D2C9961A4002A2623 /* CommunityPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7549C2C9961A4002A2623 /* CommunityPoller.swift */; }; FDE754A12C9A60A6002A2623 /* Crypto+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A02C9A60A6002A2623 /* Crypto+OpenGroup.swift */; }; @@ -2311,6 +2313,8 @@ FDE6E99729F8E63A00F93C5D /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = ""; }; FDE71B0A2E7935250023F5F9 /* DeveloperSettingsGroupsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsGroupsViewModel.swift; sourceTree = ""; }; FDE71B0C2E793B1F0023F5F9 /* DeveloperSettingsProViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsProViewModel.swift; sourceTree = ""; }; + FDE71B0E2E7A195B0023F5F9 /* Session - Anonymous Messenger.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Session - Anonymous Messenger.storekit"; sourceTree = ""; }; + FDE71B5E2E7A73560023F5F9 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = ""; }; FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintLocalizableStrings.swift; sourceTree = ""; }; FDE7549A2C940108002A2623 /* MessageViewModel+DeletionActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageViewModel+DeletionActions.swift"; sourceTree = ""; }; @@ -2564,6 +2568,7 @@ A1C32D5117A06544000A904E /* AddressBook.framework in Frameworks */, FD6DA9CF2D015B440092085A /* Lucide in Frameworks */, A1C32D5017A06538000A904E /* AddressBookUI.framework in Frameworks */, + FDE71B5F2E7A73570023F5F9 /* StoreKit.framework in Frameworks */, D2AEACDC16C426DA00C364C0 /* CFNetwork.framework in Frameworks */, C331FF222558F9D300070591 /* SessionUIKit.framework in Frameworks */, D2179CFE16BB0B480006F3AB /* SystemConfiguration.framework in Frameworks */, @@ -3853,6 +3858,7 @@ FDE125222A837E4E002DA685 /* MainAppContext.swift */, C3CA3AA0255CDA7000F4C6D4 /* Mnemonic */, FD86FDA22BC5020600EC251B /* PrivacyInfo.xcprivacy */, + FDE71B0E2E7A195B0023F5F9 /* Session - Anonymous Messenger.storekit */, B67EBF5C19194AC60084CCFD /* Settings.bundle */, B657DDC91911A40500F45B0C /* Signal.entitlements */, FDF2220A2818F38D000A4995 /* SessionApp.swift */, @@ -3909,6 +3915,7 @@ D221A08C169C9E5E00537ABF /* Frameworks */ = { isa = PBXGroup; children = ( + FDE71B5E2E7A73560023F5F9 /* StoreKit.framework */, FDB6A87B2AD75B7F002D4F96 /* PhotosUI.framework */, 3496955F21A2FC8100DCFE74 /* CloudKit.framework */, 455A16DB1F1FEA0000F86704 /* Metal.framework */, @@ -5866,6 +5873,7 @@ C34C8F7423A7830B00D82669 /* SpaceMono-Bold.ttf in Resources */, FDEF57712C44D2D300131302 /* GeoLite2-Country-Blocks-IPv4 in Resources */, B8D07405265C683300F77E07 /* ElegantIcons.ttf in Resources */, + FDE71B0F2E7A195B0023F5F9 /* Session - Anonymous Messenger.storekit in Resources */, 45B74A7E2044AAB600CD42F8 /* complete.aifc in Resources */, 9473386E2BDF5F3E00B9E169 /* InfoPlist.xcstrings in Resources */, B8CCF6352396005F0091D419 /* SpaceMono-Regular.ttf in Resources */, diff --git a/Session/Meta/Session - Anonymous Messenger.storekit b/Session/Meta/Session - Anonymous Messenger.storekit new file mode 100644 index 0000000000..901eabe896 --- /dev/null +++ b/Session/Meta/Session - Anonymous Messenger.storekit @@ -0,0 +1,117 @@ +{ + "appPolicies" : { + "eula" : "", + "policies" : [ + { + "locale" : "en_US", + "policyText" : "", + "policyURL" : "" + } + ] + }, + "identifier" : "E83EE03B", + "nonRenewingSubscriptions" : [ + + ], + "products" : [ + + ], + "settings" : { + "_applicationInternalID" : "1470168868", + "_developerTeamID" : "SUQ8J2PCT7", + "_failTransactionsEnabled" : false, + "_lastSynchronizedDate" : 779753823.36554396, + "_locale" : "en_US", + "_storefront" : "USA", + "_storeKitErrors" : [ + { + "current" : null, + "enabled" : false, + "name" : "Load Products" + }, + { + "current" : null, + "enabled" : false, + "name" : "Purchase" + }, + { + "current" : null, + "enabled" : false, + "name" : "Verification" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Store Sync" + }, + { + "current" : null, + "enabled" : false, + "name" : "Subscription Status" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Transaction" + }, + { + "current" : null, + "enabled" : false, + "name" : "Manage Subscriptions Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Refund Request Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Offer Code Redeem Sheet" + } + ] + }, + "subscriptionGroups" : [ + { + "id" : "21752814", + "localizations" : [ + + ], + "name" : "Session Pro", + "subscriptions" : [ + { + "adHocOffers" : [ + + ], + "codeOffers" : [ + + ], + "displayPrice" : "0.99", + "familyShareable" : false, + "groupNumber" : 1, + "internalID" : "6749836944", + "introductoryOffer" : null, + "localizations" : [ + { + "description" : "Test 1 Week Session Pro Subscription", + "displayName" : "Test Session Pro", + "locale" : "en_US" + } + ], + "productID" : "com.getsession.org.pro_sub", + "recurringSubscriptionPeriod" : "P1W", + "referenceName" : "Session Pro Subscription", + "subscriptionGroupID" : "21752814", + "type" : "RecurringSubscription", + "winbackOffers" : [ + + ] + } + ] + } + ], + "version" : { + "major" : 4, + "minor" : 0 + } +} diff --git a/Session/Settings/DeveloperSettings/DeveloperSettingsViewModel.swift b/Session/Settings/DeveloperSettings/DeveloperSettingsViewModel.swift index 05b772057e..b32ecb10ad 100644 --- a/Session/Settings/DeveloperSettings/DeveloperSettingsViewModel.swift +++ b/Session/Settings/DeveloperSettings/DeveloperSettingsViewModel.swift @@ -35,27 +35,27 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, public enum Section: SessionTableSection { case developerMode - case sessionPro case sessionNetwork + case sessionPro + case groups case general case logging case network case disappearingMessages case communities - case groups case database var title: String? { switch self { case .developerMode: return nil - case .sessionPro: return "Session Pro" case .sessionNetwork: return "Session Network" + case .sessionPro: return "Session Pro" + case .groups: return "Groups" case .general: return "General" case .logging: return "Logging" case .network: return "Network" case .disappearingMessages: return "Disappearing Messages" case .communities: return "Communities" - case .groups: return "Groups" case .database: return "Database" } } @@ -71,6 +71,9 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, public enum TableItem: Hashable, Differentiable, CaseIterable { case developerMode + case proConfig + case groupConfig + case animationsEnabled case showStringKeys case truncatePubkeysInLogs @@ -92,8 +95,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, case communityPollLimit - case groupConfig - case proConfig case versionBlindedID case scheduleLocalNotification @@ -110,6 +111,10 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, public var differenceIdentifier: String { switch self { case .developerMode: return "developerMode" + + case .proConfig: return "proConfig" + case .groupConfig: return "groupConfig" + case .animationsEnabled: return "animationsEnabled" case .showStringKeys: return "showStringKeys" case .truncatePubkeysInLogs: return "truncatePubkeysInLogs" @@ -131,9 +136,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, case .communityPollLimit: return "communityPollLimit" - case .groupConfig: return "groupConfig" - case .proConfig: return "proConfig" - case .versionBlindedID: return "versionBlindedID" case .scheduleLocalNotification: return "scheduleLocalNotification" @@ -152,6 +154,10 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, var result: [TableItem] = [] switch TableItem.developerMode { case .developerMode: result.append(.developerMode); fallthrough + + case .proConfig: result.append(.proConfig); fallthrough + case .groupConfig: result.append(.groupConfig); fallthrough + case .animationsEnabled: result.append(.animationsEnabled); fallthrough case .showStringKeys: result.append(.showStringKeys); fallthrough case .truncatePubkeysInLogs: result.append(.truncatePubkeysInLogs); fallthrough @@ -173,9 +179,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, case .communityPollLimit: result.append(.communityPollLimit); fallthrough - case .groupConfig: result.append(.groupConfig); fallthrough - case .proConfig: result.append(.proConfig); fallthrough - case .versionBlindedID: result.append(.versionBlindedID); fallthrough case .scheduleLocalNotification: result.append(.scheduleLocalNotification); fallthrough @@ -286,6 +289,48 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ) ] ) + let sessionPro: SectionModel = SectionModel( + model: .sessionPro, + elements: [ + SessionCell.Info( + id: .proConfig, + title: "Session Pro", + subtitle: """ + Configure settings related to Session Pro. + + Session Pro: \(dependencies[feature: .sessionProEnabled] ? "Enabled" : "Disabled") + """, + trailingAccessory: .icon(.chevronRight), + onTap: { [weak self, dependencies] in + self?.transitionToScreen( + SessionTableViewController( + viewModel: DeveloperSettingsProViewModel(using: dependencies) + ) + ) + } + ) + ] + ) + let groups: SectionModel = SectionModel( + model: .groups, + elements: [ + SessionCell.Info( + id: .groupConfig, + title: "Group Configuration", + subtitle: """ + Configure settings related to Groups. + """, + trailingAccessory: .icon(.chevronRight), + onTap: { [weak self, dependencies] in + self?.transitionToScreen( + SessionTableViewController( + viewModel: DeveloperSettingsGroupsViewModel(using: dependencies) + ) + ) + } + ) + ] + ) let general: SectionModel = SectionModel( model: .general, elements: [ @@ -597,48 +642,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ) ] ) - let groups: SectionModel = SectionModel( - model: .groups, - elements: [ - SessionCell.Info( - id: .groupConfig, - title: "Group Configuration", - subtitle: """ - Configure settings related to Groups. - """, - trailingAccessory: .icon(.chevronRight), - onTap: { [weak self, dependencies] in - self?.transitionToScreen( - SessionTableViewController( - viewModel: DeveloperSettingsGroupsViewModel(using: dependencies) - ) - ) - } - ) - ] - ) - let sessionPro: SectionModel = SectionModel( - model: .sessionPro, - elements: [ - SessionCell.Info( - id: .proConfig, - title: "Session Pro", - subtitle: """ - Configure settings related to Session Pro. - - Session Pro: \(dependencies[feature: .sessionProEnabled] ? "Enabled" : "Disabled") - """, - trailingAccessory: .icon(.chevronRight), - onTap: { [weak self, dependencies] in - self?.transitionToScreen( - SessionTableViewController( - viewModel: DeveloperSettingsProViewModel(using: dependencies) - ) - ) - } - ) - ] - ) let database: SectionModel = SectionModel( model: .database, elements: [ @@ -740,13 +743,13 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, return [ developerMode, + sessionPro, + groups, general, logging, network, disappearingMessages, communities, - groups, - sessionPro, sessionNetwork, database ] From c1a195a976a36a261a3ee00c593456e54d40deec Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 18 Sep 2025 09:21:49 +1000 Subject: [PATCH 57/66] Fixed an issue where UnsendRequests could be processed after their messages --- .../Errors/MessageReceiverError.swift | 4 +- .../Pollers/SwarmPoller.swift | 7 ++- .../Utilities/ExtensionHelper.swift | 59 ++++++++++++++----- 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift index af8255fbce..15d5488927 100644 --- a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift +++ b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift @@ -26,13 +26,14 @@ public enum MessageReceiverError: Error, CustomStringConvertible { case duplicatedCall case missingRequiredAdminPrivileges case deprecatedMessage + case failedToProcess public var isRetryable: Bool { switch self { case .duplicateMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType, .invalidSignature, .noData, .senderBlocked, .noThread, .selfSend, .decryptionFailed, .invalidConfigMessageHandling, .outdatedMessage, .ignorableMessage, .ignorableMessageRequestMessage, - .missingRequiredAdminPrivileges: + .missingRequiredAdminPrivileges, .failedToProcess: return false default: return true @@ -112,6 +113,7 @@ public enum MessageReceiverError: Error, CustomStringConvertible { case .duplicatedCall: return "Duplicate call." case .missingRequiredAdminPrivileges: return "Handling this message requires admin privileges which the current user does not have." case .deprecatedMessage: return "This message type has been deprecated." + case .failedToProcess: return "Failed to process." } } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift index cfb327b06e..ab0851838b 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift @@ -386,16 +386,17 @@ public class SwarmPoller: SwarmPollerType & PollerType { } } - /// Make sure to add any synchronously processed messages to the `finalProcessedMessages` - /// as otherwise they wouldn't be emitted by the `receivedPollResponseSubject` + /// Make sure to add any synchronously processed messages to the `finalProcessedMessages` as otherwise + /// they wouldn't be emitted by the `receivedPollResponseSubject`, also need to add the count to + /// `messageCount` to ensure it's not incorrect finalProcessedMessages += processedMessages + messageCount += processedMessages.count return nil } .flatMap { $0 } /// If we don't want to store the messages then no need to continue (don't want to create message receive jobs or mess with cached hashes) guard shouldStoreMessages && !forceSynchronousProcessing else { - messageCount += allProcessedMessages.count finalProcessedMessages += allProcessedMessages return ([], [], (finalProcessedMessages, rawMessageCount, messageCount, hadValidHashUpdate)) } diff --git a/SessionMessagingKit/Utilities/ExtensionHelper.swift b/SessionMessagingKit/Utilities/ExtensionHelper.swift index 99d5314260..0075b91d78 100644 --- a/SessionMessagingKit/Utilities/ExtensionHelper.swift +++ b/SessionMessagingKit/Utilities/ExtensionHelper.swift @@ -865,29 +865,58 @@ public class ExtensionHelper: ExtensionHelperType { } ) - allMessagePaths.forEach { path in - do { - let plaintext: Data = try this.read(from: path) - let message: SnodeReceivedMessage = try JSONDecoder(using: dependencies) - .decode(SnodeReceivedMessage.self, from: plaintext) - - SwarmPoller.processPollResponse( + let sortedMessages: [MessageData] = allMessagePaths + .reduce([Network.SnodeAPI.Namespace: [SnodeReceivedMessage]]()) { (result: [Network.SnodeAPI.Namespace: [SnodeReceivedMessage]], path: String) in + do { + let plaintext: Data = try this.read(from: path) + let message: SnodeReceivedMessage = try JSONDecoder(using: dependencies) + .decode(SnodeReceivedMessage.self, from: plaintext) + + return result.appending(message, toArrayOn: message.namespace) + } + catch { + failureStandardCount += 1 + Log.error(.cat, "Discarding standard message due to error: \(error)") + return result + } + } + .map { namespace, messages -> MessageData in + /// We need to sort the messages as we don't know what order they were read from disk in and some + /// messages (eg. a `VisibleMessage` and it's corresponding `UnsendRequest`) need to be + /// processed in a particular order or they won't behave correctly, luckily the `SnodeReceivedMessage.timestampMs` + /// is the "network offset" timestamp when the message was sent to the storage server (rather than the + /// "sent timestamp" on the message, which for an `UnsendRequest` will match it's associate message) + /// so we can just sort by that + ( + namespace, + messages.sorted { $0.timestampMs < $1.timestampMs }, + nil + ) + } + .sorted { lhs, rhs in lhs.namespace.processingOrder < rhs.namespace.processingOrder } + + /// Process the message (inserting into the database if needed (messages are processed per conversaiton so + /// all have the same `swarmPublicKey`) + switch sortedMessages.first?.messages.first?.swarmPublicKey { + case .none: break + case .some(let swarmPublicKey): + let (_, _, result) = SwarmPoller.processPollResponse( db, cat: .cat, source: .pushNotification, - swarmPublicKey: message.swarmPublicKey, + swarmPublicKey: swarmPublicKey, shouldStoreMessages: true, ignoreDedupeFiles: true, forceSynchronousProcessing: true, - sortedMessages: [(message.namespace, [message], nil)], + sortedMessages: sortedMessages, using: dependencies ) - successStandardCount += 1 - } - catch { - failureStandardCount += 1 - Log.error(.cat, "Discarding standard message due to error: \(error)") - } + successStandardCount += result.validMessageCount + + if result.validMessageCount != result.rawMessageCount { + failureStandardCount += (result.rawMessageCount - result.validMessageCount) + Log.error(.cat, "Discarding some standard messages due to error: \(MessageReceiverError.failedToProcess)") + } } /// Remove the standard message files now that they are processed From 81536247d95bec8a2e04d48df7c8dee2e6f1863a Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 18 Sep 2025 13:19:21 +1000 Subject: [PATCH 58/66] Fixed merge conflict issues --- .../Message Cells/Content Views/DeletedMessageView.swift | 3 +-- SessionUIKit/Style Guide/ThemeManager.swift | 8 +++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift b/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift index 0d552b34b4..baed864ba4 100644 --- a/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift +++ b/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift @@ -44,7 +44,6 @@ final class DeletedMessageView: UIView { // Body label let titleLabel = UILabel() titleLabel.setContentHuggingPriority(.required, for: .vertical) - titleLabel.preferredMaxLayoutWidth = maxWidth - 6 // `6` for the `stackView.layoutMargins` titleLabel.font = .italicSystemFont(ofSize: Values.mediumFontSize) titleLabel.text = { switch variant { @@ -78,6 +77,6 @@ final class DeletedMessageView: UIView { stackView.pin(.trailing, to: .trailing, of: self, withInset: -Self.horizontalInset) stackView.pin(.bottom, to: .bottom, of: self, withInset: -Self.verticalInset) - stackView.set(.height, to: calculatedSize.height) + stackView.set(.height, greaterThanOrEqualTo: calculatedSize.height) } } diff --git a/SessionUIKit/Style Guide/ThemeManager.swift b/SessionUIKit/Style Guide/ThemeManager.swift index 7065d19d0f..e2dff643f2 100644 --- a/SessionUIKit/Style Guide/ThemeManager.swift +++ b/SessionUIKit/Style Guide/ThemeManager.swift @@ -63,7 +63,13 @@ public enum ThemeManager { // If the theme was changed then trigger a UI update and the callback for the theme settings // change (so it gets persisted) - guard themeChanged || matchSystemChanged else { return } + guard themeChanged || matchSystemChanged else { + if !hasSetInitialSystemTrait { + updateAllUI() + } + + return + } if !hasSetInitialSystemTrait || themeChanged { updateAllUI() From e45ee5adb03f6ec92ad505f8ca7f8ca0e50fad55 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 18 Sep 2025 14:13:27 +1000 Subject: [PATCH 59/66] Re-apply 'fix/SES-4551/emoji_category_section_title' changes --- Scripts/EmojiGenerator.swift | 11 +- .../EmojiPickerCollectionView.swift | 7 +- Session/Emoji/Emoji+Category.swift | 85 +++++- Session/Emoji/Emoji+Name.swift | 37 ++- Session/Emoji/Emoji+SkinTones.swift | 149 +++++++++- Session/Emoji/Emoji.swift | 35 ++- Session/Emoji/EmojiWithSkinTones+String.swift | 264 +++++++++++++++++- 7 files changed, 556 insertions(+), 32 deletions(-) diff --git a/Scripts/EmojiGenerator.swift b/Scripts/EmojiGenerator.swift index 519ce65220..1c2102c81f 100755 --- a/Scripts/EmojiGenerator.swift +++ b/Scripts/EmojiGenerator.swift @@ -53,7 +53,7 @@ enum RemoteModel { case flags = "Flags" case components = "Component" - var localizedKey: String = { + var localizedKey: String { switch self { case .smileys: return "Smileys" @@ -77,8 +77,8 @@ enum RemoteModel { return "Flags" case .components: return "Component" - } - }() + } + } } static func fetchEmojiData() throws -> Data { @@ -569,8 +569,9 @@ extension EmojiGenerator { fileHandle.indent { let stringKey = "emojiCategory\(category.localizedKey)" let stringComment = "The name for the emoji category '\(category.rawValue)'" - - fileHandle.writeLine("return NSLocalizedString(\"\(stringKey)\", comment: \"\(stringComment)\")") + + fileHandle.writeLine("// \(stringComment)") + fileHandle.writeLine("return \"\(stringKey)\".localized()") } } fileHandle.writeLine("}") diff --git a/Session/Conversations/Emoji Picker/EmojiPickerCollectionView.swift b/Session/Conversations/Emoji Picker/EmojiPickerCollectionView.swift index b366b5e2a8..289a6bb8e2 100644 --- a/Session/Conversations/Emoji Picker/EmojiPickerCollectionView.swift +++ b/Session/Conversations/Emoji Picker/EmojiPickerCollectionView.swift @@ -322,7 +322,12 @@ private class EmojiSectionHeader: UICollectionReusableView { label.font = .systemFont(ofSize: Values.smallFontSize) label.themeTextColor = .textPrimary addSubview(label) - label.pin(to: self) + + label.pin(.top, to: .top, of: self) + label.pin(.leading, to: .leading, of: self, withInset: Values.mediumSpacing) + label.pin(.trailing, to: .trailing, of: self, withInset: -Values.mediumSpacing) + label.pin(.bottom, to: .bottom, of: self) + label.setCompressionResistance(to: .required) } diff --git a/Session/Emoji/Emoji+Category.swift b/Session/Emoji/Emoji+Category.swift index 343d9a0e87..996f7d83a4 100644 --- a/Session/Emoji/Emoji+Category.swift +++ b/Session/Emoji/Emoji+Category.swift @@ -20,21 +20,29 @@ extension Emoji { var localizedName: String { switch self { case .smileysAndPeople: - return NSLocalizedString("emojiCategorySmileys", comment: "The name for the emoji category 'Smileys & People'") + // The name for the emoji category 'Smileys & People' + return "emojiCategorySmileys".localized() case .animals: - return NSLocalizedString("emojiCategoryAnimals", comment: "The name for the emoji category 'Animals & Nature'") + // The name for the emoji category 'Animals & Nature' + return "emojiCategoryAnimals".localized() case .food: - return NSLocalizedString("emojiCategoryFood", comment: "The name for the emoji category 'Food & Drink'") + // The name for the emoji category 'Food & Drink' + return "emojiCategoryFood".localized() case .activities: - return NSLocalizedString("emojiCategoryActivities", comment: "The name for the emoji category 'Activities'") + // The name for the emoji category 'Activities' + return "emojiCategoryActivities".localized() case .travel: - return NSLocalizedString("emojiCategoryTravel", comment: "The name for the emoji category 'Travel & Places'") + // The name for the emoji category 'Travel & Places' + return "emojiCategoryTravel".localized() case .objects: - return NSLocalizedString("emojiCategoryObjects", comment: "The name for the emoji category 'Objects'") + // The name for the emoji category 'Objects' + return "emojiCategoryObjects".localized() case .symbols: - return NSLocalizedString("emojiCategorySymbols", comment: "The name for the emoji category 'Symbols'") + // The name for the emoji category 'Symbols' + return "emojiCategorySymbols".localized() case .flags: - return NSLocalizedString("emojiCategoryFlags", comment: "The name for the emoji category 'Flags'") + // The name for the emoji category 'Flags' + return "emojiCategoryFlags".localized() } } @@ -92,6 +100,8 @@ extension Emoji { .faceExhaling, .lyingFace, .shakingFace, + .headShakingHorizontally, + .headShakingVertically, .relieved, .pensive, .sleepy, @@ -450,24 +460,42 @@ extension Emoji { .walking, .manWalking, .womanWalking, + .personWalkingFacingRight, + .womanWalkingFacingRight, + .manWalkingFacingRight, .standingPerson, .manStanding, .womanStanding, .kneelingPerson, .manKneeling, .womanKneeling, + .personKneelingFacingRight, + .womanKneelingFacingRight, + .manKneelingFacingRight, .personWithProbingCane, + .personWithWhiteCaneFacingRight, .manWithProbingCane, + .manWithWhiteCaneFacingRight, .womanWithProbingCane, + .womanWithWhiteCaneFacingRight, .personInMotorizedWheelchair, + .personInMotorizedWheelchairFacingRight, .manInMotorizedWheelchair, + .manInMotorizedWheelchairFacingRight, .womanInMotorizedWheelchair, + .womanInMotorizedWheelchairFacingRight, .personInManualWheelchair, + .personInManualWheelchairFacingRight, .manInManualWheelchair, + .manInManualWheelchairFacingRight, .womanInManualWheelchair, + .womanInManualWheelchairFacingRight, .runner, .manRunning, .womanRunning, + .personRunningFacingRight, + .womanRunningFacingRight, + .manRunningFacingRight, .dancer, .manDancing, .manInBusinessSuitLevitating, @@ -540,7 +568,6 @@ extension Emoji { .womanHeartMan, .manHeartMan, .womanHeartWoman, - .family, .manWomanBoy, .manWomanGirl, .manWomanGirlBoy, @@ -570,6 +597,11 @@ extension Emoji { .bustInSilhouette, .bustsInSilhouette, .peopleHugging, + .family, + .familyAdultAdultChild, + .familyAdultAdultChildChild, + .familyAdultChild, + .familyAdultChildChild, .footprints, ] case .animals: @@ -661,6 +693,7 @@ extension Emoji { .wing, .blackBird, .goose, + .phoenix, .frog, .crocodile, .turtle, @@ -734,6 +767,7 @@ extension Emoji { .watermelon, .tangerine, .lemon, + .lime, .banana, .pineapple, .mango, @@ -765,6 +799,7 @@ extension Emoji { .chestnut, .gingerRoot, .peaPod, + .brownMushroom, .bread, .croissant, .baguetteBread, @@ -1382,6 +1417,7 @@ extension Emoji { .scales, .probingCane, .link, + .brokenChain, .chains, .hook, .toolbox, @@ -1989,6 +2025,8 @@ extension Emoji { case .faceExhaling: return .smileysAndPeople case .lyingFace: return .smileysAndPeople case .shakingFace: return .smileysAndPeople + case .headShakingHorizontally: return .smileysAndPeople + case .headShakingVertically: return .smileysAndPeople case .relieved: return .smileysAndPeople case .pensive: return .smileysAndPeople case .sleepy: return .smileysAndPeople @@ -2347,24 +2385,42 @@ extension Emoji { case .walking: return .smileysAndPeople case .manWalking: return .smileysAndPeople case .womanWalking: return .smileysAndPeople + case .personWalkingFacingRight: return .smileysAndPeople + case .womanWalkingFacingRight: return .smileysAndPeople + case .manWalkingFacingRight: return .smileysAndPeople case .standingPerson: return .smileysAndPeople case .manStanding: return .smileysAndPeople case .womanStanding: return .smileysAndPeople case .kneelingPerson: return .smileysAndPeople case .manKneeling: return .smileysAndPeople case .womanKneeling: return .smileysAndPeople + case .personKneelingFacingRight: return .smileysAndPeople + case .womanKneelingFacingRight: return .smileysAndPeople + case .manKneelingFacingRight: return .smileysAndPeople case .personWithProbingCane: return .smileysAndPeople + case .personWithWhiteCaneFacingRight: return .smileysAndPeople case .manWithProbingCane: return .smileysAndPeople + case .manWithWhiteCaneFacingRight: return .smileysAndPeople case .womanWithProbingCane: return .smileysAndPeople + case .womanWithWhiteCaneFacingRight: return .smileysAndPeople case .personInMotorizedWheelchair: return .smileysAndPeople + case .personInMotorizedWheelchairFacingRight: return .smileysAndPeople case .manInMotorizedWheelchair: return .smileysAndPeople + case .manInMotorizedWheelchairFacingRight: return .smileysAndPeople case .womanInMotorizedWheelchair: return .smileysAndPeople + case .womanInMotorizedWheelchairFacingRight: return .smileysAndPeople case .personInManualWheelchair: return .smileysAndPeople + case .personInManualWheelchairFacingRight: return .smileysAndPeople case .manInManualWheelchair: return .smileysAndPeople + case .manInManualWheelchairFacingRight: return .smileysAndPeople case .womanInManualWheelchair: return .smileysAndPeople + case .womanInManualWheelchairFacingRight: return .smileysAndPeople case .runner: return .smileysAndPeople case .manRunning: return .smileysAndPeople case .womanRunning: return .smileysAndPeople + case .personRunningFacingRight: return .smileysAndPeople + case .womanRunningFacingRight: return .smileysAndPeople + case .manRunningFacingRight: return .smileysAndPeople case .dancer: return .smileysAndPeople case .manDancing: return .smileysAndPeople case .manInBusinessSuitLevitating: return .smileysAndPeople @@ -2437,7 +2493,6 @@ extension Emoji { case .womanHeartMan: return .smileysAndPeople case .manHeartMan: return .smileysAndPeople case .womanHeartWoman: return .smileysAndPeople - case .family: return .smileysAndPeople case .manWomanBoy: return .smileysAndPeople case .manWomanGirl: return .smileysAndPeople case .manWomanGirlBoy: return .smileysAndPeople @@ -2467,6 +2522,11 @@ extension Emoji { case .bustInSilhouette: return .smileysAndPeople case .bustsInSilhouette: return .smileysAndPeople case .peopleHugging: return .smileysAndPeople + case .family: return .smileysAndPeople + case .familyAdultAdultChild: return .smileysAndPeople + case .familyAdultAdultChildChild: return .smileysAndPeople + case .familyAdultChild: return .smileysAndPeople + case .familyAdultChildChild: return .smileysAndPeople case .footprints: return .smileysAndPeople case .monkeyFace: return .animals case .monkey: return .animals @@ -2555,6 +2615,7 @@ extension Emoji { case .wing: return .animals case .blackBird: return .animals case .goose: return .animals + case .phoenix: return .animals case .frog: return .animals case .crocodile: return .animals case .turtle: return .animals @@ -2625,6 +2686,7 @@ extension Emoji { case .watermelon: return .food case .tangerine: return .food case .lemon: return .food + case .lime: return .food case .banana: return .food case .pineapple: return .food case .mango: return .food @@ -2656,6 +2718,7 @@ extension Emoji { case .chestnut: return .food case .gingerRoot: return .food case .peaPod: return .food + case .brownMushroom: return .food case .bread: return .food case .croissant: return .food case .baguetteBread: return .food @@ -3264,6 +3327,7 @@ extension Emoji { case .scales: return .objects case .probingCane: return .objects case .link: return .objects + case .brokenChain: return .objects case .chains: return .objects case .hook: return .objects case .toolbox: return .objects @@ -3821,4 +3885,3 @@ extension Emoji { } } } -// swiftlint:disable all diff --git a/Session/Emoji/Emoji+Name.swift b/Session/Emoji/Emoji+Name.swift index 2ddb050adb..2b6abe71f9 100644 --- a/Session/Emoji/Emoji+Name.swift +++ b/Session/Emoji/Emoji+Name.swift @@ -1,9 +1,11 @@ // This file is generated by EmojiGenerator.swift, do not manually edit it. - +// // swiftlint:disable all // stringlint:disable +import Foundation + extension Emoji { var name: String { switch self { @@ -57,6 +59,8 @@ extension Emoji { case .faceExhaling: return "face exhaling, face_exhaling, faceexhaling" case .lyingFace: return "lying face, lying_face, lyingface" case .shakingFace: return "shaking face, shaking_face, shakingface" + case .headShakingHorizontally: return "head shaking horizontally, head_shaking_horizontally, headshakinghorizontally" + case .headShakingVertically: return "head shaking vertically, head_shaking_vertically, headshakingvertically" case .relieved: return "relieved, relieved face" case .pensive: return "pensive, pensive face" case .sleepy: return "sleepy, sleepy face" @@ -415,24 +419,42 @@ extension Emoji { case .walking: return "pedestrian, walking" case .manWalking: return "man walking, man-walking, manwalking" case .womanWalking: return "woman walking, woman-walking, womanwalking" + case .personWalkingFacingRight: return "person walking facing right, person_walking_facing_right, personwalkingfacingright" + case .womanWalkingFacingRight: return "woman walking facing right, woman_walking_facing_right, womanwalkingfacingright" + case .manWalkingFacingRight: return "man walking facing right, man_walking_facing_right, manwalkingfacingright" case .standingPerson: return "standing person, standing_person, standingperson" case .manStanding: return "man standing, man_standing, manstanding" case .womanStanding: return "woman standing, woman_standing, womanstanding" case .kneelingPerson: return "kneeling person, kneeling_person, kneelingperson" case .manKneeling: return "man kneeling, man_kneeling, mankneeling" case .womanKneeling: return "woman kneeling, woman_kneeling, womankneeling" + case .personKneelingFacingRight: return "person kneeling facing right, person_kneeling_facing_right, personkneelingfacingright" + case .womanKneelingFacingRight: return "woman kneeling facing right, woman_kneeling_facing_right, womankneelingfacingright" + case .manKneelingFacingRight: return "man kneeling facing right, man_kneeling_facing_right, mankneelingfacingright" case .personWithProbingCane: return "person with white cane, person_with_probing_cane, personwithprobingcane" + case .personWithWhiteCaneFacingRight: return "person with white cane facing right, person_with_white_cane_facing_right, personwithwhitecanefacingright" case .manWithProbingCane: return "man with white cane, man_with_probing_cane, manwithprobingcane" + case .manWithWhiteCaneFacingRight: return "man with white cane facing right, man_with_white_cane_facing_right, manwithwhitecanefacingright" case .womanWithProbingCane: return "woman with white cane, woman_with_probing_cane, womanwithprobingcane" + case .womanWithWhiteCaneFacingRight: return "woman with white cane facing right, woman_with_white_cane_facing_right, womanwithwhitecanefacingright" case .personInMotorizedWheelchair: return "person in motorized wheelchair, person_in_motorized_wheelchair, personinmotorizedwheelchair" + case .personInMotorizedWheelchairFacingRight: return "person in motorized wheelchair facing right, person_in_motorized_wheelchair_facing_right, personinmotorizedwheelchairfacingright" case .manInMotorizedWheelchair: return "man in motorized wheelchair, man_in_motorized_wheelchair, maninmotorizedwheelchair" + case .manInMotorizedWheelchairFacingRight: return "man in motorized wheelchair facing right, man_in_motorized_wheelchair_facing_right, maninmotorizedwheelchairfacingright" case .womanInMotorizedWheelchair: return "woman in motorized wheelchair, woman_in_motorized_wheelchair, womaninmotorizedwheelchair" + case .womanInMotorizedWheelchairFacingRight: return "woman in motorized wheelchair facing right, woman_in_motorized_wheelchair_facing_right, womaninmotorizedwheelchairfacingright" case .personInManualWheelchair: return "person in manual wheelchair, person_in_manual_wheelchair, personinmanualwheelchair" + case .personInManualWheelchairFacingRight: return "person in manual wheelchair facing right, person_in_manual_wheelchair_facing_right, personinmanualwheelchairfacingright" case .manInManualWheelchair: return "man in manual wheelchair, man_in_manual_wheelchair, maninmanualwheelchair" + case .manInManualWheelchairFacingRight: return "man in manual wheelchair facing right, man_in_manual_wheelchair_facing_right, maninmanualwheelchairfacingright" case .womanInManualWheelchair: return "woman in manual wheelchair, woman_in_manual_wheelchair, womaninmanualwheelchair" + case .womanInManualWheelchairFacingRight: return "woman in manual wheelchair facing right, woman_in_manual_wheelchair_facing_right, womaninmanualwheelchairfacingright" case .runner: return "runner, running" case .manRunning: return "man running, man-running, manrunning" case .womanRunning: return "woman running, woman-running, womanrunning" + case .personRunningFacingRight: return "person running facing right, person_running_facing_right, personrunningfacingright" + case .womanRunningFacingRight: return "woman running facing right, woman_running_facing_right, womanrunningfacingright" + case .manRunningFacingRight: return "man running facing right, man_running_facing_right, manrunningfacingright" case .dancer: return "dancer" case .manDancing: return "man dancing, man_dancing, mandancing" case .manInBusinessSuitLevitating: return "man_in_business_suit_levitating, maninbusinesssuitlevitating, person in suit levitating" @@ -505,7 +527,6 @@ extension Emoji { case .womanHeartMan: return "couple with heart: woman, man, woman-heart-man, womanheartman" case .manHeartMan: return "couple with heart: man, man, man-heart-man, manheartman" case .womanHeartWoman: return "couple with heart: woman, woman, woman-heart-woman, womanheartwoman" - case .family: return "family" case .manWomanBoy: return "family: man, woman, boy, man-woman-boy, manwomanboy" case .manWomanGirl: return "family: man, woman, girl, man-woman-girl, manwomangirl" case .manWomanGirlBoy: return "family: man, woman, girl, boy, man-woman-girl-boy, manwomangirlboy" @@ -535,6 +556,11 @@ extension Emoji { case .bustInSilhouette: return "bust in silhouette, bust_in_silhouette, bustinsilhouette" case .bustsInSilhouette: return "busts in silhouette, busts_in_silhouette, bustsinsilhouette" case .peopleHugging: return "people hugging, people_hugging, peoplehugging" + case .family: return "family" + case .familyAdultAdultChild: return "family: adult, adult, child, family_adult_adult_child, familyadultadultchild" + case .familyAdultAdultChildChild: return "family: adult, adult, child, child, family_adult_adult_child_child, familyadultadultchildchild" + case .familyAdultChild: return "family: adult, child, family_adult_child, familyadultchild" + case .familyAdultChildChild: return "family: adult, child, child, family_adult_child_child, familyadultchildchild" case .footprints: return "footprints" case .skinTone2: return "emoji modifier fitzpatrick type-1-2, skin-tone-2, skintone2" case .skinTone3: return "emoji modifier fitzpatrick type-3, skin-tone-3, skintone3" @@ -628,6 +654,7 @@ extension Emoji { case .wing: return "wing" case .blackBird: return "black bird, black_bird, blackbird" case .goose: return "goose" + case .phoenix: return "phoenix" case .frog: return "frog, frog face" case .crocodile: return "crocodile" case .turtle: return "turtle" @@ -698,6 +725,7 @@ extension Emoji { case .watermelon: return "watermelon" case .tangerine: return "tangerine" case .lemon: return "lemon" + case .lime: return "lime" case .banana: return "banana" case .pineapple: return "pineapple" case .mango: return "mango" @@ -729,6 +757,7 @@ extension Emoji { case .chestnut: return "chestnut" case .gingerRoot: return "ginger root, ginger_root, gingerroot" case .peaPod: return "pea pod, pea_pod, peapod" + case .brownMushroom: return "brown mushroom, brown_mushroom, brownmushroom" case .bread: return "bread" case .croissant: return "croissant" case .baguetteBread: return "baguette bread, baguette_bread, baguettebread" @@ -1337,6 +1366,7 @@ extension Emoji { case .scales: return "balance scale, scales" case .probingCane: return "probing cane, probing_cane, probingcane" case .link: return "link, link symbol" + case .brokenChain: return "broken chain, broken_chain, brokenchain" case .chains: return "chains" case .hook: return "hook" case .toolbox: return "toolbox" @@ -1852,7 +1882,7 @@ extension Emoji { case .flagTm: return "flag-tm, flagtm, turkmenistan flag" case .flagTn: return "flag-tn, flagtn, tunisia flag" case .flagTo: return "flag-to, flagto, tonga flag" - case .flagTr: return "flag-tr, flagtr, turkey flag" + case .flagTr: return "flag-tr, flagtr, türkiye flag" case .flagTt: return "flag-tt, flagtt, trinidad & tobago flag" case .flagTv: return "flag-tv, flagtv, tuvalu flag" case .flagTw: return "flag-tw, flagtw, taiwan flag" @@ -1885,4 +1915,3 @@ extension Emoji { } } } -// swiftlint:disable all diff --git a/Session/Emoji/Emoji+SkinTones.swift b/Session/Emoji/Emoji+SkinTones.swift index f1fb18434f..9f34de6151 100644 --- a/Session/Emoji/Emoji+SkinTones.swift +++ b/Session/Emoji/Emoji+SkinTones.swift @@ -1,9 +1,11 @@ // This file is generated by EmojiGenerator.swift, do not manually edit it. - +// // swiftlint:disable all // stringlint:disable +import Foundation + extension Emoji { enum SkinTone: String, CaseIterable, Equatable { case light = "🏻" @@ -1841,6 +1843,30 @@ extension Emoji { [.mediumDark]: "🚶🏾‍♀️", [.dark]: "🚶🏿‍♀️", ] + case .personWalkingFacingRight: + return [ + [.light]: "🚶🏻‍➡️", + [.mediumLight]: "🚶🏼‍➡️", + [.medium]: "🚶🏽‍➡️", + [.mediumDark]: "🚶🏾‍➡️", + [.dark]: "🚶🏿‍➡️", + ] + case .womanWalkingFacingRight: + return [ + [.light]: "🚶🏻‍♀️‍➡️", + [.mediumLight]: "🚶🏼‍♀️‍➡️", + [.medium]: "🚶🏽‍♀️‍➡️", + [.mediumDark]: "🚶🏾‍♀️‍➡️", + [.dark]: "🚶🏿‍♀️‍➡️", + ] + case .manWalkingFacingRight: + return [ + [.light]: "🚶🏻‍♂️‍➡️", + [.mediumLight]: "🚶🏼‍♂️‍➡️", + [.medium]: "🚶🏽‍♂️‍➡️", + [.mediumDark]: "🚶🏾‍♂️‍➡️", + [.dark]: "🚶🏿‍♂️‍➡️", + ] case .standingPerson: return [ [.light]: "🧍🏻", @@ -1889,6 +1915,30 @@ extension Emoji { [.mediumDark]: "🧎🏾‍♀️", [.dark]: "🧎🏿‍♀️", ] + case .personKneelingFacingRight: + return [ + [.light]: "🧎🏻‍➡️", + [.mediumLight]: "🧎🏼‍➡️", + [.medium]: "🧎🏽‍➡️", + [.mediumDark]: "🧎🏾‍➡️", + [.dark]: "🧎🏿‍➡️", + ] + case .womanKneelingFacingRight: + return [ + [.light]: "🧎🏻‍♀️‍➡️", + [.mediumLight]: "🧎🏼‍♀️‍➡️", + [.medium]: "🧎🏽‍♀️‍➡️", + [.mediumDark]: "🧎🏾‍♀️‍➡️", + [.dark]: "🧎🏿‍♀️‍➡️", + ] + case .manKneelingFacingRight: + return [ + [.light]: "🧎🏻‍♂️‍➡️", + [.mediumLight]: "🧎🏼‍♂️‍➡️", + [.medium]: "🧎🏽‍♂️‍➡️", + [.mediumDark]: "🧎🏾‍♂️‍➡️", + [.dark]: "🧎🏿‍♂️‍➡️", + ] case .personWithProbingCane: return [ [.light]: "🧑🏻‍🦯", @@ -1897,6 +1947,14 @@ extension Emoji { [.mediumDark]: "🧑🏾‍🦯", [.dark]: "🧑🏿‍🦯", ] + case .personWithWhiteCaneFacingRight: + return [ + [.light]: "🧑🏻‍🦯‍➡️", + [.mediumLight]: "🧑🏼‍🦯‍➡️", + [.medium]: "🧑🏽‍🦯‍➡️", + [.mediumDark]: "🧑🏾‍🦯‍➡️", + [.dark]: "🧑🏿‍🦯‍➡️", + ] case .manWithProbingCane: return [ [.light]: "👨🏻‍🦯", @@ -1905,6 +1963,14 @@ extension Emoji { [.mediumDark]: "👨🏾‍🦯", [.dark]: "👨🏿‍🦯", ] + case .manWithWhiteCaneFacingRight: + return [ + [.light]: "👨🏻‍🦯‍➡️", + [.mediumLight]: "👨🏼‍🦯‍➡️", + [.medium]: "👨🏽‍🦯‍➡️", + [.mediumDark]: "👨🏾‍🦯‍➡️", + [.dark]: "👨🏿‍🦯‍➡️", + ] case .womanWithProbingCane: return [ [.light]: "👩🏻‍🦯", @@ -1913,6 +1979,14 @@ extension Emoji { [.mediumDark]: "👩🏾‍🦯", [.dark]: "👩🏿‍🦯", ] + case .womanWithWhiteCaneFacingRight: + return [ + [.light]: "👩🏻‍🦯‍➡️", + [.mediumLight]: "👩🏼‍🦯‍➡️", + [.medium]: "👩🏽‍🦯‍➡️", + [.mediumDark]: "👩🏾‍🦯‍➡️", + [.dark]: "👩🏿‍🦯‍➡️", + ] case .personInMotorizedWheelchair: return [ [.light]: "🧑🏻‍🦼", @@ -1921,6 +1995,14 @@ extension Emoji { [.mediumDark]: "🧑🏾‍🦼", [.dark]: "🧑🏿‍🦼", ] + case .personInMotorizedWheelchairFacingRight: + return [ + [.light]: "🧑🏻‍🦼‍➡️", + [.mediumLight]: "🧑🏼‍🦼‍➡️", + [.medium]: "🧑🏽‍🦼‍➡️", + [.mediumDark]: "🧑🏾‍🦼‍➡️", + [.dark]: "🧑🏿‍🦼‍➡️", + ] case .manInMotorizedWheelchair: return [ [.light]: "👨🏻‍🦼", @@ -1929,6 +2011,14 @@ extension Emoji { [.mediumDark]: "👨🏾‍🦼", [.dark]: "👨🏿‍🦼", ] + case .manInMotorizedWheelchairFacingRight: + return [ + [.light]: "👨🏻‍🦼‍➡️", + [.mediumLight]: "👨🏼‍🦼‍➡️", + [.medium]: "👨🏽‍🦼‍➡️", + [.mediumDark]: "👨🏾‍🦼‍➡️", + [.dark]: "👨🏿‍🦼‍➡️", + ] case .womanInMotorizedWheelchair: return [ [.light]: "👩🏻‍🦼", @@ -1937,6 +2027,14 @@ extension Emoji { [.mediumDark]: "👩🏾‍🦼", [.dark]: "👩🏿‍🦼", ] + case .womanInMotorizedWheelchairFacingRight: + return [ + [.light]: "👩🏻‍🦼‍➡️", + [.mediumLight]: "👩🏼‍🦼‍➡️", + [.medium]: "👩🏽‍🦼‍➡️", + [.mediumDark]: "👩🏾‍🦼‍➡️", + [.dark]: "👩🏿‍🦼‍➡️", + ] case .personInManualWheelchair: return [ [.light]: "🧑🏻‍🦽", @@ -1945,6 +2043,14 @@ extension Emoji { [.mediumDark]: "🧑🏾‍🦽", [.dark]: "🧑🏿‍🦽", ] + case .personInManualWheelchairFacingRight: + return [ + [.light]: "🧑🏻‍🦽‍➡️", + [.mediumLight]: "🧑🏼‍🦽‍➡️", + [.medium]: "🧑🏽‍🦽‍➡️", + [.mediumDark]: "🧑🏾‍🦽‍➡️", + [.dark]: "🧑🏿‍🦽‍➡️", + ] case .manInManualWheelchair: return [ [.light]: "👨🏻‍🦽", @@ -1953,6 +2059,14 @@ extension Emoji { [.mediumDark]: "👨🏾‍🦽", [.dark]: "👨🏿‍🦽", ] + case .manInManualWheelchairFacingRight: + return [ + [.light]: "👨🏻‍🦽‍➡️", + [.mediumLight]: "👨🏼‍🦽‍➡️", + [.medium]: "👨🏽‍🦽‍➡️", + [.mediumDark]: "👨🏾‍🦽‍➡️", + [.dark]: "👨🏿‍🦽‍➡️", + ] case .womanInManualWheelchair: return [ [.light]: "👩🏻‍🦽", @@ -1961,6 +2075,14 @@ extension Emoji { [.mediumDark]: "👩🏾‍🦽", [.dark]: "👩🏿‍🦽", ] + case .womanInManualWheelchairFacingRight: + return [ + [.light]: "👩🏻‍🦽‍➡️", + [.mediumLight]: "👩🏼‍🦽‍➡️", + [.medium]: "👩🏽‍🦽‍➡️", + [.mediumDark]: "👩🏾‍🦽‍➡️", + [.dark]: "👩🏿‍🦽‍➡️", + ] case .runner: return [ [.light]: "🏃🏻", @@ -1985,6 +2107,30 @@ extension Emoji { [.mediumDark]: "🏃🏾‍♀️", [.dark]: "🏃🏿‍♀️", ] + case .personRunningFacingRight: + return [ + [.light]: "🏃🏻‍➡️", + [.mediumLight]: "🏃🏼‍➡️", + [.medium]: "🏃🏽‍➡️", + [.mediumDark]: "🏃🏾‍➡️", + [.dark]: "🏃🏿‍➡️", + ] + case .womanRunningFacingRight: + return [ + [.light]: "🏃🏻‍♀️‍➡️", + [.mediumLight]: "🏃🏼‍♀️‍➡️", + [.medium]: "🏃🏽‍♀️‍➡️", + [.mediumDark]: "🏃🏾‍♀️‍➡️", + [.dark]: "🏃🏿‍♀️‍➡️", + ] + case .manRunningFacingRight: + return [ + [.light]: "🏃🏻‍♂️‍➡️", + [.mediumLight]: "🏃🏼‍♂️‍➡️", + [.medium]: "🏃🏽‍♂️‍➡️", + [.mediumDark]: "🏃🏾‍♂️‍➡️", + [.dark]: "🏃🏿‍♂️‍➡️", + ] case .dancer: return [ [.light]: "💃🏻", @@ -2741,4 +2887,3 @@ extension Emoji { } } } -// swiftlint:disable all diff --git a/Session/Emoji/Emoji.swift b/Session/Emoji/Emoji.swift index aa8352ca24..a315b53273 100644 --- a/Session/Emoji/Emoji.swift +++ b/Session/Emoji/Emoji.swift @@ -1,9 +1,11 @@ // This file is generated by EmojiGenerator.swift, do not manually edit it. - +// // swiftlint:disable all // stringlint:disable +import Foundation + /// A sorted representation of all available emoji enum Emoji: String, CaseIterable, Equatable { case grinning = "😀" @@ -56,6 +58,8 @@ enum Emoji: String, CaseIterable, Equatable { case faceExhaling = "😮‍💨" case lyingFace = "🤥" case shakingFace = "🫨" + case headShakingHorizontally = "🙂‍↔️" + case headShakingVertically = "🙂‍↕️" case relieved = "😌" case pensive = "😔" case sleepy = "😪" @@ -414,24 +418,42 @@ enum Emoji: String, CaseIterable, Equatable { case walking = "🚶" case manWalking = "🚶‍♂️" case womanWalking = "🚶‍♀️" + case personWalkingFacingRight = "🚶‍➡️" + case womanWalkingFacingRight = "🚶‍♀️‍➡️" + case manWalkingFacingRight = "🚶‍♂️‍➡️" case standingPerson = "🧍" case manStanding = "🧍‍♂️" case womanStanding = "🧍‍♀️" case kneelingPerson = "🧎" case manKneeling = "🧎‍♂️" case womanKneeling = "🧎‍♀️" + case personKneelingFacingRight = "🧎‍➡️" + case womanKneelingFacingRight = "🧎‍♀️‍➡️" + case manKneelingFacingRight = "🧎‍♂️‍➡️" case personWithProbingCane = "🧑‍🦯" + case personWithWhiteCaneFacingRight = "🧑‍🦯‍➡️" case manWithProbingCane = "👨‍🦯" + case manWithWhiteCaneFacingRight = "👨‍🦯‍➡️" case womanWithProbingCane = "👩‍🦯" + case womanWithWhiteCaneFacingRight = "👩‍🦯‍➡️" case personInMotorizedWheelchair = "🧑‍🦼" + case personInMotorizedWheelchairFacingRight = "🧑‍🦼‍➡️" case manInMotorizedWheelchair = "👨‍🦼" + case manInMotorizedWheelchairFacingRight = "👨‍🦼‍➡️" case womanInMotorizedWheelchair = "👩‍🦼" + case womanInMotorizedWheelchairFacingRight = "👩‍🦼‍➡️" case personInManualWheelchair = "🧑‍🦽" + case personInManualWheelchairFacingRight = "🧑‍🦽‍➡️" case manInManualWheelchair = "👨‍🦽" + case manInManualWheelchairFacingRight = "👨‍🦽‍➡️" case womanInManualWheelchair = "👩‍🦽" + case womanInManualWheelchairFacingRight = "👩‍🦽‍➡️" case runner = "🏃" case manRunning = "🏃‍♂️" case womanRunning = "🏃‍♀️" + case personRunningFacingRight = "🏃‍➡️" + case womanRunningFacingRight = "🏃‍♀️‍➡️" + case manRunningFacingRight = "🏃‍♂️‍➡️" case dancer = "💃" case manDancing = "🕺" case manInBusinessSuitLevitating = "🕴️" @@ -504,7 +526,6 @@ enum Emoji: String, CaseIterable, Equatable { case womanHeartMan = "👩‍❤️‍👨" case manHeartMan = "👨‍❤️‍👨" case womanHeartWoman = "👩‍❤️‍👩" - case family = "👪" case manWomanBoy = "👨‍👩‍👦" case manWomanGirl = "👨‍👩‍👧" case manWomanGirlBoy = "👨‍👩‍👧‍👦" @@ -534,6 +555,11 @@ enum Emoji: String, CaseIterable, Equatable { case bustInSilhouette = "👤" case bustsInSilhouette = "👥" case peopleHugging = "🫂" + case family = "👪" + case familyAdultAdultChild = "🧑‍🧑‍🧒" + case familyAdultAdultChildChild = "🧑‍🧑‍🧒‍🧒" + case familyAdultChild = "🧑‍🧒" + case familyAdultChildChild = "🧑‍🧒‍🧒" case footprints = "👣" case skinTone2 = "🏻" case skinTone3 = "🏼" @@ -627,6 +653,7 @@ enum Emoji: String, CaseIterable, Equatable { case wing = "🪽" case blackBird = "🐦‍⬛" case goose = "🪿" + case phoenix = "🐦‍🔥" case frog = "🐸" case crocodile = "🐊" case turtle = "🐢" @@ -697,6 +724,7 @@ enum Emoji: String, CaseIterable, Equatable { case watermelon = "🍉" case tangerine = "🍊" case lemon = "🍋" + case lime = "🍋‍🟩" case banana = "🍌" case pineapple = "🍍" case mango = "🥭" @@ -728,6 +756,7 @@ enum Emoji: String, CaseIterable, Equatable { case chestnut = "🌰" case gingerRoot = "🫚" case peaPod = "🫛" + case brownMushroom = "🍄‍🟫" case bread = "🍞" case croissant = "🥐" case baguetteBread = "🥖" @@ -1336,6 +1365,7 @@ enum Emoji: String, CaseIterable, Equatable { case scales = "⚖️" case probingCane = "🦯" case link = "🔗" + case brokenChain = "⛓️‍💥" case chains = "⛓️" case hook = "🪝" case toolbox = "🧰" @@ -1882,4 +1912,3 @@ enum Emoji: String, CaseIterable, Equatable { case flagScotland = "🏴󠁧󠁢󠁳󠁣󠁴󠁿" case flagWales = "🏴󠁧󠁢󠁷󠁬󠁳󠁿" } -// swiftlint:disable all diff --git a/Session/Emoji/EmojiWithSkinTones+String.swift b/Session/Emoji/EmojiWithSkinTones+String.swift index e1b1c84daa..3572ff1249 100644 --- a/Session/Emoji/EmojiWithSkinTones+String.swift +++ b/Session/Emoji/EmojiWithSkinTones+String.swift @@ -1,9 +1,11 @@ // This file is generated by EmojiGenerator.swift, do not manually edit it. - +// // swiftlint:disable all // stringlint:disable +import Foundation + extension EmojiWithSkinTones { init?(rawValue: String) { guard rawValue.isSingleEmoji else { return nil } @@ -75,16 +77,19 @@ extension EmojiWithSkinTones { case 1934: self = EmojiWithSkinTones.emojiFrom1934(rawValue) case 1935: self = EmojiWithSkinTones.emojiFrom1935(rawValue) case 1937: self = EmojiWithSkinTones.emojiFrom1937(rawValue) + case 2104: self = EmojiWithSkinTones.emojiFrom2104(rawValue) case 2109: self = EmojiWithSkinTones.emojiFrom2109(rawValue) case 2111: self = EmojiWithSkinTones.emojiFrom2111(rawValue) case 2112: self = EmojiWithSkinTones.emojiFrom2112(rawValue) case 2113: self = EmojiWithSkinTones.emojiFrom2113(rawValue) case 2116: self = EmojiWithSkinTones.emojiFrom2116(rawValue) case 2117: self = EmojiWithSkinTones.emojiFrom2117(rawValue) + case 2120: self = EmojiWithSkinTones.emojiFrom2120(rawValue) case 2123: self = EmojiWithSkinTones.emojiFrom2123(rawValue) case 2125: self = EmojiWithSkinTones.emojiFrom2125(rawValue) case 2126: self = EmojiWithSkinTones.emojiFrom2126(rawValue) case 2127: self = EmojiWithSkinTones.emojiFrom2127(rawValue) + case 2128: self = EmojiWithSkinTones.emojiFrom2128(rawValue) case 2129: self = EmojiWithSkinTones.emojiFrom2129(rawValue) case 2210: self = EmojiWithSkinTones.emojiFrom2210(rawValue) case 2549: self = EmojiWithSkinTones.emojiFrom2549(rawValue) @@ -105,8 +110,10 @@ extension EmojiWithSkinTones { case 2641: self = EmojiWithSkinTones.emojiFrom2641(rawValue) case 2642: self = EmojiWithSkinTones.emojiFrom2642(rawValue) case 2644: self = EmojiWithSkinTones.emojiFrom2644(rawValue) + case 2645: self = EmojiWithSkinTones.emojiFrom2645(rawValue) case 2646: self = EmojiWithSkinTones.emojiFrom2646(rawValue) case 2649: self = EmojiWithSkinTones.emojiFrom2649(rawValue) + case 2650: self = EmojiWithSkinTones.emojiFrom2650(rawValue) case 2655: self = EmojiWithSkinTones.emojiFrom2655(rawValue) case 2656: self = EmojiWithSkinTones.emojiFrom2656(rawValue) case 2657: self = EmojiWithSkinTones.emojiFrom2657(rawValue) @@ -117,6 +124,9 @@ extension EmojiWithSkinTones { case 2760: self = EmojiWithSkinTones.emojiFrom2760(rawValue) case 2761: self = EmojiWithSkinTones.emojiFrom2761(rawValue) case 2764: self = EmojiWithSkinTones.emojiFrom2764(rawValue) + case 2943: self = EmojiWithSkinTones.emojiFrom2943(rawValue) + case 2951: self = EmojiWithSkinTones.emojiFrom2951(rawValue) + case 2959: self = EmojiWithSkinTones.emojiFrom2959(rawValue) case 3289: self = EmojiWithSkinTones.emojiFrom3289(rawValue) case 3295: self = EmojiWithSkinTones.emojiFrom3295(rawValue) case 3389: self = EmojiWithSkinTones.emojiFrom3389(rawValue) @@ -126,12 +136,16 @@ extension EmojiWithSkinTones { case 3394: self = EmojiWithSkinTones.emojiFrom3394(rawValue) case 3396: self = EmojiWithSkinTones.emojiFrom3396(rawValue) case 3397: self = EmojiWithSkinTones.emojiFrom3397(rawValue) + case 3400: self = EmojiWithSkinTones.emojiFrom3400(rawValue) case 3403: self = EmojiWithSkinTones.emojiFrom3403(rawValue) case 3404: self = EmojiWithSkinTones.emojiFrom3404(rawValue) case 3405: self = EmojiWithSkinTones.emojiFrom3405(rawValue) case 3406: self = EmojiWithSkinTones.emojiFrom3406(rawValue) case 3407: self = EmojiWithSkinTones.emojiFrom3407(rawValue) + case 3408: self = EmojiWithSkinTones.emojiFrom3408(rawValue) case 3477: self = EmojiWithSkinTones.emojiFrom3477(rawValue) + case 3491: self = EmojiWithSkinTones.emojiFrom3491(rawValue) + case 3505: self = EmojiWithSkinTones.emojiFrom3505(rawValue) case 3921: self = EmojiWithSkinTones.emojiFrom3921(rawValue) case 3922: self = EmojiWithSkinTones.emojiFrom3922(rawValue) case 3924: self = EmojiWithSkinTones.emojiFrom3924(rawValue) @@ -149,9 +163,16 @@ extension EmojiWithSkinTones { case 3951: self = EmojiWithSkinTones.emojiFrom3951(rawValue) case 4007: self = EmojiWithSkinTones.emojiFrom4007(rawValue) case 4046: self = EmojiWithSkinTones.emojiFrom4046(rawValue) + case 4048: self = EmojiWithSkinTones.emojiFrom4048(rawValue) + case 4223: self = EmojiWithSkinTones.emojiFrom4223(rawValue) + case 4231: self = EmojiWithSkinTones.emojiFrom4231(rawValue) + case 4239: self = EmojiWithSkinTones.emojiFrom4239(rawValue) + case 4771: self = EmojiWithSkinTones.emojiFrom4771(rawValue) + case 4785: self = EmojiWithSkinTones.emojiFrom4785(rawValue) case 4840: self = EmojiWithSkinTones.emojiFrom4840(rawValue) case 5237: self = EmojiWithSkinTones.emojiFrom5237(rawValue) case 5370: self = EmojiWithSkinTones.emojiFrom5370(rawValue) + case 5425: self = EmojiWithSkinTones.emojiFrom5425(rawValue) case 6037: self = EmojiWithSkinTones.emojiFrom6037(rawValue) case 6065: self = EmojiWithSkinTones.emojiFrom6065(rawValue) case 6579: self = EmojiWithSkinTones.emojiFrom6579(rawValue) @@ -961,9 +982,9 @@ extension EmojiWithSkinTones { "👬": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: nil), "💏": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: nil), "💑": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: nil), - "👪": EmojiWithSkinTones(baseEmoji: .family, skinTones: nil), "👤": EmojiWithSkinTones(baseEmoji: .bustInSilhouette, skinTones: nil), "👥": EmojiWithSkinTones(baseEmoji: .bustsInSilhouette, skinTones: nil), + "👪": EmojiWithSkinTones(baseEmoji: .family, skinTones: nil), "💐": EmojiWithSkinTones(baseEmoji: .bouquet, skinTones: nil), "💮": EmojiWithSkinTones(baseEmoji: .whiteFlower, skinTones: nil), "💒": EmojiWithSkinTones(baseEmoji: .wedding, skinTones: nil), @@ -1993,6 +2014,14 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom2104(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🙂‍↔️": EmojiWithSkinTones(baseEmoji: .headShakingHorizontally, skinTones: nil), + "🙂‍↕️": EmojiWithSkinTones(baseEmoji: .headShakingVertically, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom2109(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "🏃‍♂️": EmojiWithSkinTones(baseEmoji: .manRunning, skinTones: nil), @@ -2046,7 +2075,9 @@ extension EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👨‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: nil), "👩‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: nil), - "🐻‍❄️": EmojiWithSkinTones(baseEmoji: .polarBear, skinTones: nil) + "🏃‍➡️": EmojiWithSkinTones(baseEmoji: .personRunningFacingRight, skinTones: nil), + "🐻‍❄️": EmojiWithSkinTones(baseEmoji: .polarBear, skinTones: nil), + "⛓️‍💥": EmojiWithSkinTones(baseEmoji: .brokenChain, skinTones: nil) ] return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } @@ -2084,6 +2115,13 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom2120(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🚶‍➡️": EmojiWithSkinTones(baseEmoji: .personWalkingFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom2123(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "🤦‍♂️": EmojiWithSkinTones(baseEmoji: .manFacepalming, skinTones: nil), @@ -2159,6 +2197,13 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom2128(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧎‍➡️": EmojiWithSkinTones(baseEmoji: .personKneelingFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom2129(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "❤️‍🩹": EmojiWithSkinTones(baseEmoji: .mendingHeart, skinTones: nil) @@ -3197,6 +3242,13 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom2645(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🐦‍🔥": EmojiWithSkinTones(baseEmoji: .phoenix, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom2646(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👨‍🔧": EmojiWithSkinTones(baseEmoji: .maleMechanic, skinTones: nil), @@ -3219,6 +3271,14 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom2650(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🍋‍🟩": EmojiWithSkinTones(baseEmoji: .lime, skinTones: nil), + "🍄‍🟫": EmojiWithSkinTones(baseEmoji: .brownMushroom, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom2655(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "🧑‍🎓": EmojiWithSkinTones(baseEmoji: .student, skinTones: nil), @@ -3293,7 +3353,8 @@ extension EmojiWithSkinTones { "🧑‍🦲": EmojiWithSkinTones(baseEmoji: .baldPerson, skinTones: nil), "🧑‍🦯": EmojiWithSkinTones(baseEmoji: .personWithProbingCane, skinTones: nil), "🧑‍🦼": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchair, skinTones: nil), - "🧑‍🦽": EmojiWithSkinTones(baseEmoji: .personInManualWheelchair, skinTones: nil) + "🧑‍🦽": EmojiWithSkinTones(baseEmoji: .personInManualWheelchair, skinTones: nil), + "🧑‍🧒": EmojiWithSkinTones(baseEmoji: .familyAdultChild, skinTones: nil) ] return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } @@ -3323,6 +3384,30 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom2943(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏃‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanRunningFacingRight, skinTones: nil), + "🏃‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manRunningFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2951(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🚶‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanWalkingFacingRight, skinTones: nil), + "🚶‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manWalkingFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2959(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧎‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanKneelingFacingRight, skinTones: nil), + "🧎‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manKneelingFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom3289(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "🏳️‍🌈": EmojiWithSkinTones(baseEmoji: .rainbowFlag, skinTones: nil) @@ -3519,14 +3604,19 @@ extension EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👨🏻‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.light]), "👩🏻‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.light]), + "🏃🏻‍➡️": EmojiWithSkinTones(baseEmoji: .personRunningFacingRight, skinTones: [.light]), "👨🏼‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.mediumLight]), "👩🏼‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.mediumLight]), + "🏃🏼‍➡️": EmojiWithSkinTones(baseEmoji: .personRunningFacingRight, skinTones: [.mediumLight]), "👨🏽‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.medium]), "👩🏽‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.medium]), + "🏃🏽‍➡️": EmojiWithSkinTones(baseEmoji: .personRunningFacingRight, skinTones: [.medium]), "👨🏾‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.mediumDark]), "👩🏾‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.mediumDark]), + "🏃🏾‍➡️": EmojiWithSkinTones(baseEmoji: .personRunningFacingRight, skinTones: [.mediumDark]), "👨🏿‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.dark]), - "👩🏿‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.dark]) + "👩🏿‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.dark]), + "🏃🏿‍➡️": EmojiWithSkinTones(baseEmoji: .personRunningFacingRight, skinTones: [.dark]) ] return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } @@ -3659,6 +3749,17 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom3400(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🚶🏻‍➡️": EmojiWithSkinTones(baseEmoji: .personWalkingFacingRight, skinTones: [.light]), + "🚶🏼‍➡️": EmojiWithSkinTones(baseEmoji: .personWalkingFacingRight, skinTones: [.mediumLight]), + "🚶🏽‍➡️": EmojiWithSkinTones(baseEmoji: .personWalkingFacingRight, skinTones: [.medium]), + "🚶🏾‍➡️": EmojiWithSkinTones(baseEmoji: .personWalkingFacingRight, skinTones: [.mediumDark]), + "🚶🏿‍➡️": EmojiWithSkinTones(baseEmoji: .personWalkingFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom3403(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "🤦🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manFacepalming, skinTones: [.light]), @@ -3914,6 +4015,17 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom3408(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧎🏻‍➡️": EmojiWithSkinTones(baseEmoji: .personKneelingFacingRight, skinTones: [.light]), + "🧎🏼‍➡️": EmojiWithSkinTones(baseEmoji: .personKneelingFacingRight, skinTones: [.mediumLight]), + "🧎🏽‍➡️": EmojiWithSkinTones(baseEmoji: .personKneelingFacingRight, skinTones: [.medium]), + "🧎🏾‍➡️": EmojiWithSkinTones(baseEmoji: .personKneelingFacingRight, skinTones: [.mediumDark]), + "🧎🏿‍➡️": EmojiWithSkinTones(baseEmoji: .personKneelingFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom3477(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👩‍❤️‍👨": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: nil), @@ -3923,6 +4035,27 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom3491(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .manWithWhiteCaneFacingRight, skinTones: nil), + "👩‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .womanWithWhiteCaneFacingRight, skinTones: nil), + "👨‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchairFacingRight, skinTones: nil), + "👩‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchairFacingRight, skinTones: nil), + "👨‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .manInManualWheelchairFacingRight, skinTones: nil), + "👩‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchairFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3505(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .personWithWhiteCaneFacingRight, skinTones: nil), + "🧑‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchairFacingRight, skinTones: nil), + "🧑‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .personInManualWheelchairFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom3921(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👨🏻‍🎓": EmojiWithSkinTones(baseEmoji: .maleStudent, skinTones: [.light]), @@ -4359,6 +4492,119 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom4048(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍🧑‍🧒": EmojiWithSkinTones(baseEmoji: .familyAdultAdultChild, skinTones: nil), + "🧑‍🧒‍🧒": EmojiWithSkinTones(baseEmoji: .familyAdultChildChild, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom4223(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏃🏻‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanRunningFacingRight, skinTones: [.light]), + "🏃🏻‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manRunningFacingRight, skinTones: [.light]), + "🏃🏼‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanRunningFacingRight, skinTones: [.mediumLight]), + "🏃🏼‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manRunningFacingRight, skinTones: [.mediumLight]), + "🏃🏽‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanRunningFacingRight, skinTones: [.medium]), + "🏃🏽‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manRunningFacingRight, skinTones: [.medium]), + "🏃🏾‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanRunningFacingRight, skinTones: [.mediumDark]), + "🏃🏾‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manRunningFacingRight, skinTones: [.mediumDark]), + "🏃🏿‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanRunningFacingRight, skinTones: [.dark]), + "🏃🏿‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manRunningFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom4231(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🚶🏻‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanWalkingFacingRight, skinTones: [.light]), + "🚶🏻‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manWalkingFacingRight, skinTones: [.light]), + "🚶🏼‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanWalkingFacingRight, skinTones: [.mediumLight]), + "🚶🏼‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manWalkingFacingRight, skinTones: [.mediumLight]), + "🚶🏽‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanWalkingFacingRight, skinTones: [.medium]), + "🚶🏽‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manWalkingFacingRight, skinTones: [.medium]), + "🚶🏾‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanWalkingFacingRight, skinTones: [.mediumDark]), + "🚶🏾‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manWalkingFacingRight, skinTones: [.mediumDark]), + "🚶🏿‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanWalkingFacingRight, skinTones: [.dark]), + "🚶🏿‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manWalkingFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom4239(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧎🏻‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanKneelingFacingRight, skinTones: [.light]), + "🧎🏻‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manKneelingFacingRight, skinTones: [.light]), + "🧎🏼‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanKneelingFacingRight, skinTones: [.mediumLight]), + "🧎🏼‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manKneelingFacingRight, skinTones: [.mediumLight]), + "🧎🏽‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanKneelingFacingRight, skinTones: [.medium]), + "🧎🏽‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manKneelingFacingRight, skinTones: [.medium]), + "🧎🏾‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanKneelingFacingRight, skinTones: [.mediumDark]), + "🧎🏾‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manKneelingFacingRight, skinTones: [.mediumDark]), + "🧎🏿‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanKneelingFacingRight, skinTones: [.dark]), + "🧎🏿‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manKneelingFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom4771(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨🏻‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .manWithWhiteCaneFacingRight, skinTones: [.light]), + "👩🏻‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .womanWithWhiteCaneFacingRight, skinTones: [.light]), + "👨🏻‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchairFacingRight, skinTones: [.light]), + "👩🏻‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchairFacingRight, skinTones: [.light]), + "👨🏻‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .manInManualWheelchairFacingRight, skinTones: [.light]), + "👩🏻‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchairFacingRight, skinTones: [.light]), + "👨🏼‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .manWithWhiteCaneFacingRight, skinTones: [.mediumLight]), + "👩🏼‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .womanWithWhiteCaneFacingRight, skinTones: [.mediumLight]), + "👨🏼‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchairFacingRight, skinTones: [.mediumLight]), + "👩🏼‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchairFacingRight, skinTones: [.mediumLight]), + "👨🏼‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .manInManualWheelchairFacingRight, skinTones: [.mediumLight]), + "👩🏼‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchairFacingRight, skinTones: [.mediumLight]), + "👨🏽‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .manWithWhiteCaneFacingRight, skinTones: [.medium]), + "👩🏽‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .womanWithWhiteCaneFacingRight, skinTones: [.medium]), + "👨🏽‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchairFacingRight, skinTones: [.medium]), + "👩🏽‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchairFacingRight, skinTones: [.medium]), + "👨🏽‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .manInManualWheelchairFacingRight, skinTones: [.medium]), + "👩🏽‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchairFacingRight, skinTones: [.medium]), + "👨🏾‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .manWithWhiteCaneFacingRight, skinTones: [.mediumDark]), + "👩🏾‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .womanWithWhiteCaneFacingRight, skinTones: [.mediumDark]), + "👨🏾‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchairFacingRight, skinTones: [.mediumDark]), + "👩🏾‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchairFacingRight, skinTones: [.mediumDark]), + "👨🏾‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .manInManualWheelchairFacingRight, skinTones: [.mediumDark]), + "👩🏾‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchairFacingRight, skinTones: [.mediumDark]), + "👨🏿‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .manWithWhiteCaneFacingRight, skinTones: [.dark]), + "👩🏿‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .womanWithWhiteCaneFacingRight, skinTones: [.dark]), + "👨🏿‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchairFacingRight, skinTones: [.dark]), + "👩🏿‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchairFacingRight, skinTones: [.dark]), + "👨🏿‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .manInManualWheelchairFacingRight, skinTones: [.dark]), + "👩🏿‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchairFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom4785(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .personWithWhiteCaneFacingRight, skinTones: [.light]), + "🧑🏻‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchairFacingRight, skinTones: [.light]), + "🧑🏻‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .personInManualWheelchairFacingRight, skinTones: [.light]), + "🧑🏼‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .personWithWhiteCaneFacingRight, skinTones: [.mediumLight]), + "🧑🏼‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchairFacingRight, skinTones: [.mediumLight]), + "🧑🏼‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .personInManualWheelchairFacingRight, skinTones: [.mediumLight]), + "🧑🏽‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .personWithWhiteCaneFacingRight, skinTones: [.medium]), + "🧑🏽‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchairFacingRight, skinTones: [.medium]), + "🧑🏽‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .personInManualWheelchairFacingRight, skinTones: [.medium]), + "🧑🏾‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .personWithWhiteCaneFacingRight, skinTones: [.mediumDark]), + "🧑🏾‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchairFacingRight, skinTones: [.mediumDark]), + "🧑🏾‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .personInManualWheelchairFacingRight, skinTones: [.mediumDark]), + "🧑🏿‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .personWithWhiteCaneFacingRight, skinTones: [.dark]), + "🧑🏿‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchairFacingRight, skinTones: [.dark]), + "🧑🏿‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .personInManualWheelchairFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom4840(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👩‍❤️‍💋‍👨": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: nil), @@ -4409,6 +4655,13 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom5425(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍🧑‍🧒‍🧒": EmojiWithSkinTones(baseEmoji: .familyAdultAdultChildChild, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom6037(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👩🏻‍❤️‍👨🏻": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.light]), @@ -4729,4 +4982,3 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } } -// swiftlint:disable all From 567bade1615ea781e2f202abe6a3b0e66d5200a6 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 18 Sep 2025 14:14:30 +1000 Subject: [PATCH 60/66] Re-apply 'fix/SES-4573/dimiss_keyboard_on_tap_out' changes --- .../ConversationVC+Interaction.swift | 15 ++++++- Session/Conversations/ConversationVC.swift | 16 ++++++- .../Conversations/Input View/InputView.swift | 15 +++++++ .../Message Cells/CallMessageCell.swift | 20 ++++----- .../Message Cells/InfoMessageCell.swift | 22 +++++----- .../Message Cells/MessageCell.swift | 42 ++++++++++++++++++- .../Message Cells/VisibleMessageCell.swift | 32 ++++++-------- 7 files changed, 116 insertions(+), 46 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index da4ac06010..ad0541a19d 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -22,7 +22,8 @@ extension ConversationVC: ContextMenuActionDelegate, SendMediaNavDelegate, AttachmentApprovalViewControllerDelegate, - GifPickerViewControllerDelegate + GifPickerViewControllerDelegate, + UIGestureRecognizerDelegate { // MARK: - Open Settings @@ -33,6 +34,11 @@ extension ConversationVC: openSettingsFromTitleView() } + // Handle taps outside of tableview cell to dismiss keyboard + @MainActor @objc func dismissKeyboardOnTap() { + _ = self.snInputView.resignFirstResponder() + } + @MainActor func openSettingsFromTitleView() { // If we shouldn't be able to access settings then disable the title view shortcuts guard viewModel.threadData.canAccessSettings(using: viewModel.dependencies) else { return } @@ -254,6 +260,11 @@ extension ConversationVC: return true } + + // MARK: - UIGestureRecognizerDelegate + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } // MARK: - SendMediaNavDelegate @@ -1063,7 +1074,7 @@ extension ConversationVC: } // MARK: MessageCellDelegate - + func handleItemLongPressed(_ cellViewModel: MessageViewModel) { // Show the unblock modal if needed guard self.viewModel.threadData.threadIsBlocked != true else { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index fd52a451dc..0b13d35bbd 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -386,6 +386,16 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa return result }() + + // Handle taps outside of tableview cell + private lazy var tableViewTapGesture: UITapGestureRecognizer = { + let result: UITapGestureRecognizer = UITapGestureRecognizer() + result.delegate = self + result.addTarget(self, action: #selector(dismissKeyboardOnTap)) + result.cancelsTouchesInView = false + + return result + }() // MARK: - Settings @@ -537,6 +547,9 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa object: nil ) } + + // Gesture + view.addGestureRecognizer(tableViewTapGesture) self.viewModel.navigatableState.setupBindings(viewController: self, disposables: &self.viewModel.disposables) @@ -1580,7 +1593,8 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa // value will break things) let tableViewBottom: CGFloat = (tableView.contentSize.height - tableView.bounds.height + tableView.contentInset.bottom) - if tableView.contentOffset.y < (tableViewBottom - 5) { + // Added `insetDifference > 0` to remove sudden table collapse and overscroll + if tableView.contentOffset.y < (tableViewBottom - 5) && insetDifference > 0 { tableView.contentOffset.y += insetDifference } diff --git a/Session/Conversations/Input View/InputView.swift b/Session/Conversations/Input View/InputView.swift index 74316f9525..b75531a7e0 100644 --- a/Session/Conversations/Input View/InputView.swift +++ b/Session/Conversations/Input View/InputView.swift @@ -62,6 +62,15 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M return result }() + private lazy var swipeGestureRecognizer: UISwipeGestureRecognizer = { + let result: UISwipeGestureRecognizer = UISwipeGestureRecognizer() + result.direction = .down + result.addTarget(self, action: #selector(didSwipeDown)) + result.cancelsTouchesInView = false + + return result + }() + private var bottomStackView: UIStackView? private lazy var attachmentsButton: ExpandingAttachmentsButton = { let result = ExpandingAttachmentsButton(delegate: delegate) @@ -227,6 +236,7 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M autoresizingMask = .flexibleHeight addGestureRecognizer(tapGestureRecognizer) + addGestureRecognizer(swipeGestureRecognizer) // Background & blur let backgroundView = UIView() @@ -454,6 +464,7 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M self.accessibilityIdentifier = updatedInputState.accessibility?.identifier self.accessibilityLabel = updatedInputState.accessibility?.label tapGestureRecognizer.isEnabled = (updatedInputState.allowedInputTypes == .none) + inputState = updatedInputState disabledInputLabel.text = (updatedInputState.message ?? "") disabledInputLabel.accessibilityIdentifier = updatedInputState.messageAccessibility?.identifier @@ -630,6 +641,10 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M @objc private func characterLimitLabelTapped() { delegate?.handleCharacterLimitLabelTapped() } + + @objc private func didSwipeDown() { + inputTextView.resignFirstResponder() + } // MARK: - Convenience diff --git a/Session/Conversations/Message Cells/CallMessageCell.swift b/Session/Conversations/Message Cells/CallMessageCell.swift index de80fef7c1..47dca8d33b 100644 --- a/Session/Conversations/Message Cells/CallMessageCell.swift +++ b/Session/Conversations/Message Cells/CallMessageCell.swift @@ -18,6 +18,13 @@ final class CallMessageCell: MessageCell { override var contextSnapshotView: UIView? { return container } + override var allowedGestureRecognizers: Set { + return [ + .longPress, + .tap + ] + } + // MARK: - UI private lazy var topConstraint: NSLayoutConstraint = mainStackView.pin(.top, to: .top, of: self, withInset: CallMessageCell.inset) @@ -115,15 +122,6 @@ final class CallMessageCell: MessageCell { mainStackView.pin(.bottom, to: .bottom, of: self, withInset: -CallMessageCell.inset) } - override func setUpGestureRecognizers() { - let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - addGestureRecognizer(longPressRecognizer) - - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - tapGestureRecognizer.numberOfTapsRequired = 1 - addGestureRecognizer(tapGestureRecognizer) - } - // MARK: - Updating override func update( @@ -207,7 +205,7 @@ final class CallMessageCell: MessageCell { // MARK: - Interaction - @objc func handleLongPress(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { if [ .ended, .cancelled, .failed ].contains(gestureRecognizer.state) { isHandlingLongPress = false return @@ -218,7 +216,7 @@ final class CallMessageCell: MessageCell { isHandlingLongPress = true } - @objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { guard let dependencies: Dependencies = self.dependencies, let cellViewModel: MessageViewModel = self.viewModel, diff --git a/Session/Conversations/Message Cells/InfoMessageCell.swift b/Session/Conversations/Message Cells/InfoMessageCell.swift index 9c626a90c5..a5196a0b88 100644 --- a/Session/Conversations/Message Cells/InfoMessageCell.swift +++ b/Session/Conversations/Message Cells/InfoMessageCell.swift @@ -13,6 +13,13 @@ final class InfoMessageCell: MessageCell { override var contextSnapshotView: UIView? { return label } + override var allowedGestureRecognizers: Set { + return [ + .longPress, + .tap + ] + } + // MARK: - UI private lazy var iconContainerViewWidthConstraint = iconContainerView.set(.width, to: InfoMessageCell.iconSize) @@ -77,15 +84,6 @@ final class InfoMessageCell: MessageCell { stackView.pin(.right, to: .right, of: self, withInset: -Values.massiveSpacing) stackView.pin(.bottom, to: .bottom, of: self, withInset: -InfoMessageCell.inset) } - - override func setUpGestureRecognizers() { - let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - addGestureRecognizer(longPressRecognizer) - - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - tapGestureRecognizer.numberOfTapsRequired = 1 - addGestureRecognizer(tapGestureRecognizer) - } // MARK: - Updating @@ -169,7 +167,7 @@ final class InfoMessageCell: MessageCell { // MARK: - Interaction - @objc func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { + override func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { if [ .ended, .cancelled, .failed ].contains(gestureRecognizer.state) { isHandlingLongPress = false return @@ -180,9 +178,9 @@ final class InfoMessageCell: MessageCell { isHandlingLongPress = true } - @objc func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { guard let cellViewModel: MessageViewModel = self.viewModel else { return } - + if cellViewModel.variant == .infoDisappearingMessagesUpdate && cellViewModel.canDoFollowingSetting() { delegate?.handleItemTapped(cellViewModel, cell: self, cellLocation: gestureRecognizer.location(in: self)) } diff --git a/Session/Conversations/Message Cells/MessageCell.swift b/Session/Conversations/Message Cells/MessageCell.swift index 39730a9d6c..ec40f8e72e 100644 --- a/Session/Conversations/Message Cells/MessageCell.swift +++ b/Session/Conversations/Message Cells/MessageCell.swift @@ -10,11 +10,16 @@ public enum SwipeState { case cancelled } +public enum GestureRecognizerType { + case tap, longPress, doubleTap +} + public class MessageCell: UITableViewCell { var dependencies: Dependencies? var viewModel: MessageViewModel? weak var delegate: MessageCellDelegate? open var contextSnapshotView: UIView? { return nil } + open var allowedGestureRecognizers: Set { return [] } // Override to have gestures // MARK: - Lifecycle @@ -41,7 +46,32 @@ public class MessageCell: UITableViewCell { } func setUpGestureRecognizers() { - // To be overridden by subclasses + var tapGestureRecognizer: UITapGestureRecognizer? + var doubleTapGestureRecognizer: UITapGestureRecognizer? + + if allowedGestureRecognizers.contains(.tap) { + let tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) + tapGesture.numberOfTapsRequired = 1 + addGestureRecognizer(tapGesture) + tapGestureRecognizer = tapGesture + } + + if allowedGestureRecognizers.contains(.doubleTap) { + let doubleTapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap)) + doubleTapGesture.numberOfTapsRequired = 2 + addGestureRecognizer(doubleTapGesture) + doubleTapGestureRecognizer = doubleTapGesture + } + + if allowedGestureRecognizers.contains(.longPress) { + let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) + addGestureRecognizer(longPressGesture) + } + + // If we have both tap and double tap gestures then the single tap should fail if a double tap occurs + if let tapGesture: UITapGestureRecognizer = tapGestureRecognizer, let doubleTapGesture: UITapGestureRecognizer = doubleTapGestureRecognizer { + tapGesture.require(toFail: doubleTapGesture) + } } // MARK: - Updating @@ -93,6 +123,16 @@ public class MessageCell: UITableViewCell { return CallMessageCell.self } } + + // MARK: - Gesture events + @objc + func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {} + + @objc + func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {} + + @objc + func handleDoubleTap() {} } // MARK: - MessageCellDelegate diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 21a9517955..0dbde5e460 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -25,6 +25,14 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { override var contextSnapshotView: UIView? { return snContentView } + override var allowedGestureRecognizers: Set { + return [ + .tap, + .longPress, + .doubleTap + ] + } + // Constraints internal lazy var authorLabelTopConstraint = authorLabel.pin(.top, to: .top, of: self) private lazy var authorLabelHeightConstraint = authorLabel.set(.height, to: 0) @@ -270,21 +278,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { messageStatusLabelPaddingView.pin(.leading, to: .leading, of: messageStatusContainerView) messageStatusLabelPaddingView.pin(.trailing, to: .trailing, of: messageStatusContainerView) } - - override func setUpGestureRecognizers() { - let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - addGestureRecognizer(longPressRecognizer) - - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - tapGestureRecognizer.numberOfTapsRequired = 1 - addGestureRecognizer(tapGestureRecognizer) - - let doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap)) - doubleTapGestureRecognizer.numberOfTapsRequired = 2 - addGestureRecognizer(doubleTapGestureRecognizer) - tapGestureRecognizer.require(toFail: doubleTapGestureRecognizer) - } - + // MARK: - Updating override func update( @@ -968,7 +962,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { } } - @objc func handleLongPress(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { if [ .ended, .cancelled, .failed ].contains(gestureRecognizer.state) { isHandlingLongPress = false return @@ -994,9 +988,9 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { isHandlingLongPress = true } - @objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { guard let cellViewModel: MessageViewModel = self.viewModel else { return } - + let location = gestureRecognizer.location(in: self) if profilePictureView.bounds.contains(profilePictureView.convert(location, from: self)), cellViewModel.shouldShowProfile { @@ -1062,7 +1056,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { } } - @objc private func handleDoubleTap() { + override func handleDoubleTap() { guard let cellViewModel: MessageViewModel = self.viewModel else { return } delegate?.handleItemDoubleTapped(cellViewModel) From 6385830eefa593066accfcc0325502cad168bbdf Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 18 Sep 2025 14:17:26 +1000 Subject: [PATCH 61/66] Re-apply 'feat/SES-3085/remove_authentication_on_default_rooms' changes --- .../Jobs/DisplayPictureDownloadJob.swift | 18 ++-- .../RetrieveDefaultOpenGroupRoomsJob.swift | 1 + ...RetrieveDefaultOpenGroupRoomsJobSpec.swift | 3 + SessionNetworkingKit/SOGS/SOGSAPI.swift | 42 +++++--- .../SOGS/SOGSAPISpec.swift | 97 +++++++++++++++++++ 5 files changed, 142 insertions(+), 19 deletions(-) diff --git a/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift b/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift index dac761d872..edae3c0108 100644 --- a/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift +++ b/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift @@ -48,7 +48,7 @@ public enum DisplayPictureDownloadJob: JobExecutor { using: dependencies ) - case .community(let fileId, let roomToken, let server): + case .community(let fileId, let roomToken, let server, let skipAuthentication): guard let info: LibSession.OpenGroupCapabilityInfo = try? LibSession.OpenGroupCapabilityInfo .fetchOne(db, id: OpenGroup.idFor(roomToken: roomToken, server: server)) @@ -58,6 +58,7 @@ public enum DisplayPictureDownloadJob: JobExecutor { fileId: fileId, roomToken: roomToken, authMethod: Authentication.community(info: info), + skipAuthentication: skipAuthentication, using: dependencies ) } @@ -206,7 +207,7 @@ public enum DisplayPictureDownloadJob: JobExecutor { ) db.addConversationEvent(id: id, type: .updated(.displayPictureUrl(url))) - case .community(_, let roomToken, let server): + case .community(_, let roomToken, let server, _): _ = try? OpenGroup .filter(id: OpenGroup.idFor(roomToken: roomToken, server: server)) .updateAllAndConfig( @@ -228,7 +229,7 @@ extension DisplayPictureDownloadJob { public enum Target: Codable, Hashable, CustomStringConvertible { case profile(id: String, url: String, encryptionKey: Data) case group(id: String, url: String, encryptionKey: Data) - case community(imageId: String, roomToken: String, server: String) + case community(imageId: String, roomToken: String, server: String, skipAuthentication: Bool = false) var isValid: Bool { switch self { @@ -239,7 +240,7 @@ extension DisplayPictureDownloadJob { encryptionKey.count == DisplayPictureManager.aes256KeyByteLength ) - case .community(let imageId, _, _): return !imageId.isEmpty + case .community(let imageId, _, _, _): return !imageId.isEmpty } } @@ -249,7 +250,7 @@ extension DisplayPictureDownloadJob { switch self { case .profile(let id, _, _): return "profile: \(id)" case .group(let id, _, _): return "group: \(id)" - case .community(_, let roomToken, let server): return "room: \(roomToken) on server: \(server)" + case .community(_, let roomToken, let server, _): return "room: \(roomToken) on server: \(server)" } } } @@ -274,11 +275,12 @@ extension DisplayPictureDownloadJob { self.target = { switch target { - case .community(let imageId, let roomToken, let server): + case .community(let imageId, let roomToken, let server, let skipAuthentication): return .community( imageId: imageId, roomToken: roomToken, - server: server.lowercased() // Always in lowercase on `OpenGroup` + server: server.lowercased(), // Always in lowercase on `OpenGroup` + skipAuthentication: skipAuthentication ) default: return target @@ -358,7 +360,7 @@ extension DisplayPictureDownloadJob { return (url == latestDisplayPictureUrl) - case .community(let imageId, let roomToken, let server): + case .community(let imageId, let roomToken, let server, _): guard let latestImageId: String = try? OpenGroup .select(.imageId) diff --git a/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift b/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift index c5dbbd97ef..e34c48694b 100644 --- a/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift +++ b/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift @@ -72,6 +72,7 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { .tryFlatMap { [dependencies] authMethod -> AnyPublisher<(ResponseInfoType, Network.SOGS.CapabilitiesAndRoomsResponse), Error> in try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: authMethod, + skipAuthentication: true, using: dependencies ).send(using: dependencies) } diff --git a/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift b/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift index 3ab3ae81bf..42ff8e59cc 100644 --- a/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift +++ b/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift @@ -265,6 +265,7 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { ), forceBlinded: false ), + skipAuthentication: true, using: dependencies ) } @@ -286,6 +287,8 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { requestAndPathBuildTimeout: expectedRequest.requestAndPathBuildTimeout ) }) + + expect(expectedRequest?.headers).to(beEmpty()) } // MARK: -- will retry 8 times before it fails diff --git a/SessionNetworkingKit/SOGS/SOGSAPI.swift b/SessionNetworkingKit/SOGS/SOGSAPI.swift index 4898a6202c..981058ae32 100644 --- a/SessionNetworkingKit/SOGS/SOGSAPI.swift +++ b/SessionNetworkingKit/SOGS/SOGSAPI.swift @@ -156,9 +156,10 @@ public extension Network.SOGS { private static func preparedSequence( requests: [any ErasedPreparedRequest], authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest> { - return try Network.PreparedRequest( + let preparedRequest = try Network.PreparedRequest( request: Request( method: .post, endpoint: Endpoint.sequence, @@ -170,7 +171,10 @@ public extension Network.SOGS { using: dependencies ) - return (skipAuthentication ? preparedRequest : try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies)) + return (skipAuthentication ? + preparedRequest : + try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) + ) } // MARK: - Capabilities @@ -184,6 +188,7 @@ public extension Network.SOGS { /// could return: `{"capabilities": ["sogs", "batch"], "missing": ["magic"]}` static func preparedCapabilities( authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest { let preparedRequest = try Network.PreparedRequest( @@ -196,7 +201,10 @@ public extension Network.SOGS { using: dependencies ) - return (skipAuthentication ? preparedRequest : try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies)) + return (skipAuthentication ? + preparedRequest : + try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) + ) } // MARK: - Room @@ -206,9 +214,10 @@ public extension Network.SOGS { /// Rooms to which the user does not have access (e.g. because they are banned, or the room has restricted access permissions) are not included static func preparedRooms( authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest<[Room]> { - return try Network.PreparedRequest( + let preparedRequest = try Network.PreparedRequest( request: Request( endpoint: .rooms, authMethod: authMethod @@ -218,7 +227,10 @@ public extension Network.SOGS { using: dependencies ) - return (skipAuthentication ? preparedRequest : try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies)) + return (skipAuthentication ? + preparedRequest : + try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) + ) } /// Returns the details of a single room @@ -320,6 +332,7 @@ public extension Network.SOGS { /// methods for the documented behaviour of each method static func preparedCapabilitiesAndRooms( authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest { let preparedRequest = try Network.SOGS @@ -327,14 +340,17 @@ public extension Network.SOGS { requests: [ // Get the latest capabilities for the server (in case it's a new server or the // cached ones are stale) - preparedCapabilities(authMethod: authMethod, using: dependencies), - preparedRooms(authMethod: authMethod, using: dependencies) + preparedCapabilities(authMethod: authMethod, skipAuthentication: skipAuthentication, using: dependencies), + preparedRooms(authMethod: authMethod, skipAuthentication: skipAuthentication, using: dependencies) ], authMethod: authMethod, + skipAuthentication: skipAuthentication, using: dependencies ) - - let finalRequest = (skipAuthentication ? preparedRequest : try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies)) + let finalRequest = (skipAuthentication ? + preparedRequest : + try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) + ) return finalRequest .tryMap { (info: ResponseInfoType, response: Network.BatchResponseMap) -> CapabilitiesAndRoomsResponse in @@ -841,9 +857,10 @@ public extension Network.SOGS { fileId: String, roomToken: String, authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( + let preparedRequest = try Network.PreparedRequest( request: Request( endpoint: .roomFileIndividual(roomToken, fileId), authMethod: authMethod @@ -854,7 +871,10 @@ public extension Network.SOGS { using: dependencies ) - return (skipAuthentication ? preparedRequest : try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies)) + return (skipAuthentication ? + preparedRequest : + try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) + ) } // MARK: - Inbox/Outbox (Message Requests) diff --git a/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift b/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift index 877b6f31da..627199f818 100644 --- a/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift +++ b/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift @@ -751,6 +751,44 @@ class SOGSAPISpec: QuickSpec { expect(preparedRequest?.path).to(equal("/sequence")) expect(preparedRequest?.method.rawValue).to(equal("POST")) + + expect(preparedRequest?.headers).toNot(beEmpty()) + expect(preparedRequest?.headers).to(equal([ + HTTPHeader.sogsNonce: "pK6YRtQApl4NhECGizF0Cg==", + HTTPHeader.sogsTimestamp: "1234567890", + HTTPHeader.sogsSignature: "VGVzdFNvZ3NTaWduYXR1cmU=", + HTTPHeader.sogsPubKey: "1588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b" + ])) + } + + // MARK: ---- generates the request correctly and skips adding request headers + it("generates the request correctly and skips adding request headers") { + expect { + preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRooms( + authMethod: Authentication.community( + info: LibSession.OpenGroupCapabilityInfo( + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + capabilities: [] + ), + forceBlinded: false + ), + skipAuthentication: true, + using: dependencies + ) + }.toNot(throwError()) + + expect(preparedRequest?.batchEndpoints.count).to(equal(2)) + expect(preparedRequest?.batchEndpoints[test: 0].asType(OpenGroupAPI.Endpoint.self)) + .to(equal(.capabilities)) + expect(preparedRequest?.batchEndpoints[test: 1].asType(OpenGroupAPI.Endpoint.self)) + .to(equal(.rooms)) + + expect(preparedRequest?.path).to(equal("/sequence")) + expect(preparedRequest?.method.rawValue).to(equal("POST")) + + expect(preparedRequest?.headers).to(beEmpty()) } // MARK: ---- processes a valid response correctly @@ -1578,6 +1616,31 @@ class SOGSAPISpec: QuickSpec { ])) } + // MARK: ---- generates the download destination correctly when given an id and skips adding request headers + it("generates the download destination correctly when given an id and skips adding request headers") { + expect { + preparedRequest = try OpenGroupAPI.preparedDownload( + fileId: "1", + roomToken: "roomToken", + authMethod: Authentication.community( + info: LibSession.OpenGroupCapabilityInfo( + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + capabilities: [] + ), + forceBlinded: false + ), + skipAuthentication: true, + using: dependencies + ) + }.toNot(throwError()) + + expect(preparedRequest?.path).to(equal("/room/roomToken/file/1")) + expect(preparedRequest?.method.rawValue).to(equal("GET")) + expect(preparedRequest?.headers).to(beEmpty()) + } + // MARK: ---- generates the download request correctly when given a URL it("generates the download request correctly when given a URL") { expect { @@ -2203,6 +2266,40 @@ class SOGSAPISpec: QuickSpec { .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) + + expect(preparedRequest?.headers).toNot(beEmpty()) + + expect(response).toNot(beNil()) + expect(error).to(beNil()) + } + + // MARK: ---- triggers sending correctly without headers + it("triggers sending correctly without headers") { + var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])? + + expect { + preparedRequest = try OpenGroupAPI.preparedRooms( + authMethod: Authentication.community( + info: LibSession.OpenGroupCapabilityInfo( + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + capabilities: [] + ), + forceBlinded: false + ), + skipAuthentication: true, + using: dependencies + ) + }.toNot(throwError()) + + preparedRequest? + .send(using: dependencies) + .handleEvents(receiveOutput: { result in response = result }) + .mapError { error.setting(to: $0) } + .sinkAndStore(in: &disposables) + + expect(preparedRequest?.headers).to(beEmpty()) expect(response).toNot(beNil()) expect(error).to(beNil()) From a7e137535e6dff3922b3c1558798e4ff81352cd9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 18 Sep 2025 14:22:07 +1000 Subject: [PATCH 62/66] Fixed broken tests --- .../SOGS/SOGSAPISpec.swift | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift b/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift index 627199f818..342be00b57 100644 --- a/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift +++ b/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift @@ -764,14 +764,13 @@ class SOGSAPISpec: QuickSpec { // MARK: ---- generates the request correctly and skips adding request headers it("generates the request correctly and skips adding request headers") { expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRooms( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), skipAuthentication: true, @@ -780,9 +779,9 @@ class SOGSAPISpec: QuickSpec { }.toNot(throwError()) expect(preparedRequest?.batchEndpoints.count).to(equal(2)) - expect(preparedRequest?.batchEndpoints[test: 0].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 0].asType(Network.SOGS.Endpoint.self)) .to(equal(.capabilities)) - expect(preparedRequest?.batchEndpoints[test: 1].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 1].asType(Network.SOGS.Endpoint.self)) .to(equal(.rooms)) expect(preparedRequest?.path).to(equal("/sequence")) @@ -1619,16 +1618,15 @@ class SOGSAPISpec: QuickSpec { // MARK: ---- generates the download destination correctly when given an id and skips adding request headers it("generates the download destination correctly when given an id and skips adding request headers") { expect { - preparedRequest = try OpenGroupAPI.preparedDownload( + preparedRequest = try Network.SOGS.preparedDownload( fileId: "1", roomToken: "roomToken", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), skipAuthentication: true, @@ -2275,17 +2273,16 @@ class SOGSAPISpec: QuickSpec { // MARK: ---- triggers sending correctly without headers it("triggers sending correctly without headers") { - var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])? + var response: (info: ResponseInfoType, data: [Network.SOGS.Room])? expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), skipAuthentication: true, From da9d9254bccdfc652673ac343d0554d2e34e1c40 Mon Sep 17 00:00:00 2001 From: Bilb <1544279+Bilb@users.noreply.github.com> Date: Thu, 18 Sep 2025 08:09:33 +0000 Subject: [PATCH 63/66] [Automated] Update translations from Crowdin --- .../Meta/Translations/Localizable.xcstrings | 244 +++++++++++++++++- 1 file changed, 243 insertions(+), 1 deletion(-) diff --git a/Session/Meta/Translations/Localizable.xcstrings b/Session/Meta/Translations/Localizable.xcstrings index 31da116616..e1b245a269 100644 --- a/Session/Meta/Translations/Localizable.xcstrings +++ b/Session/Meta/Translations/Localizable.xcstrings @@ -84364,6 +84364,39 @@ } } }, + "checkingProStatus" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Checking {pro} Status" + } + } + } + }, + "checkingProStatusDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Checking your {pro} details. Some information on this page may be inaccurate until this check is complete." + } + } + } + }, + "checkingProStatusUpgradeDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Checking your {pro} status. You'll be able to upgrade to {pro} once this check is complete." + } + } + } + }, "clear" : { "extractionState" : "manual", "localizations" : { @@ -189073,6 +189106,17 @@ } } }, + "errorCheckingProStatus" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error checking {pro} status." + } + } + } + }, "errorConnection" : { "extractionState" : "manual", "localizations" : { @@ -190671,6 +190715,17 @@ } } }, + "errorLoadingProPlan" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error loading {pro} plan." + } + } + } + }, "errorUnknown" : { "extractionState" : "manual", "localizations" : { @@ -354919,7 +354974,7 @@ "az" : { "stringUnit" : { "state" : "translated", - "value" : "Hazırkı planınızda artıq\r\ntam {app_pro} qiymətinin {percent}% endirimi mövcuddur." + "value" : "Hazırkı planınızda artıq tam {app_pro} qiymətinin {percent}% endirimi mövcuddur." } }, "cs" : { @@ -354948,6 +355003,17 @@ } } }, + "proErrorRefreshingStatus" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error refreshing {pro} status" + } + } + } + }, "proExpired" : { "extractionState" : "manual", "localizations" : { @@ -362239,6 +362305,17 @@ } } }, + "proPlanError" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} Plan Error" + } + } + } + }, "proPlanExpireDate" : { "extractionState" : "manual", "localizations" : { @@ -362274,6 +362351,50 @@ } } }, + "proPlanLoading" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} Plan Loading" + } + } + } + }, + "proPlanLoadingDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Information about your {pro} plan is still being loaded. You cannot update your plan until this process is complete." + } + } + } + }, + "proPlanLoadingEllipsis" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} plan loading..." + } + } + } + }, + "proPlanNetworkLoadError" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unable to connect to the network to load your current plan. Updating your plan via {app_name} will be disabled until connectivity is restored.

Please check your network connection and retry." + } + } + } + }, "proPlanNotFound" : { "extractionState" : "manual", "localizations" : { @@ -363248,6 +363369,28 @@ } } }, + "proStatsLoading" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} Stats Loading" + } + } + } + }, + "proStatsLoadingDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your {pro} stats are loading, please wait." + } + } + } + }, "proStatsTooltip" : { "extractionState" : "manual", "localizations" : { @@ -363283,6 +363426,83 @@ } } }, + "proStatusError" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} Status Error" + } + } + } + }, + "proStatusInfoInaccurateNetworkError" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unable to connect to the network to check your {pro} status. Information displayed on this page may be inaccurate until connectivity is restored.

Please check your network connection and retry." + } + } + } + }, + "proStatusLoading" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} Status Loading" + } + } + } + }, + "proStatusLoadingDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your {pro} information is being loaded. Some actions on this page may be unavailable until loading is complete." + } + } + } + }, + "proStatusLoadingSubtitle" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} status loading" + } + } + } + }, + "proStatusNetworkErrorDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unable to connect to the network to check your {pro} status. You cannot upgrade to {pro} until connectivity is restored.

Please check your network connection and retry." + } + } + } + }, + "proStatusRefreshNetworkError" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unable to connect to the network to refresh your {pro} status. Some actions on this page will be disabled until connectivity is restored.

Please check your network connection and retry." + } + } + } + }, "proSupportDescription" : { "extractionState" : "manual", "localizations" : { @@ -415000,6 +415220,17 @@ } } }, + "unsupportedCpu" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unsupported CPU" + } + } + } + }, "updateApp" : { "extractionState" : "manual", "localizations" : { @@ -428707,6 +428938,17 @@ } } }, + "yourCpuIsUnsupportedSSE42" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your CPU does not support SSE 4.2, which is required for Session to run on Linux x64. Please upgrade your CPU or use a different operating system." + } + } + } + }, "yourRecoveryPassword" : { "extractionState" : "manual", "localizations" : { From 6ed6aec702fe7ccf9b938be5bbc347b90d303945 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 19 Sep 2025 10:58:13 +1000 Subject: [PATCH 64/66] Fixed a couple of layout issues found by the automated tests --- Session/Settings/Views/NewTagView.swift | 2 +- Session/Shared/Views/SessionCell+AccessoryView.swift | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Session/Settings/Views/NewTagView.swift b/Session/Settings/Views/NewTagView.swift index 5156a4885a..416f0ae49b 100644 --- a/Session/Settings/Views/NewTagView.swift +++ b/Session/Settings/Views/NewTagView.swift @@ -34,7 +34,7 @@ final class NewTagView: UIView { private func setupUI() { addSubview(newTagLabel) - newTagLabel.pin(.leading, to: .leading, of: self, withInset: -(Values.mediumSpacing + Values.verySmallSpacing)) + newTagLabel.pin(.leading, to: .leading, of: self, withInset: -Values.mediumSmallSpacing) newTagLabel.pin([ UIView.VerticalEdge.top, UIView.VerticalEdge.bottom, UIView.HorizontalEdge.trailing ], to: self) } diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index 3b09865e9f..ef3e66cf9e 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -782,10 +782,7 @@ extension SessionCell { minWidthConstraint.isActive = true } - view.pin(.top, to: .top, of: self) - view.pin(.bottom, to: .bottom, of: self) - view.pin(.leading, to: .leading, of: self, withInset: Values.smallSpacing) - view.pin(.trailing, to: .trailing, of: self, withInset: -Values.smallSpacing) + view.pin(to: self) } private func configureCustomView(_ view: UIView?, _ accessory: SessionCell.AccessoryConfig.AnyCustom) { From 6895c147cab8e75be910bf7e8c6a1c8d51b249a3 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 19 Sep 2025 11:03:03 +1000 Subject: [PATCH 65/66] Bumped build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 4982853375..2ab6a4f486 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -1040,11 +1040,11 @@ FDE521A22E0D23AB00061B8E /* ObservableKey+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE521A12E0D23A200061B8E /* ObservableKey+SessionMessagingKit.swift */; }; FDE521A62E0E6C8C00061B8E /* MockNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F5C2CAA28CF00C0B51B /* MockNotificationsManager.swift */; }; FDE6E99829F8E63A00F93C5D /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE6E99729F8E63A00F93C5D /* Accessibility.swift */; }; + FDE71B052E77E1AA0023F5F9 /* ObservableKey+SessionNetworkingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE71B042E77E1A00023F5F9 /* ObservableKey+SessionNetworkingKit.swift */; }; FDE71B0B2E79352D0023F5F9 /* DeveloperSettingsGroupsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE71B0A2E7935250023F5F9 /* DeveloperSettingsGroupsViewModel.swift */; }; FDE71B0D2E793B250023F5F9 /* DeveloperSettingsProViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE71B0C2E793B1F0023F5F9 /* DeveloperSettingsProViewModel.swift */; }; FDE71B0F2E7A195B0023F5F9 /* Session - Anonymous Messenger.storekit in Resources */ = {isa = PBXBuildFile; fileRef = FDE71B0E2E7A195B0023F5F9 /* Session - Anonymous Messenger.storekit */; }; FDE71B5F2E7A73570023F5F9 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDE71B5E2E7A73560023F5F9 /* StoreKit.framework */; }; - FDE71B052E77E1AA0023F5F9 /* ObservableKey+SessionNetworkingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE71B042E77E1A00023F5F9 /* ObservableKey+SessionNetworkingKit.swift */; }; FDE7549B2C940108002A2623 /* MessageViewModel+DeletionActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7549A2C940108002A2623 /* MessageViewModel+DeletionActions.swift */; }; FDE7549D2C9961A4002A2623 /* CommunityPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7549C2C9961A4002A2623 /* CommunityPoller.swift */; }; FDE754A12C9A60A6002A2623 /* Crypto+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A02C9A60A6002A2623 /* Crypto+OpenGroup.swift */; }; @@ -2310,11 +2310,11 @@ FDE5219F2E0D22FD00061B8E /* ObservationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservationManager.swift; sourceTree = ""; }; FDE521A12E0D23A200061B8E /* ObservableKey+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObservableKey+SessionMessagingKit.swift"; sourceTree = ""; }; FDE6E99729F8E63A00F93C5D /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = ""; }; + FDE71B042E77E1A00023F5F9 /* ObservableKey+SessionNetworkingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObservableKey+SessionNetworkingKit.swift"; sourceTree = ""; }; FDE71B0A2E7935250023F5F9 /* DeveloperSettingsGroupsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsGroupsViewModel.swift; sourceTree = ""; }; FDE71B0C2E793B1F0023F5F9 /* DeveloperSettingsProViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsProViewModel.swift; sourceTree = ""; }; FDE71B0E2E7A195B0023F5F9 /* Session - Anonymous Messenger.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Session - Anonymous Messenger.storekit"; sourceTree = ""; }; FDE71B5E2E7A73560023F5F9 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; - FDE71B042E77E1A00023F5F9 /* ObservableKey+SessionNetworkingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObservableKey+SessionNetworkingKit.swift"; sourceTree = ""; }; FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = ""; }; FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintLocalizableStrings.swift; sourceTree = ""; }; FDE7549A2C940108002A2623 /* MessageViewModel+DeletionActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageViewModel+DeletionActions.swift"; sourceTree = ""; }; @@ -8320,7 +8320,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COMPILE_LIB_SESSION = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 632; + CURRENT_PROJECT_VERSION = 633; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -8401,7 +8401,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; COMPILE_LIB_SESSION = ""; - CURRENT_PROJECT_VERSION = 632; + CURRENT_PROJECT_VERSION = 633; ENABLE_BITCODE = NO; ENABLE_MODULE_VERIFIER = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -8882,7 +8882,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COMPILE_LIB_SESSION = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 632; + CURRENT_PROJECT_VERSION = 633; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -9469,7 +9469,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; COMPILE_LIB_SESSION = YES; - CURRENT_PROJECT_VERSION = 632; + CURRENT_PROJECT_VERSION = 633; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; From fc7fc4789ae65f3f0788daf10239a2a46f7b17f7 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 19 Sep 2025 11:11:26 +1000 Subject: [PATCH 66/66] Fixed a bug where deleting a conversation would delete the contact --- Session/Utilities/UIContextualAction+Utilities.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Session/Utilities/UIContextualAction+Utilities.swift b/Session/Utilities/UIContextualAction+Utilities.swift index 7e30326c7e..c0edbc3b20 100644 --- a/Session/Utilities/UIContextualAction+Utilities.swift +++ b/Session/Utilities/UIContextualAction+Utilities.swift @@ -692,7 +692,11 @@ public extension UIContextualAction { return .deleteGroupAndContent case (.group, _, _): return .leaveGroupAsync - case (.contact, _, _): return .deleteContactConversationAndContact + case (.contact, true, _): + return .deleteContactConversationAndContact + + case (.contact, false, _): + return .deleteContactConversationAndMarkHidden } }()