From 4330a40f6f725b5921bb9f8eff6cbf4f0d523ad5 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 19 May 2023 17:26:14 +1000 Subject: [PATCH 01/17] Started working on integrating the updated push APIs Updated the PushNotificationAPI to be more consistent with the SnodeAPI and OpenGroupAPI structures Updated the logic so if the database key can't be retrieved the app will no longer throw a fatalError (now just fail to initialise Storage and rely on the App/Extensions to properly handle this case) Fixed a couple of bugs where the share extension wouldn't populate correctly --- Session.xcodeproj/project.pbxproj | 52 +- Session/Meta/AppDelegate.swift | 17 +- .../Translations/de.lproj/Localizable.strings | 2 + .../Translations/en.lproj/Localizable.strings | 2 + .../Translations/es.lproj/Localizable.strings | 2 + .../Translations/fa.lproj/Localizable.strings | 2 + .../Translations/fi.lproj/Localizable.strings | 2 + .../Translations/fr.lproj/Localizable.strings | 2 + .../Translations/hi.lproj/Localizable.strings | 2 + .../Translations/hr.lproj/Localizable.strings | 2 + .../id-ID.lproj/Localizable.strings | 2 + .../Translations/it.lproj/Localizable.strings | 2 + .../Translations/ja.lproj/Localizable.strings | 2 + .../Translations/nl.lproj/Localizable.strings | 2 + .../Translations/pl.lproj/Localizable.strings | 2 + .../pt_BR.lproj/Localizable.strings | 2 + .../Translations/ru.lproj/Localizable.strings | 2 + .../Translations/si.lproj/Localizable.strings | 2 + .../Translations/sk.lproj/Localizable.strings | 2 + .../Translations/sv.lproj/Localizable.strings | 2 + .../Translations/th.lproj/Localizable.strings | 2 + .../vi-VN.lproj/Localizable.strings | 2 + .../zh-Hant.lproj/Localizable.strings | 2 + .../zh_CN.lproj/Localizable.strings | 2 + Session/Notifications/SyncPushTokensJob.swift | 8 +- Session/Settings/NukeDataModal.swift | 5 +- .../Database/Models/ClosedGroup.swift | 7 +- .../Jobs/Types/NotifyPushServerJob.swift | 3 +- .../Open Groups/Models/SOGSMessage.swift | 2 +- .../Open Groups/OpenGroupManager.swift | 2 + .../MessageReceiver+ClosedGroups.swift | 20 +- .../MessageReceiver+VisibleMessages.swift | 6 +- .../MessageSender+ClosedGroups.swift | 9 +- .../Sending & Receiving/MessageReceiver.swift | 5 + .../Models/LegacyGroupRequest.swift | 10 + .../Models/LegacyNotifyRequest.swift | 15 + ...e.swift => LegacyPushServerResponse.swift} | 2 +- .../Models/PushNotificationAPIRequest.swift | 33 + .../Models/SubscribeRequest.swift | 153 +++++ .../Models/SubscribeResponse.swift | 31 + .../Models/UnsubscribeRequest.swift | 111 ++++ .../Models/UnsubscribeResponse.swift | 31 + .../Notifications/PushNotificationAPI.swift | 602 +++++++++++------- .../Types/PushNotificationAPIEndpoint.swift | 40 ++ .../Notifications/Types/Service.swift | 9 + .../ShareNavController.swift | 21 +- SessionShareExtension/ThreadPickerVC.swift | 19 +- SessionUtilitiesKit/Database/Storage.swift | 42 +- .../Database/StorageError.swift | 2 + SessionUtilitiesKit/General/SessionId.swift | 1 + SignalUtilitiesKit/Utilities/AppSetup.swift | 8 + 51 files changed, 1026 insertions(+), 284 deletions(-) create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupRequest.swift create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyNotifyRequest.swift rename SessionMessagingKit/Sending & Receiving/Notifications/Models/{PushServerResponse.swift => LegacyPushServerResponse.swift} (78%) create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/PushNotificationAPIRequest.swift create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeResponse.swift create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeResponse.swift create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index a09fceb75..cbe919ba5 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -758,6 +758,15 @@ FDBB25E12983909300F1508E /* ConfigConvoInfoVolatileSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.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 */; }; + 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 */; }; + FDC13D522A16F22E007267C7 /* PushNotificationAPIRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D512A16F22E007267C7 /* PushNotificationAPIRequest.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 */; }; 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 */; }; @@ -778,7 +787,7 @@ FDC4381527B329CE00C60D73 /* NonceGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381427B329CE00C60D73 /* NonceGenerator.swift */; }; FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381627B32EC700C60D73 /* Personalization.swift */; }; FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */; }; - FDC4382F27B383AF00C60D73 /* PushServerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382E27B383AF00C60D73 /* PushServerResponse.swift */; }; + FDC4382F27B383AF00C60D73 /* LegacyPushServerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */; }; FDC4383827B3863200C60D73 /* VersionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383727B3863200C60D73 /* VersionResponse.swift */; }; FDC4385D27B4C18900C60D73 /* Room.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385C27B4C18900C60D73 /* Room.swift */; }; FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */; }; @@ -1890,6 +1899,15 @@ FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigConvoInfoVolatileSpec.swift; sourceTree = ""; }; FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_AddJobPriority.swift; sourceTree = ""; }; FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Theming.swift"; sourceTree = ""; }; + 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 = ""; }; + FDC13D512A16F22E007267C7 /* PushNotificationAPIRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationAPIRequest.swift; sourceTree = ""; }; + FDC13D532A16FF29007267C7 /* LegacyGroupRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGroupRequest.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 = ""; }; 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 = ""; }; @@ -1909,7 +1927,7 @@ FDC4381427B329CE00C60D73 /* NonceGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceGenerator.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 /* PushServerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushServerResponse.swift; sourceTree = ""; }; + FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyPushServerResponse.swift; sourceTree = ""; }; FDC4383727B3863200C60D73 /* VersionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionResponse.swift; sourceTree = ""; }; FDC4383D27B4708600C60D73 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = ""; }; FDC4385C27B4C18900C60D73 /* Room.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Room.swift; sourceTree = ""; }; @@ -3151,6 +3169,7 @@ C379DC6825672B5E0002D4EB /* Notifications */ = { isa = PBXGroup; children = ( + FDC13D4E2A16EE41007267C7 /* Types */, FDC4382D27B383A600C60D73 /* Models */, FDF0B7502807BA56004C14C5 /* NotificationsProtocol.swift */, C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */, @@ -4140,6 +4159,15 @@ path = Configs; sourceTree = ""; }; + FDC13D4E2A16EE41007267C7 /* Types */ = { + isa = PBXGroup; + children = ( + FDC13D482A16EC20007267C7 /* Service.swift */, + FDC13D4F2A16EE50007267C7 /* PushNotificationAPIEndpoint.swift */, + ); + path = Types; + sourceTree = ""; + }; FDC2909227D710A9005DAE71 /* Types */ = { isa = PBXGroup; children = ( @@ -4192,7 +4220,14 @@ FDC4382D27B383A600C60D73 /* Models */ = { isa = PBXGroup; children = ( - FDC4382E27B383AF00C60D73 /* PushServerResponse.swift */, + FDC13D512A16F22E007267C7 /* PushNotificationAPIRequest.swift */, + FDC13D462A16E4CA007267C7 /* SubscribeRequest.swift */, + FDC13D4A2A16ECBA007267C7 /* SubscribeResponse.swift */, + FDC13D552A171FE4007267C7 /* UnsubscribeRequest.swift */, + FDC13D572A17207D007267C7 /* UnsubscribeResponse.swift */, + FDC13D532A16FF29007267C7 /* LegacyGroupRequest.swift */, + FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */, + FDC13D592A1721C5007267C7 /* LegacyNotifyRequest.swift */, ); path = Models; sourceTree = ""; @@ -5703,6 +5738,7 @@ FD245C672850665E00B966DD /* AttachmentDownloadJob.swift in Sources */, C300A5D32554B05A00555489 /* TypingIndicator.swift in Sources */, 7B521E0A29BFF84400C3C36A /* GroupLeavingJob.swift in Sources */, + FDC13D582A17207D007267C7 /* UnsubscribeResponse.swift in Sources */, FD09799927FFC1A300936362 /* Attachment.swift in Sources */, FD245C5F2850662200B966DD /* OWSWindowManager.m in Sources */, C3471ECB2555356A00297E91 /* MessageSender+Encryption.swift in Sources */, @@ -5749,6 +5785,7 @@ FD37EA0F28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift in Sources */, FD2B4AFD294688D000AB4848 /* SessionUtil+Contacts.swift in Sources */, 7B81682328A4C1210069F315 /* UpdateTypes.swift in Sources */, + FDC13D472A16E4CA007267C7 /* SubscribeRequest.swift in Sources */, FD2B4AFF2946C93200AB4848 /* ConfigurationSyncJob.swift in Sources */, FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */, FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */, @@ -5757,6 +5794,7 @@ C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */, FDB4BBC72838B91E00B7C95D /* LinkPreviewError.swift in Sources */, FD09798327FD1A1500936362 /* ClosedGroup.swift in Sources */, + FDC13D542A16FF29007267C7 /* LegacyGroupRequest.swift in Sources */, B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */, FD8ECF8B2935DB4B00C0D1BB /* SharedConfigMessage.swift in Sources */, FD09798727FD1B7800936362 /* GroupMember.swift in Sources */, @@ -5766,6 +5804,7 @@ FD17D79927F40AB800122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, FDF0B7512807BA56004C14C5 /* NotificationsProtocol.swift in Sources */, B8DE1FB426C22F2F0079C9CE /* WebRTCSession.swift in Sources */, + FDC13D5A2A1721C5007267C7 /* LegacyNotifyRequest.swift in Sources */, FDC6D6F32860607300B04575 /* Environment.swift in Sources */, C3A3A171256E1D25004D228D /* SSKReachabilityManager.swift in Sources */, FD245C59285065FC00B966DD /* ControlMessage.swift in Sources */, @@ -5794,16 +5833,18 @@ FD6A7A692818BE7300035AC1 /* RetrieveDefaultOpenGroupRoomsJob.swift in Sources */, FD09798D27FD1D8900936362 /* DisappearingMessageConfiguration.swift in Sources */, FDF0B75A2807F3A3004C14C5 /* MessageSenderError.swift in Sources */, - FDC4382F27B383AF00C60D73 /* PushServerResponse.swift in Sources */, + FDC4382F27B383AF00C60D73 /* LegacyPushServerResponse.swift in Sources */, FDC4386327B4D94E00C60D73 /* SOGSMessage.swift in Sources */, FD245C692850666800B966DD /* ExpirationTimerUpdate.swift in Sources */, FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */, FD245C6C2850669200B966DD /* MessageReceiveJob.swift in Sources */, FD43EE9D297A5190009C87C5 /* SessionUtil+UserGroups.swift in Sources */, FD83B9CC27D179BC005E1583 /* FSEndpoint.swift in Sources */, + FDC13D4B2A16ECBA007267C7 /* SubscribeResponse.swift in Sources */, FD7115F228C6CB3900B47552 /* _010_AddThreadIdToFTS.swift in Sources */, FD716E6428502DDD00C96BF4 /* CallManagerProtocol.swift in Sources */, FDC438C727BB6DF000C60D73 /* DirectMessage.swift in Sources */, + FDC13D502A16EE50007267C7 /* PushNotificationAPIEndpoint.swift in Sources */, FD432434299C6985008A0213 /* PendingReadReceipt.swift in Sources */, FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */, FD245C51285065CC00B966DD /* MessageReceiver.swift in Sources */, @@ -5831,7 +5872,9 @@ FD09796E27FA6D0000936362 /* Contact.swift in Sources */, C38D5E8D2575011E00B6A65C /* MessageSender+ClosedGroups.swift in Sources */, FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */, + FDC13D492A16EC20007267C7 /* Service.swift in Sources */, FD778B6429B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift in Sources */, + FDC13D562A171FE4007267C7 /* UnsubscribeRequest.swift in Sources */, C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */, @@ -5854,6 +5897,7 @@ FD245C682850666300B966DD /* Message+Destination.swift in Sources */, FDF8488029405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift in Sources */, FD09798527FD1A6500936362 /* ClosedGroupKeyPair.swift in Sources */, + FDC13D522A16F22E007267C7 /* PushNotificationAPIRequest.swift in Sources */, FD245C632850664600B966DD /* Configuration.swift in Sources */, C32C5DBF256DD743003C73A2 /* ClosedGroupPoller.swift in Sources */, C352A35B2557824E00338F3E /* AttachmentUploadJob.swift in Sources */, diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 3de4c535c..bd6aed602 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -74,7 +74,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD }, migrationsCompletion: { [weak self] result, needsConfigSync in if case .failure(let error) = result { - self?.showFailedMigrationAlert(calledFrom: .finishLaunching, error: error) + self?.showDatabaseSetupFailureModal(calledFrom: .finishLaunching, error: error) return } @@ -145,7 +145,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD }, migrationsCompletion: { [weak self] result, needsConfigSync in if case .failure(let error) = result { - self?.showFailedMigrationAlert(calledFrom: .enterForeground, error: error) + self?.showDatabaseSetupFailureModal(calledFrom: .enterForeground, error: error) return } @@ -330,15 +330,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } } - private func showFailedMigrationAlert(calledFrom lifecycleMethod: LifecycleMethod, error: Error?) { + private func showDatabaseSetupFailureModal(calledFrom lifecycleMethod: LifecycleMethod, error: Error?) { let alert = UIAlertController( title: "Session", - message: "DATABASE_MIGRATION_FAILED".localized(), + message: { + switch (error as? StorageError) { + case .databaseInvalid: return "DATABASE_SETUP_FAILED".localized() + default: return "DATABASE_MIGRATION_FAILED".localized() + } + }(), preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "HELP_REPORT_BUG_ACTION_TITLE".localized(), style: .default) { _ in HelpViewModel.shareLogs(viewControllerToDismiss: alert) { [weak self] in - self?.showFailedMigrationAlert(calledFrom: lifecycleMethod, error: error) + self?.showDatabaseSetupFailureModal(calledFrom: lifecycleMethod, error: error) } }) alert.addAction(UIAlertAction(title: "vc_restore_title".localized(), style: .destructive) { _ in @@ -359,7 +364,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD }, migrationsCompletion: { [weak self] result, needsConfigSync in if case .failure(let error) = result { - self?.showFailedMigrationAlert(calledFrom: lifecycleMethod, error: error) + self?.showDatabaseSetupFailureModal(calledFrom: lifecycleMethod, error: error) return } diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 4cc110972..7de00d961 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index c8a44a169..2f64e3165 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 23e1407ae..e15ce85a7 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 0f4ea4b7a..72682bc96 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "متاسفانه خطایی رخ داده است"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "لطفا بعدا دوباره تلاش کنید"; "LOADING_CONVERSATIONS" = "درحال بارگزاری پیام ها..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "هنگام بهینه‌سازی پایگاه داده خطایی روی داد\n\nشما می‌توانید گزارش‌های برنامه خود را صادر کنید تا بتوانید برای عیب‌یابی به اشتراک بگذارید یا می‌توانید دستگاه خود را بازیابی کنید\n\nهشدار: بازیابی دستگاه شما منجر به از دست رفتن داده‌های قدیمی‌تر از دو هفته می‌شود."; "RECOVERY_PHASE_ERROR_GENERIC" = "مشکلی پیش آمد. لطفاً عبارت بازیابی خود را بررسی کنید و دوباره امتحان کنید."; "RECOVERY_PHASE_ERROR_LENGTH" = "به نظر می رسد کلمات کافی وارد نکرده اید. لطفاً عبارت بازیابی خود را بررسی کنید و دوباره امتحان کنید."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index 04b308b45..e212cc192 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index f5a82501b..3ba4e14c6 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oups, une erreur est survenue"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Veuillez réessayer plus tard"; "LOADING_CONVERSATIONS" = "Chargement des conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "Une erreur est survenue pendant l'optimisation de la base de données\n\nVous pouvez exporter votre journal d'application pour le partager et aider à régler le problème ou vous pouvez restaurer votre appareil\n\nAttention : restaurer votre appareil résultera en une perte des données des deux dernières semaines"; "RECOVERY_PHASE_ERROR_GENERIC" = "Quelque chose s'est mal passé. Vérifiez votre phrase de récupération et réessayez s'il vous plaît."; "RECOVERY_PHASE_ERROR_LENGTH" = "Il semble que vous n'avez pas saisi tous les mots. Vérifiez votre phrase de récupération et réessayez s'il vous plaît."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index fb2fd0ed9..c0bae9882 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 2f603b200..2dcb2114f 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index 7a23f9aff..73632f8ee 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index c094b389d..18969699b 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index f092cd291..146814921 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 9315f29ac..bc1b3bcc0 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index 8a2c7865f..1bda47473 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 1991bb588..53c834129 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 6d516c8bf..6058afaff 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index 8f66d6a66..d2ef27707 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 08f7018f9..652259fac 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index 6896d243c..055a05234 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index a1fe56cdd..055471f54 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 3b060a0ba..51d47e9cc 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 5dd8760a7..23786b3f6 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index c14cf0c85..a56b174b0 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_SETUP_FAILED" = "An error occurred when opening the database, please restart to try again\n\nIf you contineu to see this error you can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -641,3 +642,4 @@ "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"database_inaccessible_error" = "There is an issue opening the database. Please restart the app and try again."; diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 4ac93c7a8..90a6585c6 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -155,15 +155,15 @@ extension SyncPushTokensJob { .setFailureType(to: Error.self) .flatMap { pushTokenAsData -> AnyPublisher in guard isUsingFullAPNs else { - return PushNotificationAPI.unregister(pushTokenAsData) + return PushNotificationAPI + .unsubscribe(token: pushTokenAsData) .map { _ in true } .eraseToAnyPublisher() } return PushNotificationAPI - .register( - with: pushTokenAsData, - publicKey: getUserHexEncodedPublicKey(), + .subscribe( + token: pushTokenAsData, isForcedUpdate: isForcedUpdate ) .map { _ in true } diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift index 2d8ceeaff..18aa51a56 100644 --- a/Session/Settings/NukeDataModal.swift +++ b/Session/Settings/NukeDataModal.swift @@ -225,8 +225,9 @@ final class NukeDataModal: Modal { let maybeDeviceToken: String? = UserDefaults.standard[.deviceToken] if isUsingFullAPNs, let deviceToken: String = maybeDeviceToken { - let data: Data = Data(hex: deviceToken) - PushNotificationAPI.unregister(data).sinkUntilComplete() + PushNotificationAPI + .unsubscribe(token: Data(hex: deviceToken)) + .sinkUntilComplete() } // Clear the app badge and notifications diff --git a/SessionMessagingKit/Database/Models/ClosedGroup.swift b/SessionMessagingKit/Database/Models/ClosedGroup.swift index 43e922ed0..de285bb2b 100644 --- a/SessionMessagingKit/Database/Models/ClosedGroup.swift +++ b/SessionMessagingKit/Database/Models/ClosedGroup.swift @@ -144,10 +144,9 @@ public extension ClosedGroup { ClosedGroupPoller.shared.stopPolling(for: threadId) PushNotificationAPI - .performOperation( - .unsubscribe, - for: threadId, - publicKey: userPublicKey + .unsubscribeFromLegacyGroup( + legacyGroupId: threadId, + currentUserPublicKey: userPublicKey ) .sinkUntilComplete() } diff --git a/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift b/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift index 121aa0d47..474fcbd27 100644 --- a/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift +++ b/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift @@ -5,6 +5,7 @@ import Combine import SessionSnodeKit import SessionUtilitiesKit +// FIXME: Remove this once legacy notifications and legacy groups are deprecated public enum NotifyPushServerJob: JobExecutor { public static var maxFailureCount: Int = 20 public static var requiresThreadId: Bool = false @@ -26,7 +27,7 @@ public enum NotifyPushServerJob: JobExecutor { } PushNotificationAPI - .notify( + .legacyNotify( recipient: details.message.recipient, with: details.message.data, maxRetryCount: 4 diff --git a/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift b/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift index 5bbccaf02..211d5b3da 100644 --- a/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift +++ b/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift @@ -89,7 +89,7 @@ extension OpenGroupAPI.Message { throw HTTPError.parsingFailed } - case .none: + case .none, .group: SNLog("Ignoring message with invalid sender.") throw HTTPError.parsingFailed } diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 3716460df..2b006dadf 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -992,6 +992,8 @@ public final class OpenGroupManager { .filter(possibleKeys.contains(GroupMember.Columns.profileId)) .filter(targetRoles.contains(GroupMember.Columns.role)) .isNotEmpty(db) + + case .group: return false } } .defaulting(to: false) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index 860df5069..2bfa9e813 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -192,8 +192,13 @@ extension MessageReceiver { // Start polling ClosedGroupPoller.shared.startIfNeeded(for: groupPublicKey) - // Notify the PN server - let _ = PushNotificationAPI.performOperation(.subscribe, for: groupPublicKey, publicKey: getUserHexEncodedPublicKey(db)) + // Subscribe for push notifications + PushNotificationAPI + .subscribeToLegacyGroup( + legacyGroupId: groupPublicKey, + currentUserPublicKey: getUserHexEncodedPublicKey(db) + ) + .sinkUntilComplete() } /// Extracts and adds the new encryption key pair to our list of key pairs if there is one for our public key, AND the message was @@ -479,11 +484,12 @@ extension MessageReceiver { .keyPairs .deleteAll(db) - let _ = PushNotificationAPI.performOperation( - .unsubscribe, - for: threadId, - publicKey: userPublicKey - ) + PushNotificationAPI + .unsubscribeFromLegacyGroup( + legacyGroupId: threadId, + currentUserPublicKey: userPublicKey + ) + .sinkUntilComplete() } } ) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index a891bb3ba..943f3f556 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -71,7 +71,7 @@ extension MessageReceiver { return try? OpenGroup.fetchOne(db, id: threadId) }() - let variant: Interaction.Variant = { + let variant: Interaction.Variant = try { guard let senderSessionId: SessionId = SessionId(from: sender), let openGroup: OpenGroup = maybeOpenGroup @@ -106,6 +106,10 @@ extension MessageReceiver { .standardOutgoing : .standardIncoming ) + + case .group: + SNLog("Ignoring message with invalid sender.") + throw HTTPError.parsingFailed } }() diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index a6722a847..9f4ed0dc2 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -117,11 +117,10 @@ extension MessageSender { memberSendData .map { MessageSender.sendImmediate(preparedSendData: $0) } .appending( - // Notify the PN server - PushNotificationAPI.performOperation( - .subscribe, - for: groupPublicKey, - publicKey: userPublicKey + // Subscribe for push notifications (if enabled) + PushNotificationAPI.subscribeToLegacyGroup( + legacyGroupId: groupPublicKey, + currentUserPublicKey: userPublicKey ) ) ) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index d4ab2e3c7..8f3c0c4f5 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -64,6 +64,11 @@ public enum MessageReceiver { userEd25519KeyPair: userEd25519KeyPair, using: dependencies ) + + case .group: + // TODO: Need to decide how we will handle updated group messages + SNLog("Ignoring message with invalid sender.") + throw HTTPError.parsingFailed } case .closedGroupMessage: diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupRequest.swift new file mode 100644 index 000000000..962011dfd --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupRequest.swift @@ -0,0 +1,10 @@ +// 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 new file mode 100644 index 000000000..491fa7757 --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyNotifyRequest.swift @@ -0,0 +1,15 @@ +// 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/PushServerResponse.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyPushServerResponse.swift similarity index 78% rename from SessionMessagingKit/Sending & Receiving/Notifications/Models/PushServerResponse.swift rename to SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyPushServerResponse.swift index eee22e266..dc8c77ff7 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/PushServerResponse.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyPushServerResponse.swift @@ -3,7 +3,7 @@ import Foundation extension PushNotificationAPI { - struct PushServerResponse: Codable { + struct LegacyPushServerResponse: Codable { let code: Int let message: String? } diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/PushNotificationAPIRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/PushNotificationAPIRequest.swift new file mode 100644 index 000000000..671b0b7a5 --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/PushNotificationAPIRequest.swift @@ -0,0 +1,33 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +public struct PushNotificationAPIRequest: Encodable { + private enum CodingKeys: String, CodingKey { + case method + case body = "params" + } + + internal let endpoint: PushNotificationAPI.Endpoint + internal let body: T + + // MARK: - Initialization + + public init( + endpoint: PushNotificationAPI.Endpoint, + body: T + ) { + self.endpoint = endpoint + self.body = body + } + + // MARK: - Codable + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(endpoint.rawValue, forKey: .method) + try container.encode(body, forKey: .body) + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift new file mode 100644 index 000000000..6c302a051 --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift @@ -0,0 +1,153 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionSnodeKit + +extension PushNotificationAPI { + struct SubscribeRequest: Encodable { + struct ServiceInfo: Codable { + private enum CodingKeys: String, CodingKey { + case token + } + + private let token: String + + // MARK: - Initialization + + init(token: String) { + self.token = token + } + } + + private enum CodingKeys: String, CodingKey { + case pubkey + case ed25519PublicKey = "session_ed25519" + case subkey = "subkey_tag" + case namespaces + case includeMessageData = "data" + case timestamp = "sig_ts" + case signatureBase64 = "signature" + case service + case serviceInfo = "service_info" + case notificationsEncryptionKey = "enc_key" + } + + /// The 33-byte account being subscribed to; typically a session ID. + private let pubkey: String + + /// List of integer namespace (-32768 through 32767). These must be sorted in ascending order. + private let namespaces: [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. + private let includeMessageData: Bool + + /// Dict of service-specific data; typically this includes just a "token" field with a device-specific token, but different services in the + /// future may have different input requirements. + private let serviceInfo: ServiceInfo + + /// 32-byte encryption key; notification payloads sent to the device will be encrypted with XChaCha20-Poly1305 using this key. Though + /// it is permitted for this to change, it is recommended that the device generate this once and persist it. + private let notificationsEncryptionKey: Data + + /// 32-byte swarm authentication subkey; omitted (or null) when not using subkey auth + private let subkey: String? + + /// The signature unix timestamp (seconds, not ms) + private let timestamp: TimeInterval + + /// When the pubkey value starts with 05 (i.e. a session ID) this is the underlying ed25519 32-byte pubkey associated with the session + /// ID. When not 05, this field should not be provided. + private let ed25519PublicKey: [UInt8] + + /// Secret key used to generate the signature (**Not** sent with the request) + private let ed25519SecretKey: [UInt8] + + // MARK: - Initialization + + init( + pubkey: String, + namespaces: [SnodeAPI.Namespace], + includeMessageData: Bool, + serviceInfo: ServiceInfo, + notificationsEncryptionKey: Data, + subkey: String?, + timestamp: TimeInterval, + ed25519PublicKey: [UInt8], + ed25519SecretKey: [UInt8] + ) { + self.pubkey = pubkey + self.namespaces = namespaces + self.includeMessageData = includeMessageData + self.serviceInfo = serviceInfo + self.notificationsEncryptionKey = notificationsEncryptionKey + self.subkey = subkey + self.timestamp = timestamp + self.ed25519PublicKey = ed25519PublicKey + self.ed25519SecretKey = ed25519SecretKey + } + + // MARK: - Coding + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + // Generate the signature for the request for encoding + let signatureBase64: String = try generateSignature().toBase64() + try container.encode(pubkey, forKey: .pubkey) + try container.encode(ed25519PublicKey.toHexString(), forKey: .ed25519PublicKey) + try container.encodeIfPresent(subkey, forKey: .subkey) + try container.encode(namespaces.map { $0.rawValue}.sorted(), forKey: .namespaces) + try container.encode(includeMessageData, forKey: .includeMessageData) + try container.encode(Int64(timestamp), forKey: .timestamp) // Server expects rounded seconds + try container.encode(signatureBase64, forKey: .signatureBase64) + try container.encode(Service.apns, forKey: .service) + try container.encode(serviceInfo, forKey: .serviceInfo) + try container.encode(notificationsEncryptionKey.toHexString(), forKey: .notificationsEncryptionKey) + } + + // MARK: - Abstract Methods + + func generateSignature() throws -> [UInt8] { + /// The signature data collected and stored here is used by the PN server to subscribe to the swarms + /// for the given account; the specific rules are governed by the storage server, but in general: + /// + /// A signature must have been produced (via the timestamp) within the past 14 days. It is + /// recommended that clients generate a new signature whenever they re-subscribe, and that + /// re-subscriptions happen more frequently than once every 14 days. + /// + /// A signature is signed using the account's Ed25519 private key (or Ed25519 subkey, if using + /// subkey authentication with a `subkey_tag`, for future closed group subscriptions), and signs the value: + /// `"MONITOR" || HEX(ACCOUNT) || SIG_TS || DATA01 || NS[0] || "," || ... || "," || NS[n]` + /// + /// Where `SIG_TS` is the `sig_ts` value as a base-10 string; `DATA01` is either "0" or "1" depending + /// on whether the subscription wants message data included; and the trailing `NS[i]` values are a + /// comma-delimited list of namespaces that should be subscribed to, in the same sorted order as + /// the `namespaces` parameter. + let verificationBytes: [UInt8] = "MONITOR".bytes + .appending(contentsOf: pubkey.bytes) + .appending(contentsOf: "\(Int64(timestamp))".bytes) // Server expects rounded seconds + .appending(contentsOf: (includeMessageData ? "1" : "0").bytes) + .appending( + contentsOf: namespaces + .map { $0.rawValue } // Intentionally not using `verificationString` here + .sorted() + .map { "\($0)" } + .joined(separator: ",") + .bytes + ) + + // TODO: Need to add handling for subkey auth + guard + let signatureBytes: [UInt8] = sodium.wrappedValue.sign.signature( + message: verificationBytes, + secretKey: ed25519SecretKey + ) + else { + throw SnodeAPIError.signingFailed + } + + return signatureBytes + } + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeResponse.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeResponse.swift new file mode 100644 index 000000000..c2298e1c2 --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeResponse.swift @@ -0,0 +1,31 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension PushNotificationAPI { + struct SubscribeResponse: Codable { + /// Flag indicating the success of the registration + let success: Bool? + + /// Value is `true` upon an initial registration + let added: Bool? + + /// Value is `true` upon a renewal/update registration + 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 + /// + /// Values at the time of writing are: + /// OK = 0 // Great Success! + /// BAD_INPUT = 1 // Unparseable, invalid values, missing required arguments, etc. (details in the string) + /// SERVICE_NOT_AVAILABLE = 2 // The requested service name isn't currently available + /// 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? + + /// Includes additional information about the error + let message: String? + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift new file mode 100644 index 000000000..e26007fee --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift @@ -0,0 +1,111 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionSnodeKit + +extension PushNotificationAPI { + struct UnsubscribeRequest: Encodable { + struct ServiceInfo: Codable { + private enum CodingKeys: String, CodingKey { + case token + } + + private let token: String + + // MARK: - Initialization + + init(token: String) { + self.token = token + } + } + + private enum CodingKeys: String, CodingKey { + case pubkey + case ed25519PublicKey = "session_ed25519" + case subkey = "subkey_tag" + case timestamp = "sig_ts" + case signatureBase64 = "signature" + case service + case serviceInfo = "service_info" + } + + /// The 33-byte account being subscribed to; typically a session ID. + private let pubkey: String + + /// Dict of service-specific data; typically this includes just a "token" field with a device-specific token, but different services in the + /// future may have different input requirements. + private let serviceInfo: ServiceInfo + + /// 32-byte swarm authentication subkey; omitted (or null) when not using subkey auth + private let subkey: String? + + /// The signature unix timestamp (seconds, not ms) + private let timestamp: TimeInterval + + /// When the pubkey value starts with 05 (i.e. a session ID) this is the underlying ed25519 32-byte pubkey associated with the session + /// ID. When not 05, this field should not be provided. + private let ed25519PublicKey: [UInt8] + + /// Secret key used to generate the signature (**Not** sent with the request) + private let ed25519SecretKey: [UInt8] + + // MARK: - Initialization + + init( + pubkey: String, + serviceInfo: ServiceInfo, + subkey: String?, + timestamp: TimeInterval, + ed25519PublicKey: [UInt8], + ed25519SecretKey: [UInt8] + ) { + self.pubkey = pubkey + self.serviceInfo = serviceInfo + self.subkey = subkey + self.timestamp = timestamp + self.ed25519PublicKey = ed25519PublicKey + self.ed25519SecretKey = ed25519SecretKey + } + + // MARK: - Coding + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + // Generate the signature for the request for encoding + let signatureBase64: String = try generateSignature().toBase64() + try container.encode(pubkey, forKey: .pubkey) + try container.encode(ed25519PublicKey.toHexString(), forKey: .ed25519PublicKey) + try container.encodeIfPresent(subkey, forKey: .subkey) + try container.encode(timestamp, forKey: .timestamp) + try container.encode(signatureBase64, forKey: .signatureBase64) + try container.encode(Service.apns, forKey: .service) + try container.encode(serviceInfo, forKey: .serviceInfo) + } + + // MARK: - Abstract Methods + + func generateSignature() throws -> [UInt8] { + /// A signature is signed using the account's Ed25519 private key (or Ed25519 subkey, if using + /// subkey authentication with a `subkey_tag`, for future closed group subscriptions), and signs the value: + /// `"UNSUBSCRIBE" || HEX(ACCOUNT) || SIG_TS` + /// + /// Where `SIG_TS` is the `sig_ts` value as a base-10 string and must be within 24 hours of the current time. + let verificationBytes: [UInt8] = "UNSUBSCRIBE".bytes + .appending(contentsOf: pubkey.bytes) + .appending(contentsOf: "\(timestamp)".data(using: .ascii)?.bytes) + + // TODO: Need to add handling for subkey auth + guard + let signatureBytes: [UInt8] = sodium.wrappedValue.sign.signature( + message: verificationBytes, + secretKey: ed25519SecretKey + ) + else { + throw SnodeAPIError.signingFailed + } + + return signatureBytes + } + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeResponse.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeResponse.swift new file mode 100644 index 000000000..03b38c524 --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeResponse.swift @@ -0,0 +1,31 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension PushNotificationAPI { + struct UnsubscribeResponse: Codable { + /// Flag indicating the success of the registration + let success: Bool? + + /// Value is `true` upon an initial registration + let added: Bool? + + /// Value is `true` upon a renewal/update registration + 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 + /// + /// Values at the time of writing are: + /// OK = 0 // Great Success! + /// BAD_INPUT = 1 // Unparseable, invalid values, missing required arguments, etc. (details in the string) + /// SERVICE_NOT_AVAILABLE = 2 // The requested service name isn't currently available + /// 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? + + /// Includes additional information about the error + let message: String? + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index 78886b8c8..4d1c0148a 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -3,134 +3,31 @@ import Foundation import Combine import GRDB +import Sodium import SessionSnodeKit import SessionUtilitiesKit public enum PushNotificationAPI { - struct RegistrationRequestBody: Codable { - let token: String - let pubKey: String? - } - - struct NotifyRequestBody: Codable { - enum CodingKeys: String, CodingKey { - case data - case sendTo = "send_to" - } - - let data: String - let sendTo: String - } - - struct ClosedGroupRequestBody: Codable { - let closedGroupPublicKey: String - let pubKey: String - } - - // MARK: - Settings - - public static let server = "https://live.apns.getsession.org" - public static let serverPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" - + internal static let sodium: Atomic = Atomic(Sodium()) + private static let keychainService: String = "PNKeyChainService" + private static let encryptionKeyKey: String = "PNEncryptionKeyKey" + private static let encryptionKeyLength: Int = 32 private static let maxRetryCount: Int = 4 - private static let tokenExpirationInterval: TimeInterval = 12 * 60 * 60 - - public enum ClosedGroupOperation: Int { - case subscribe, unsubscribe - - public var endpoint: String { - switch self { - case .subscribe: return "subscribe_closed_group" - case .unsubscribe: return "unsubscribe_closed_group" - } - } - } + private static let tokenExpirationInterval: TimeInterval = (12 * 60 * 60) - // MARK: - Registration + public static let server = "https://push.getsession.org" + public static let serverPublicKey = "d7557fe563e2610de876c0ac7341b62f3c82d5eea4b62c702392ea4368f51b3b" + public static let legacyServer = "https://live.apns.getsession.org" + public static let legacyServerPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" + + // MARK: - Requests - public static func unregister(_ token: Data) -> AnyPublisher { - let requestBody: RegistrationRequestBody = RegistrationRequestBody(token: token.toHexString(), pubKey: nil) - - guard let body: Data = try? JSONEncoder().encode(requestBody) else { - return Fail(error: HTTPError.invalidJSON) - .eraseToAnyPublisher() - } - - // Unsubscribe from all closed groups (including ones the user is no longer a member of, - // just in case) - Storage.shared - .readPublisher { db -> (String, Set) in - ( - getUserHexEncodedPublicKey(db), - try ClosedGroup - .select(.threadId) - .asRequest(of: String.self) - .fetchSet(db) - ) - } - .flatMap { userPublicKey, closedGroupPublicKeys in - Publishers - .MergeMany( - closedGroupPublicKeys - .map { closedGroupPublicKey -> AnyPublisher in - PushNotificationAPI - .performOperation( - .unsubscribe, - for: closedGroupPublicKey, - publicKey: userPublicKey - ) - } - ) - .collect() - .eraseToAnyPublisher() - } - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .sinkUntilComplete() - - // Unregister for normal push notifications - let url = URL(string: "\(server)/unregister")! - var request: URLRequest = URLRequest(url: url) - request.httpMethod = "POST" - request.allHTTPHeaderFields = [ HTTPHeader.contentType: "application/json" ] - request.httpBody = body - - return OnionRequestAPI - .sendOnionRequest(request, to: server, with: serverPublicKey) - .map { _, data -> Void in - guard let response: PushServerResponse = try? data?.decoded(as: PushServerResponse.self) else { - return SNLog("Couldn't unregister from push notifications.") - } - guard response.code != 0 else { - return SNLog("Couldn't unregister from push notifications due to error: \(response.message ?? "nil").") - } - - return () - } - .retry(maxRetryCount) - .handleEvents( - receiveCompletion: { result in - switch result { - case .finished: break - case .failure: SNLog("Couldn't unregister from push notifications.") - } - } - ) - .eraseToAnyPublisher() - } - - public static func register( - with token: Data, - publicKey: String, - isForcedUpdate: Bool + public static func subscribe( + token: Data, + isForcedUpdate: Bool, + using dependencies: SSKDependencies = SSKDependencies() ) -> AnyPublisher { let hexEncodedToken: String = token.toHexString() - let requestBody: RegistrationRequestBody = RegistrationRequestBody(token: hexEncodedToken, pubKey: publicKey) - - guard let body: Data = try? JSONEncoder().encode(requestBody) else { - return Fail(error: HTTPError.invalidJSON) - .eraseToAnyPublisher() - } - let oldToken: String? = UserDefaults.standard[.deviceToken] let lastUploadTime: Double = UserDefaults.standard[.lastDeviceTokenUpload] let now: TimeInterval = Date().timeIntervalSince1970 @@ -142,153 +39,402 @@ public enum PushNotificationAPI { .eraseToAnyPublisher() } - let url = URL(string: "\(server)/register")! - var request: URLRequest = URLRequest(url: url) - request.httpMethod = "POST" - request.allHTTPHeaderFields = [ HTTPHeader.contentType: "application/json" ] - request.httpBody = body + guard let notificationsEncryptionKey: Data = try? getOrGenerateEncryptionKey() else { + SNLog("Unable to retrieve PN encryption key.") + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } - return Publishers - .MergeMany( - [ - OnionRequestAPI - .sendOnionRequest(request, to: server, with: serverPublicKey) - .map { _, data -> Void in - guard let response: PushServerResponse = try? data?.decoded(as: PushServerResponse.self) else { - return SNLog("Couldn't register device token.") + // TODO: Need to generate requests for each updated group as well + return Storage.shared + .readPublisher { db -> (SubscribeRequest, String, Set) in + guard let userED25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else { + throw SnodeAPIError.noKeyPair + } + + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) + let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType] + .defaulting(to: Preferences.NotificationPreviewType.defaultPreviewType) + + let request: SubscribeRequest = SubscribeRequest( + pubkey: currentUserPublicKey, + namespaces: [.default], + includeMessageData: (previewType == .nameAndPreview), // TODO: Test resubscribing when changing the type + serviceInfo: SubscribeRequest.ServiceInfo( + token: hexEncodedToken + ), + notificationsEncryptionKey: notificationsEncryptionKey, + subkey: nil, + timestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000), // Seconds + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey + ) + + return ( + request, + currentUserPublicKey, + try ClosedGroup + .select(.threadId) + .filter(!ClosedGroup.Columns.threadId.like("\(SessionId.Prefix.group.rawValue)%")) + .joining( + required: ClosedGroup.members + .filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db)) + ) + .asRequest(of: String.self) + .fetchSet(db) + ) + } + .flatMap { request, currentUserPublicKey, legacyGroupIds -> AnyPublisher in + Publishers + .MergeMany( + [ + PushNotificationAPI + .send( + request: PushNotificationAPIRequest( + endpoint: .subscribe, + body: request + ) + ) + .decoded(as: SubscribeResponse.self, using: dependencies) + .retry(maxRetryCount) + .handleEvents( + receiveOutput: { _, response in + guard response.success == true else { + return SNLog("Couldn't subscribe for push notifications due to error (\(response.error ?? -1)): \(response.message ?? "nil").") + } + + UserDefaults.standard[.deviceToken] = hexEncodedToken + UserDefaults.standard[.lastDeviceTokenUpload] = now + UserDefaults.standard[.isUsingFullAPNs] = true + }, + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: SNLog("Couldn't subscribe for push notifications.") + } + } + ) + .map { _ in () } + .eraseToAnyPublisher() + ].appending( + // FIXME: Remove this once legacy groups are deprecated + contentsOf: legacyGroupIds + .map { legacyGroupId in + PushNotificationAPI.subscribeToLegacyGroup( + legacyGroupId: legacyGroupId, + currentUserPublicKey: currentUserPublicKey, + using: dependencies + ) + } + ) + ) + .collect() + .map { _ in () } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + + public static func unsubscribe( + token: Data, + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher { + let hexEncodedToken: String = token.toHexString() + + // FIXME: Remove this once legacy groups are deprecated + /// Unsubscribe from all legacy groups (including ones the user is no longer a member of, just in case) + Storage.shared + .readPublisher { db -> (String, Set) in + ( + getUserHexEncodedPublicKey(db), + try ClosedGroup + .select(.threadId) + .filter(!ClosedGroup.Columns.threadId.like("\(SessionId.Prefix.group.rawValue)%")) + .asRequest(of: String.self) + .fetchSet(db) + ) + } + .flatMap { currentUserPublicKey, legacyGroupIds in + Publishers + .MergeMany( + legacyGroupIds + .map { legacyGroupId -> AnyPublisher in + PushNotificationAPI + .unsubscribeFromLegacyGroup( + legacyGroupId: legacyGroupId, + currentUserPublicKey: currentUserPublicKey, + using: dependencies + ) } - guard response.code != 0 else { - return SNLog("Couldn't register device token due to error: \(response.message ?? "nil").") + ) + .collect() + .eraseToAnyPublisher() + } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .sinkUntilComplete() + + // TODO: Need to generate requests for each updated group as well + return Storage.shared + .readPublisher { db -> UnsubscribeRequest in + guard let userED25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else { + throw SnodeAPIError.noKeyPair + } + + return UnsubscribeRequest( + pubkey: getUserHexEncodedPublicKey(db), + serviceInfo: UnsubscribeRequest.ServiceInfo( + token: hexEncodedToken + ), + subkey: nil, + timestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000), // Seconds + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey + ) + } + .flatMap { request -> AnyPublisher in + PushNotificationAPI + .send( + request: PushNotificationAPIRequest( + endpoint: .unsubscribe, + body: request + ) + ) + .decoded(as: UnsubscribeResponse.self, using: dependencies) + .retry(maxRetryCount) + .handleEvents( + receiveOutput: { _, response in + guard response.success == true else { + return SNLog("Couldn't unsubscribe for push notifications due to error (\(response.error ?? -1)): \(response.message ?? "nil").") } - UserDefaults.standard[.deviceToken] = hexEncodedToken - UserDefaults.standard[.lastDeviceTokenUpload] = now - UserDefaults.standard[.isUsingFullAPNs] = true - return () - } - .retry(maxRetryCount) - .handleEvents( - receiveCompletion: { result in - switch result { - case .finished: break - case .failure: SNLog("Couldn't register device token.") - } + UserDefaults.standard[.deviceToken] = nil + }, + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: SNLog("Couldn't unsubscribe for push notifications.") } - ) - .eraseToAnyPublisher() - ].appending( - contentsOf: Storage.shared - .read { db -> [String] in - try ClosedGroup - .select(.threadId) - .joining( - required: ClosedGroup.members - .filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db)) - ) - .asRequest(of: String.self) - .fetchAll(db) - } - .defaulting(to: []) - .map { closedGroupPublicKey -> AnyPublisher in - PushNotificationAPI - .performOperation( - .subscribe, - for: closedGroupPublicKey, - publicKey: publicKey - ) } + ) + .map { _ in () } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + + // MARK: - Legacy Notifications + + // FIXME: Remove this once legacy notifications and legacy groups are deprecated + public static func legacyNotify( + recipient: String, + with message: String, + maxRetryCount: Int? = nil, + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher { + return PushNotificationAPI + .send( + request: PushNotificationAPIRequest( + endpoint: .legacyNotify, + body: LegacyNotifyRequest( + data: message, + sendTo: recipient + ) ) ) - .collect() + .decoded(as: LegacyPushServerResponse.self, using: dependencies) + .retry(maxRetryCount ?? PushNotificationAPI.maxRetryCount) + .handleEvents( + receiveOutput: { _, response in + guard response.code != 0 else { + return SNLog("Couldn't send push notification due to error: \(response.message ?? "nil").") + } + }, + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: SNLog("Couldn't send push notification.") + } + } + ) .map { _ in () } .eraseToAnyPublisher() } - - public static func performOperation( - _ operation: ClosedGroupOperation, - for closedGroupPublicKey: String, - publicKey: String + + // MARK: - Legacy Groups + + // FIXME: Remove this once legacy groups are deprecated + public static func subscribeToLegacyGroup( + legacyGroupId: String, + currentUserPublicKey: String, + using dependencies: SSKDependencies = SSKDependencies() ) -> AnyPublisher { let isUsingFullAPNs = UserDefaults.standard[.isUsingFullAPNs] - let requestBody: ClosedGroupRequestBody = ClosedGroupRequestBody( - closedGroupPublicKey: closedGroupPublicKey, - pubKey: publicKey - ) guard isUsingFullAPNs else { return Just(()) .setFailureType(to: Error.self) .eraseToAnyPublisher() } - guard let body: Data = try? JSONEncoder().encode(requestBody) else { - return Fail(error: HTTPError.invalidJSON) - .eraseToAnyPublisher() - } - let url = URL(string: "\(server)/\(operation.endpoint)")! - var request: URLRequest = URLRequest(url: url) - request.httpMethod = "POST" - request.allHTTPHeaderFields = [ HTTPHeader.contentType: "application/json" ] - request.httpBody = body - - return OnionRequestAPI - .sendOnionRequest(request, to: server, with: serverPublicKey) - .map { _, data in - guard let response: PushServerResponse = try? data?.decoded(as: PushServerResponse.self) else { - return SNLog("Couldn't subscribe/unsubscribe for closed group: \(closedGroupPublicKey).") - } - guard response.code != 0 else { - return SNLog("Couldn't subscribe/unsubscribe for closed group: \(closedGroupPublicKey) due to error: \(response.message ?? "nil").") - } - - return () - } + return PushNotificationAPI + .send( + request: PushNotificationAPIRequest( + endpoint: .legacyGroupSubscribe, + body: LegacyGroupRequest( + pubKey: currentUserPublicKey, + closedGroupPublicKey: legacyGroupId + ) + ) + ) + .decoded(as: LegacyPushServerResponse.self, using: dependencies) .retry(maxRetryCount) .handleEvents( + receiveOutput: { _, response in + guard response.code != 0 else { + return SNLog("Couldn't subscribe for legacy group: \(legacyGroupId) due to error: \(response.message ?? "nil").") + } + }, receiveCompletion: { result in switch result { case .finished: break - case .failure: - SNLog("Couldn't subscribe/unsubscribe for closed group: \(closedGroupPublicKey).") + case .failure: SNLog("Couldn't subscribe for legacy group: \(legacyGroupId).") } } ) + .map { _ in () } .eraseToAnyPublisher() } - // MARK: - Notify - - public static func notify( - recipient: String, - with message: String, - maxRetryCount: Int? = nil + // FIXME: Remove this once legacy groups are deprecated + public static func unsubscribeFromLegacyGroup( + legacyGroupId: String, + currentUserPublicKey: String, + using dependencies: SSKDependencies = SSKDependencies() ) -> AnyPublisher { - let requestBody: NotifyRequestBody = NotifyRequestBody(data: message, sendTo: recipient) + let isUsingFullAPNs = UserDefaults.standard[.isUsingFullAPNs] - guard let body: Data = try? JSONEncoder().encode(requestBody) else { + // TODO: Need to validate if this is actually desired behaviour - would this check prevent the app from unsubscribing if the user switches off fast mode??? (this is what the app is currently doing) + // TODO: This flag seems like it might actually be buggy... should double check it + guard isUsingFullAPNs else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + return PushNotificationAPI + .send( + request: PushNotificationAPIRequest( + endpoint: .legacyGroupUnsubscribe, + body: LegacyGroupRequest( + pubKey: currentUserPublicKey, + closedGroupPublicKey: legacyGroupId + ) + ) + ) + .decoded(as: LegacyPushServerResponse.self, using: dependencies) + .retry(maxRetryCount) + .handleEvents( + receiveOutput: { _, response in + guard response.code != 0 else { + return SNLog("Couldn't unsubscribe for legacy group: \(legacyGroupId) due to error: \(response.message ?? "nil").") + } + }, + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: SNLog("Couldn't unsubscribe for legacy group: \(legacyGroupId).") + } + } + ) + .map { _ in () } + .eraseToAnyPublisher() + } + + // MARK: - Security + + @discardableResult private static func getOrGenerateEncryptionKey() throws -> Data { + // TODO: May want to work this differently (will break after a phone restart if the device hasn't been unlocked yet) + do { + var encryptionKey: Data = try SSKDefaultKeychainStorage.shared.data( + forService: keychainService, + key: encryptionKeyKey + ) + defer { encryptionKey.resetBytes(in: 0..( + request: PushNotificationAPIRequest, + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + guard + let url: URL = URL(string: "\(request.endpoint.server)/\(request.endpoint.rawValue)"), + let payload: Data = try? JSONEncoder().encode(request.body) + else { return Fail(error: HTTPError.invalidJSON) .eraseToAnyPublisher() } - let url = URL(string: "\(server)/notify")! - var request: URLRequest = URLRequest(url: url) - request.httpMethod = "POST" - request.allHTTPHeaderFields = [ HTTPHeader.contentType: "application/json" ] - request.httpBody = body + guard Features.useOnionRequests else { + return HTTP + .execute( + .post, + "\(request.endpoint.server)/\(request.endpoint.rawValue)", + body: payload + ) + .map { response in (HTTP.ResponseInfo(code: -1, headers: [:]), response) } + .eraseToAnyPublisher() + } - return OnionRequestAPI - .sendOnionRequest(request, to: server, with: serverPublicKey) - .map { _, data -> Void in - guard let response: PushServerResponse = try? data?.decoded(as: PushServerResponse.self) else { - return SNLog("Couldn't send push notification.") - } - guard response.code != 0 else { - return SNLog("Couldn't send push notification due to error: \(response.message ?? "nil").") - } - - return () - } - .retry(maxRetryCount ?? PushNotificationAPI.maxRetryCount) + var urlRequest: URLRequest = URLRequest(url: url) + urlRequest.httpMethod = "POST" + urlRequest.allHTTPHeaderFields = [ HTTPHeader.contentType: "application/json" ] + urlRequest.httpBody = payload + + return dependencies.onionApi + .sendOnionRequest(urlRequest, to: request.endpoint.server, with: request.endpoint.serverPublicKey) .eraseToAnyPublisher() } } diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift new file mode 100644 index 000000000..cc3068395 --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift @@ -0,0 +1,40 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension PushNotificationAPI { + enum Endpoint: String { + case subscribe = "subscribe" + case unsubscribe = "unsubscribe" + + // MARK: - Legacy Endpoints + + case legacyNotify = "notify" + case legacyRegister = "register" + case legacyUnregister = "unregister" + case legacyGroupSubscribe = "subscribe_closed_group" + case legacyGroupUnsubscribe = "unsubscribe_closed_group" + + // MARK: - Convenience + + var server: String { + switch self { + case .legacyNotify, .legacyRegister, .legacyUnregister, + .legacyGroupSubscribe, .legacyGroupUnsubscribe: + return PushNotificationAPI.legacyServer + + default: return PushNotificationAPI.server + } + } + + var serverPublicKey: String { + switch self { + case .legacyNotify, .legacyRegister, .legacyUnregister, + .legacyGroupSubscribe, .legacyGroupUnsubscribe: + return PushNotificationAPI.legacyServerPublicKey + + default: return PushNotificationAPI.serverPublicKey + } + } + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift new file mode 100644 index 000000000..cc2e5157f --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift @@ -0,0 +1,9 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension PushNotificationAPI { + enum Service: String, Codable { + case apns + } +} diff --git a/SessionShareExtension/ShareNavController.swift b/SessionShareExtension/ShareNavController.swift index d8a1cfde4..2d95ab044 100644 --- a/SessionShareExtension/ShareNavController.swift +++ b/SessionShareExtension/ShareNavController.swift @@ -8,7 +8,7 @@ import SessionUIKit import SignalCoreKit final class ShareNavController: UINavigationController, ShareViewDelegate { - private var areVersionMigrationsComplete = false + private static let areVersionMigrationsComplete: Atomic = Atomic(false) public static var attachmentPrepPublisher: AnyPublisher<[SignalAttachment], Error>? // MARK: - Error @@ -24,6 +24,8 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { override func loadView() { super.loadView() + + view.themeBackgroundColor = .backgroundPrimary // This should be the first thing we do (Note: If you leave the share context and return to it // the context will already exist, trying to override it results in the share context crashing @@ -72,6 +74,12 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { name: .OWSApplicationDidEnterBackground, object: nil ) + + /// **Note:** If the user opens, dismisses and re-opens the share extension it'll actually use the same instance which + /// results in the `AppSetup` not actually running (and the UI not actually being loaded correctly) - in order to avoid this + /// we call `checkIsAppReady` explicitly here assuming that either the `AppSetup` _hasn't_ complete or won't ever + /// get run + checkIsAppReady() } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { @@ -88,7 +96,7 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { Logger.debug("") - areVersionMigrationsComplete = true + ShareNavController.areVersionMigrationsComplete.mutate { $0 = true } // If we need a config sync then trigger it now if needsConfigSync { @@ -105,10 +113,15 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { AssertIsOnMainThread() // App isn't ready until storage is ready AND all version migrations are complete. - guard areVersionMigrationsComplete else { return } - guard Storage.shared.isValid else { return } + guard ShareNavController.areVersionMigrationsComplete.wrappedValue else { return } + guard Storage.shared.isValid else { + // If the database is invalid then the UI will handle it + showLockScreenOrMainContent() + return + } guard !AppReadiness.isAppReady() else { // Only mark the app as ready once. + showLockScreenOrMainContent() return } diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index a00a4c020..5b0f4e730 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -31,6 +31,18 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView return titleLabel }() + + private lazy var databaseErrorLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.text = "database_inaccessible_error".localized() + result.textAlignment = .center + result.themeTextColor = .textPrimary + result.numberOfLines = 0 + result.isHidden = true + + return result + }() private lazy var tableView: UITableView = { let tableView: UITableView = UITableView() @@ -53,6 +65,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView view.themeBackgroundColor = .backgroundPrimary view.addSubview(tableView) + view.addSubview(databaseErrorLabel) setupLayout() @@ -99,6 +112,10 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView private func setupLayout() { tableView.pin(to: view) + + databaseErrorLabel.pin(.top, to: .top, of: view, withInset: Values.massiveSpacing) + databaseErrorLabel.pin(.leading, to: .leading, of: view, withInset: Values.veryLargeSpacing) + databaseErrorLabel.pin(.trailing, to: .trailing, of: view, withInset: -Values.veryLargeSpacing) } // MARK: - Updating @@ -107,7 +124,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView // Start observing for data changes dataChangeObservable = Storage.shared.start( viewModel.observableViewData, - onError: { _ in }, + onError: { [weak self] _ in self?.databaseErrorLabel.isHidden = Storage.shared.isValid }, onChange: { [weak self] viewData in // The defaul scheduler emits changes on the main thread self?.handleUpdates(viewData) diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 4993be3f7..7679942f8 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -56,20 +56,25 @@ open class Storage { return } - // Generate the database KeySpec if needed (this MUST be done before we try to access the database - // as a different thread might attempt to access the database before the key is successfully created) - // - // Note: We reset the bytes immediately after generation to ensure the database key doesn't hang - // around in memory unintentionally - var tmpKeySpec: Data = Storage.getOrGenerateDatabaseKeySpec() - tmpKeySpec.resetBytes(in: 0.. Data { + @discardableResult private static func getOrGenerateDatabaseKeySpec() throws -> Data { do { var keySpec: Data = try getDatabaseCipherKeySpec() defer { keySpec.resetBytes(in: 0.. Void, onChange: @escaping (Reducer.Value) -> Void ) -> DatabaseCancellable { - guard isValid, let dbWriter: DatabaseWriter = dbWriter else { return AnyDatabaseCancellable(cancel: {}) } + guard isValid, let dbWriter: DatabaseWriter = dbWriter else { + onError(StorageError.databaseInvalid) + return AnyDatabaseCancellable(cancel: {}) + } return observation.start( in: dbWriter, diff --git a/SessionUtilitiesKit/Database/StorageError.swift b/SessionUtilitiesKit/Database/StorageError.swift index 7e48cabc5..c4036b8d6 100644 --- a/SessionUtilitiesKit/Database/StorageError.swift +++ b/SessionUtilitiesKit/Database/StorageError.swift @@ -7,6 +7,8 @@ public enum StorageError: Error { case databaseInvalid case migrationFailed case invalidKeySpec + case keySpecCreationFailed + case keySpecInaccessible case decodingFailed case failedToSave diff --git a/SessionUtilitiesKit/General/SessionId.swift b/SessionUtilitiesKit/General/SessionId.swift index c9391c6c7..7a8dba7b9 100644 --- a/SessionUtilitiesKit/General/SessionId.swift +++ b/SessionUtilitiesKit/General/SessionId.swift @@ -10,6 +10,7 @@ public struct SessionId { case standard = "05" // Used for identified users, open groups, etc. case blinded = "15" // Used for authentication and participants in open groups with blinding enabled case unblinded = "00" // Used for authentication in open groups with blinding disabled + case group = "03" // Used for update group conversations public init?(from stringValue: String?) { guard let stringValue: String = stringValue else { return nil } diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index 9e89ed175..0ff6acf26 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -63,6 +63,14 @@ public enum AppSetup { migrationProgressChanged: ((CGFloat, TimeInterval) -> ())? = nil, migrationsCompletion: @escaping (Result, Bool) -> () ) { + // If the database can't be initialised into a valid state then error + guard Storage.shared.isValid else { + DispatchQueue.main.async { + migrationsCompletion(Result.failure(StorageError.databaseInvalid), false) + } + return + } + var backgroundTask: OWSBackgroundTask? = (backgroundTask ?? OWSBackgroundTask(labelStr: #function)) Storage.shared.perform( From 61ad85b97b16b6a6dc627205cc4dce5be839fd72 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 22 May 2023 10:57:16 +1000 Subject: [PATCH 02/17] Added logic to unsubscribe for legacy one-to-one PNs --- .../Models/LegacyUnsubscribeRequest.swift | 14 +++++++ .../Notifications/PushNotificationAPI.swift | 37 ++++++++++++++++++- .../General/SNUserDefaults.swift | 1 + 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift new file mode 100644 index 000000000..663bafb17 --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift @@ -0,0 +1,14 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionSnodeKit + +extension PushNotificationAPI { + struct LegacyUnsubscribeRequest: Codable { + private let token: String + + init(token: String) { + self.token = token + } + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index 4d1c0148a..bb5d6ce8d 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -115,7 +115,42 @@ public enum PushNotificationAPI { } } ) - .map { _ in () } + .flatMap { _ in + guard UserDefaults.standard[.hasUnregisteredForLegacyPushNotifications] != true else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + return PushNotificationAPI + .send( + request: PushNotificationAPIRequest( + endpoint: .legacyUnregister, + body: LegacyUnsubscribeRequest( + token: hexEncodedToken + ) + ) + ) + .retry(maxRetryCount) + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: + /// Save that we've already unsubscribed + /// + /// **Note:** The server can return an error (`response.code != 0`) but + /// that means the server properly processed the request and the error is likely + /// due to the device not actually being previously subscribed for notifications + /// rather than actually failing to unsubscribe + UserDefaults.standard[.hasUnregisteredForLegacyPushNotifications] = true + + case .failure: SNLog("Couldn't unsubscribe for legacy notifications.") + } + } + ) + .map { _ in () } + .eraseToAnyPublisher() + } .eraseToAnyPublisher() ].appending( // FIXME: Remove this once legacy groups are deprecated diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index 82f18bd64..910461a3d 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -31,6 +31,7 @@ public enum SNUserDefaults { case hasSeenCallIPExposureWarning case hasSeenCallMissedTips case isUsingFullAPNs + case hasUnregisteredForLegacyPushNotifications case wasUnlinked case isMainAppActive case isCallOngoing From 09ab977861c7d07214bbf874d014a97662ca5757 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 26 May 2023 14:27:14 +1000 Subject: [PATCH 03/17] Updated the code to decode and use updated notifications Made the JobQueue execution type explicit Fixed a bug where legacy group's might not be unsubscribed from --- Session.xcodeproj/project.pbxproj | 28 ++ .../Open Groups/Types/SodiumProtocols.swift | 1 + .../Models/NotificationMetadata.swift | 47 ++++ .../Notifications/PushNotificationAPI.swift | 77 ++++- .../Notifications/Types/ProcessResult.swift | 13 + .../Notifications/Types/Service.swift | 1 + .../MockAeadXChaCha20Poly1305Ietf.swift | 1 + .../NotificationServiceExtension.swift | 18 +- .../Networking/OnionRequestAPI.swift | 43 +-- SessionUtilitiesKit/JobRunner/JobRunner.swift | 5 +- SessionUtilitiesKit/Utilities/Bencode.swift | 263 ++++++++++++++++++ .../Utilities/BencodeSpec.swift | 97 +++++++ 12 files changed, 537 insertions(+), 57 deletions(-) create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Types/ProcessResult.swift create mode 100644 SessionUtilitiesKit/Utilities/Bencode.swift create mode 100644 SessionUtilitiesKitTests/Utilities/BencodeSpec.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index cbe919ba5..69124a8f7 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -654,6 +654,7 @@ FD6A7A692818BE7300035AC1 /* RetrieveDefaultOpenGroupRoomsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A682818BE7300035AC1 /* RetrieveDefaultOpenGroupRoomsJob.swift */; }; FD6A7A6B2818C17C00035AC1 /* UpdateProfilePictureJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A6A2818C17C00035AC1 /* UpdateProfilePictureJob.swift */; }; FD6A7A6D2818C61500035AC1 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */; }; + FD6E4C8A2A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */; }; FD705A92278D051200F16121 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A91278D051200F16121 /* ReusableView.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 */; }; @@ -817,6 +818,7 @@ FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; }; FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */; }; FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; }; + FDD82C3F2A205D0A00425F05 /* ProcessResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */; }; FDDC08F229A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */; }; FDDCBDA829E776BF00303C38 /* seed2-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA229E776BF00303C38 /* seed2-2023-2y.crt */; }; FDDCBDA929E776BF00303C38 /* seed1-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA329E776BF00303C38 /* seed1-2023-2y.crt */; }; @@ -910,6 +912,9 @@ 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 */; }; + FDFBB74B2A1EFF4900CA7350 /* Bencode.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFBB74A2A1EFF4900CA7350 /* Bencode.swift */; }; + FDFBB74D2A1F3C4E00CA7350 /* NotificationMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */; }; + FDFBB7542A2023EB00CA7350 /* BencodeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFBB7532A2023EB00CA7350 /* BencodeSpec.swift */; }; FDFC4D9A29F0C51500992FB6 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D22553860900C340D1 /* String+Trimming.swift */; }; FDFC4E1929F1F9A600992FB6 /* libsession-util.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */; }; FDFD645927F26C6800808CA1 /* Array+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D12553860800C340D1 /* Array+Utilities.swift */; }; @@ -1802,6 +1807,7 @@ FD6A7A682818BE7300035AC1 /* RetrieveDefaultOpenGroupRoomsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrieveDefaultOpenGroupRoomsJob.swift; sourceTree = ""; }; FD6A7A6A2818C17C00035AC1 /* UpdateProfilePictureJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateProfilePictureJob.swift; sourceTree = ""; }; FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.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 = ""; }; FD7115EA28C5D78E00B47552 /* ThreadSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSettingsViewModel.swift; sourceTree = ""; }; FD7115EF28C5D7DE00B47552 /* SessionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionHeaderView.swift; sourceTree = ""; }; @@ -1958,6 +1964,7 @@ FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DifferenceKit+Utilities.swift"; sourceTree = ""; }; FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageCollectionJob.swift; sourceTree = ""; }; FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = ""; }; + FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessResult.swift; sourceTree = ""; }; FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionTypeConversionUtilitiesSpec.swift; sourceTree = ""; }; FDDCBDA229E776BF00303C38 /* seed2-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed2-2023-2y.crt"; sourceTree = ""; }; FDDCBDA329E776BF00303C38 /* seed1-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed1-2023-2y.crt"; sourceTree = ""; }; @@ -2054,6 +2061,9 @@ FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePickerHandler.swift; sourceTree = ""; }; FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionCell+Styling.swift"; sourceTree = ""; }; FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrentUserPoller.swift; sourceTree = ""; }; + FDFBB74A2A1EFF4900CA7350 /* Bencode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bencode.swift; sourceTree = ""; }; + FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMetadata.swift; sourceTree = ""; }; + FDFBB7532A2023EB00CA7350 /* BencodeSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BencodeSpec.swift; sourceTree = ""; }; FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */ = {isa = PBXFileReference; explicitFileType = wrapper.xcframework; includeInIndex = 0; path = "libsession-util.xcframework"; sourceTree = BUILD_DIR; }; FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGeneralCache.swift; sourceTree = ""; }; @@ -3612,6 +3622,7 @@ FD09796527F6B0A800936362 /* Utilities */ = { isa = PBXGroup; children = ( + FDFBB74A2A1EFF4900CA7350 /* Bencode.swift */, FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */, FD09796A27F6C67500936362 /* Failable.swift */, FD09797127FAA2F500936362 /* Optional+Utilities.swift */, @@ -4045,6 +4056,7 @@ FD37EA1228AB3F60003AE748 /* Database */, FD83B9B927CF20A5005E1583 /* General */, FD9B30F1293EA0AF008DEE3E /* Networking */, + FDFBB7522A2023DE00CA7350 /* Utilities */, ); path = SessionUtilitiesKitTests; sourceTree = ""; @@ -4163,6 +4175,7 @@ isa = PBXGroup; children = ( FDC13D482A16EC20007267C7 /* Service.swift */, + FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */, FDC13D4F2A16EE50007267C7 /* PushNotificationAPIEndpoint.swift */, ); path = Types; @@ -4225,9 +4238,11 @@ FDC13D4A2A16ECBA007267C7 /* SubscribeResponse.swift */, FDC13D552A171FE4007267C7 /* UnsubscribeRequest.swift */, FDC13D572A17207D007267C7 /* UnsubscribeResponse.swift */, + FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */, FDC13D532A16FF29007267C7 /* LegacyGroupRequest.swift */, FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */, FDC13D592A1721C5007267C7 /* LegacyNotifyRequest.swift */, + FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */, ); path = Models; sourceTree = ""; @@ -4425,6 +4440,14 @@ path = Models; sourceTree = ""; }; + FDFBB7522A2023DE00CA7350 /* Utilities */ = { + isa = PBXGroup; + children = ( + FDFBB7532A2023EB00CA7350 /* BencodeSpec.swift */, + ); + path = Utilities; + sourceTree = ""; + }; FDFDE122282D04E30098B17F /* Transitions */ = { isa = PBXGroup; children = ( @@ -5644,6 +5667,7 @@ C3D9E4C02567767F0040E4F3 /* DataSource.m in Sources */, C32C5DD2256DD9E5003C73A2 /* LRUCache.swift in Sources */, FD71160228C8255900B47552 /* UIControl+Combine.swift in Sources */, + FDFBB74B2A1EFF4900CA7350 /* Bencode.swift in Sources */, FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */, FDF8487929405906007DCAE5 /* HTTPQueryParam.swift in Sources */, FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */, @@ -5758,6 +5782,7 @@ FD245C5A2850660100B966DD /* LinkPreviewDraft.swift in Sources */, FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */, FD6A7A6B2818C17C00035AC1 /* UpdateProfilePictureJob.swift in Sources */, + FDD82C3F2A205D0A00425F05 /* ProcessResult.swift in Sources */, FD716E6A2850327900C96BF4 /* EndCallMode.swift in Sources */, FDF0B75C2807F41D004C14C5 /* MessageSender+Convenience.swift in Sources */, 7B81682A28B6F1420069F315 /* ReactionResponse.swift in Sources */, @@ -5771,6 +5796,7 @@ FD245C57285065F100B966DD /* Poller.swift in Sources */, FDA8EAFE280E8B78002B68E5 /* FailedMessageSendsJob.swift in Sources */, FD245C6A2850666F00B966DD /* FileServerAPI.swift in Sources */, + FDFBB74D2A1F3C4E00CA7350 /* NotificationMetadata.swift in Sources */, FDC4386927B4E6B800C60D73 /* String+Utlities.swift in Sources */, FD716E6628502EE200C96BF4 /* CurrentCallProtocol.swift in Sources */, FD8ECF9029381FC200C0D1BB /* SessionUtil+UserProfile.swift in Sources */, @@ -5808,6 +5834,7 @@ FDC6D6F32860607300B04575 /* Environment.swift in Sources */, C3A3A171256E1D25004D228D /* SSKReachabilityManager.swift in Sources */, FD245C59285065FC00B966DD /* ControlMessage.swift in Sources */, + FD6E4C8A2A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift in Sources */, B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */, FD245C50285065C700B966DD /* VisibleMessage+Quote.swift in Sources */, FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */, @@ -6155,6 +6182,7 @@ FD2AAAF228ED57B500A49611 /* SynchronousStorage.swift in Sources */, FD078E4927E02576000769AF /* CommonMockedExtensions.swift in Sources */, FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */, + FDFBB7542A2023EB00CA7350 /* BencodeSpec.swift in Sources */, FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */, FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */, diff --git a/SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift b/SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift index 223a42e44..cbbc7e66a 100644 --- a/SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift +++ b/SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift @@ -24,6 +24,7 @@ public protocol SodiumType { public protocol AeadXChaCha20Poly1305IetfType { var KeyBytes: Int { get } var ABytes: Int { get } + var NonceBytes: Int { get } func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes? func decrypt(authenticatedCipherText: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes? diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift new file mode 100644 index 000000000..9a3633d85 --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift @@ -0,0 +1,47 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension PushNotificationAPI { + struct NotificationMetadata: Codable { + private enum CodingKeys: String, CodingKey { + case accountId = "@" + case hash = "#" + case namespace = "n" + case dataLength = "l" + case dataTooLong = "B" + } + + /// Account ID (such as Session ID or closed group ID) where the message arrived. + let accountId: String + + /// The hash of the message in the swarm. + let hash: String + + /// The swarm namespace in which this message arrived. + let namespace: Int + + /// The length of the message data. This is always included, even if the message content + /// itself was too large to fit into the push notification. + let dataLength: Int + + /// This will be `true` if the data was omitted because it was too long to fit in a push + /// notification (around 2.5kB of raw data), in which case the push notification includes + /// only this metadata but not the message content itself. + let dataTooLong: Bool + } +} + +extension PushNotificationAPI.NotificationMetadata { + init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + self = PushNotificationAPI.NotificationMetadata( + accountId: try container.decode(String.self, forKey: .accountId), + hash: try container.decode(String.self, forKey: .hash), + namespace: try container.decode(Int.self, forKey: .namespace), + dataLength: try container.decode(Int.self, forKey: .dataLength), + dataTooLong: ((try? container.decode(Bool.self, forKey: .dataTooLong)) ?? false) + ) + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index bb5d6ce8d..66e28ae9a 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -54,13 +54,13 @@ public enum PushNotificationAPI { } let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) - let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType] - .defaulting(to: Preferences.NotificationPreviewType.defaultPreviewType) - let request: SubscribeRequest = SubscribeRequest( pubkey: currentUserPublicKey, namespaces: [.default], - includeMessageData: (previewType == .nameAndPreview), // TODO: Test resubscribing when changing the type + // 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: SubscribeRequest.ServiceInfo( token: hexEncodedToken ), @@ -349,14 +349,6 @@ public enum PushNotificationAPI { ) -> AnyPublisher { let isUsingFullAPNs = UserDefaults.standard[.isUsingFullAPNs] - // TODO: Need to validate if this is actually desired behaviour - would this check prevent the app from unsubscribing if the user switches off fast mode??? (this is what the app is currently doing) - // TODO: This flag seems like it might actually be buggy... should double check it - guard isUsingFullAPNs else { - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - return PushNotificationAPI .send( request: PushNotificationAPIRequest( @@ -385,11 +377,70 @@ public enum PushNotificationAPI { .map { _ in () } .eraseToAnyPublisher() } + + // MARK: - Notification Handling + + public static func processNotification( + notificationContent: UNNotificationContent, + dependencies: SMKDependencies = SMKDependencies() + ) -> (envelope: SNProtoEnvelope?, result: ProcessResult) { + // Make sure the notification is from the updated push server + guard notificationContent.userInfo["spns"] != nil else { + guard + let base64EncodedData: String = notificationContent.userInfo["ENCRYPTED_DATA"] as? String, + let data: Data = Data(base64Encoded: base64EncodedData), + let envelope: SNProtoEnvelope = try? MessageWrapper.unwrap(data: data) + else { return (nil, .legacyFailure) } + + // We only support legacy notifications for legacy group conversations + guard envelope.type == .closedGroupMessage else { return (envelope, .legacyForceSilent) } + + return (envelope, .legacySuccess) + } + + guard + let base64EncodedEncString: String = notificationContent.userInfo["enc_payload"] as? String, + let encData: Data = Data(base64Encoded: base64EncodedEncString), + let notificationsEncryptionKey: Data = try? getOrGenerateEncryptionKey(), + encData.count > dependencies.aeadXChaCha20Poly1305Ietf.NonceBytes + else { return (nil, .failure) } + + let nonce: Data = encData[0.. = try? Bencode.decodeResponse(from: decryptedData) else { + return (nil, .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, .success) } + + // Check that the body we were given is valid + guard + let notificationData: Data = notification.data, + notification.info.dataLength == notificationData.count, + let envelope = try? MessageWrapper.unwrap(data: notificationData) + else { return (nil, .failure) } + + // Success, we have the notification content + return (envelope, .success) + } // MARK: - Security @discardableResult private static func getOrGenerateEncryptionKey() throws -> Data { - // TODO: May want to work this differently (will break after a phone restart if the device hasn't been unlocked yet) do { var encryptionKey: Data = try SSKDefaultKeychainStorage.shared.data( forService: keychainService, diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/ProcessResult.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Types/ProcessResult.swift new file mode 100644 index 000000000..1c72b1629 --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Types/ProcessResult.swift @@ -0,0 +1,13 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension PushNotificationAPI { + enum ProcessResult { + case success + case failure + case legacySuccess + case legacyFailure + case legacyForceSilent + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift index cc2e5157f..b9aeb904b 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift @@ -5,5 +5,6 @@ import Foundation extension PushNotificationAPI { enum Service: String, Codable { case apns + case sandbox = "apns-sandbox" // Use for push notifications in Testnet } } diff --git a/SessionMessagingKitTests/_TestUtilities/MockAeadXChaCha20Poly1305Ietf.swift b/SessionMessagingKitTests/_TestUtilities/MockAeadXChaCha20Poly1305Ietf.swift index cb3888b59..a90f118ca 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockAeadXChaCha20Poly1305Ietf.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockAeadXChaCha20Poly1305Ietf.swift @@ -8,6 +8,7 @@ import Sodium class MockAeadXChaCha20Poly1305Ietf: Mock, AeadXChaCha20Poly1305IetfType { var KeyBytes: Int = 32 var ABytes: Int = 16 + var NonceBytes: Int = 24 func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes? { return accept(args: [message, secretKey, nonce, additionalData]) as? Bytes diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 3a61aa8b3..741f17f27 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -57,12 +57,22 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension ) } + let (maybeEnvelope, result) = PushNotificationAPI.processNotification( + notificationContent: notificationContent + ) + guard - let base64EncodedData: String = notificationContent.userInfo["ENCRYPTED_DATA"] as? String, - let data: Data = Data(base64Encoded: base64EncodedData), - let envelope = try? MessageWrapper.unwrap(data: data) + (result == .success || result == .legacySuccess), + let envelope: SNProtoEnvelope = maybeEnvelope else { - return self.handleFailure(for: notificationContent) + switch result { + // If we got an explicit failure, or we got a success but no content then show + // the fallback notification + case .success, .legacySuccess, .failure, .legacyFailure: + return self.handleFailure(for: notificationContent) + + case .legacyForceSilent: return + } } // HACK: It is important to use write synchronously here to avoid a race condition diff --git a/SessionSnodeKit/Networking/OnionRequestAPI.swift b/SessionSnodeKit/Networking/OnionRequestAPI.swift index 538733eea..100a8d380 100644 --- a/SessionSnodeKit/Networking/OnionRequestAPI.swift +++ b/SessionSnodeKit/Networking/OnionRequestAPI.swift @@ -784,50 +784,15 @@ public enum OnionRequestAPI: OnionRequestAPIType { } public static func process(bencodedData data: Data) -> (info: ResponseInfoType, body: Data?)? { - // The data will be in the form of `l123:jsone` or `l123:json456:bodye` so we need to break - // the data into parts to properly process it - guard let responseString: String = String(data: data, encoding: .ascii), responseString.starts(with: "l") else { + guard let response: BencodeResponse = try? Bencode.decodeResponse(from: data) else { return nil } - let stringParts: [String.SubSequence] = responseString.split(separator: ":") - - guard stringParts.count > 1, let infoLength: Int = Int(stringParts[0].suffix(from: stringParts[0].index(stringParts[0].startIndex, offsetBy: 1))) else { - return nil - } - - let infoStringStartIndex: String.Index = responseString.index(responseString.startIndex, offsetBy: "l\(infoLength):".count) - let infoStringEndIndex: String.Index = responseString.index(infoStringStartIndex, offsetBy: infoLength) - let infoString: String = String(responseString[infoStringStartIndex.. "l\(infoLength)\(infoString)e".count else { - return (responseInfo, nil) - } - - // Extract the response data as well - let dataString: String = String(responseString.suffix(from: infoStringEndIndex)) - let dataStringParts: [String.SubSequence] = dataString.split(separator: ":") - - guard dataStringParts.count > 1, let finalDataLength: Int = Int(dataStringParts[0]), let suffixData: Data = "e".data(using: .utf8) else { - return nil - } - - let dataBytes: Array = Array(data) - let dataEndIndex: Int = (dataBytes.count - suffixData.count) - let dataStartIndex: Int = (dataEndIndex - finalDataLength) - let finalDataBytes: ArraySlice = dataBytes[dataStartIndex.. = Atomic( JobQueue( type: .blocking, + executionType: .serial, qos: .default, jobVariants: [], onQueueDrained: { @@ -85,6 +86,7 @@ public final class JobRunner { ) let attachmentDownloadQueue: JobQueue = JobQueue( type: .attachmentDownload, + executionType: .serial, qos: .utility, jobVariants: [ jobVariants.remove(.attachmentDownload) @@ -92,6 +94,7 @@ public final class JobRunner { ) let generalQueue: JobQueue = JobQueue( type: .general(number: 0), + executionType: .serial, qos: .utility, jobVariants: Array(jobVariants) ) @@ -509,7 +512,7 @@ private final class JobQueue { init( type: QueueType, - executionType: ExecutionType = .serial, + executionType: ExecutionType, qos: DispatchQoS, jobVariants: [Job.Variant], onQueueDrained: (() -> ())? = nil diff --git a/SessionUtilitiesKit/Utilities/Bencode.swift b/SessionUtilitiesKit/Utilities/Bencode.swift new file mode 100644 index 000000000..1138208cc --- /dev/null +++ b/SessionUtilitiesKit/Utilities/Bencode.swift @@ -0,0 +1,263 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public protocol BencodableType { + associatedtype ValueType: BencodableType + + static var isCollection: Bool { get } + static var isDictionary: Bool { get } +} + +public struct BencodeResponse { + public let info: T + public let data: Data? +} + +extension BencodeResponse: Equatable where T: Equatable {} + +public enum Bencode { + private enum Element: Character { + case number0 = "0" + case number1 = "1" + case number2 = "2" + case number3 = "3" + case number4 = "4" + case number5 = "5" + case number6 = "6" + case number7 = "7" + case number8 = "8" + case number9 = "9" + case intIndicator = "i" + case listIndicator = "l" + case dictIndicator = "d" + case endIndicator = "e" + case separator = ":" + + init?(_ byte: UInt8?) { + guard + let byte: UInt8 = byte, + let byteString: String = String(data: Data([byte]), encoding: .utf8), + let character: Character = byteString.first, + let result: Element = Element(rawValue: character) + else { return nil } + + self = result + } + } + + private struct BencodeString { + let value: String? + let rawValue: Data + } + + // MARK: - Functions + + public static func decodeResponse( + from data: Data, + using dependencies: Dependencies = Dependencies() + ) throws -> BencodeResponse where T: Decodable { + guard + let result: [Data] = try? decode([Data].self, from: data), + let responseData: Data = result.first + else { throw HTTPError.parsingFailed } + + return BencodeResponse( + info: try responseData.decoded(as: T.self, using: dependencies), + data: (result.count > 1 ? result.last : nil) + ) + } + + public static func decode(_ type: T.Type, from data: Data) throws -> T { + guard + let decodedData: (value: Any, remainingData: Data) = decodeData(data), + decodedData.remainingData.isEmpty == true // Ensure there is no left over data + else { throw HTTPError.parsingFailed } + + return try recursiveCast(type, from: decodedData.value) + } + + // MARK: - Logic + + private static func decodeData(_ data: Data) -> (value: Any, remainingData: Data)? { + switch Element(data.first) { + case .number0, .number1, .number2, .number3, .number4, + .number5, .number6, .number7, .number8, .number9: + return decodeString(data) + + case .intIndicator: return decodeInt(data) + case .listIndicator: return decodeList(data) + case .dictIndicator: return decodeDict(data) + default: return nil + } + } + + /// Decode a string element from iterator assumed to have structure `{length}:{data}` + private static func decodeString(_ data: Data) -> (value: BencodeString, remainingData: Data)? { + var mutableData: Data = data + var lengthData: [UInt8] = [] + + // Remove bytes until we hit the separator + while let next: UInt8 = mutableData.popFirst(), Element(next) != .separator { + lengthData.append(next) + } + + // Need to reset the index of the data (it maintains the index after popping/slicing) + // See https://forums.swift.org/t/data-subscript/57195 for more info + mutableData = Data(mutableData) + + guard + let lengthString: String = String(data: Data(lengthData), encoding: .ascii), + let length: Int = Int(lengthString, radix: 10), + mutableData.count >= length + else { return nil } + + // Need to reset the index of the data (it maintains the index after popping/slicing) + // See https://forums.swift.org/t/data-subscript/57195 for more info + return ( + BencodeString( + value: String(data: mutableData[0.. (value: Int, remainingData: Data)? { + var mutableData: Data = data + var intData: [UInt8] = [] + _ = mutableData.popFirst() // drop `i` + + // Pop until after `e` + while let next: UInt8 = mutableData.popFirst(), Element(next) != .endIndicator { + intData.append(next) + } + + guard + let intString: String = String(data: Data(intData), encoding: .ascii), + let result: Int = Int(intString, radix: 10) + else { return nil } + + // Need to reset the index of the data (it maintains the index after popping/slicing) + // See https://forums.swift.org/t/data-subscript/57195 for more info + return (result, Data(mutableData)) + } + + /// Decode a list element from iterator assumed to have structure `l{data}e` + private static func decodeList(_ data: Data) -> ([Any], Data)? { + var mutableData: Data = data + var listElements: [Any] = [] + _ = mutableData.popFirst() // drop `l` + + while !mutableData.isEmpty, let next: UInt8 = mutableData.first, Element(next) != .endIndicator { + guard let result = decodeData(mutableData) else { break } + + listElements.append(result.value) + mutableData = result.remainingData + } + + _ = mutableData.popFirst() // drop `e` + + // Need to reset the index of the data (it maintains the index after popping/slicing) + // See https://forums.swift.org/t/data-subscript/57195 for more info + return (listElements, Data(mutableData)) + } + + /// Decode a dict element from iterator assumed to have structure `d{data}e` + private static func decodeDict(_ data: Data) -> ([String: Any], Data)? { + var mutableData: Data = data + var dictElements: [String: Any] = [:] + _ = mutableData.popFirst() // drop `d` + + while !mutableData.isEmpty, let next: UInt8 = mutableData.first, Element(next) != .endIndicator { + guard + let keyResult = decodeString(mutableData), + let key: String = keyResult.value.value, + let valueResult = decodeData(keyResult.remainingData) + else { return nil } + + dictElements[key] = valueResult.value + mutableData = valueResult.remainingData + } + + _ = mutableData.popFirst() // drop `e` + + // Need to reset the index of the data (it maintains the index after popping/slicing) + // See https://forums.swift.org/t/data-subscript/57195 for more info + return (dictElements, Data(mutableData)) + } + + // MARK: - Internal Functions + + private static func recursiveCast(_ type: T.Type, from value: Any) throws -> T { + switch (type.isCollection, type.isDictionary) { + case (_, true): + guard let dictValue: [String: Any] = value as? [String: Any] else { throw HTTPError.parsingFailed } + + return try ( + dictValue.mapValues { try recursiveCast(type.ValueType.self, from: $0) } as? T ?? + { throw HTTPError.parsingFailed }() + ) + + case (true, _): + guard let arrayValue: [Any] = value as? [Any] else { throw HTTPError.parsingFailed } + + return try ( + arrayValue.map { try recursiveCast(type.ValueType.self, from: $0) } as? T ?? + { throw HTTPError.parsingFailed }() + ) + + default: + switch (value, type) { + case (let bencodeString as BencodeString, is String.Type): + return try (bencodeString.value as? T ?? { throw HTTPError.parsingFailed }()) + + case (let bencodeString as BencodeString, is Optional.Type): + return try (bencodeString.value as? T ?? { throw HTTPError.parsingFailed }()) + + case (let bencodeString as BencodeString, _): + return try (bencodeString.rawValue as? T ?? { throw HTTPError.parsingFailed }()) + + default: return try (value as? T ?? { throw HTTPError.parsingFailed }()) + } + } + } +} + +// MARK: - BencodableType Extensions + +extension Data: BencodableType { + public typealias ValueType = Data + + public static var isCollection: Bool { false } + public static var isDictionary: Bool { false } +} + +extension Int: BencodableType { + public typealias ValueType = Int + + public static var isCollection: Bool { false } + public static var isDictionary: Bool { false } +} + +extension String: BencodableType { + public typealias ValueType = String + + public static var isCollection: Bool { false } + public static var isDictionary: Bool { false } +} + +extension Array: BencodableType where Element: BencodableType { + public typealias ValueType = Element + + public static var isCollection: Bool { true } + public static var isDictionary: Bool { false } +} + +extension Dictionary: BencodableType where Key == String, Value: BencodableType { + public typealias ValueType = Value + + public static var isCollection: Bool { false } + public static var isDictionary: Bool { true } +} diff --git a/SessionUtilitiesKitTests/Utilities/BencodeSpec.swift b/SessionUtilitiesKitTests/Utilities/BencodeSpec.swift new file mode 100644 index 000000000..08f00df10 --- /dev/null +++ b/SessionUtilitiesKitTests/Utilities/BencodeSpec.swift @@ -0,0 +1,97 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +import Quick +import Nimble + +@testable import SessionUtilitiesKit + +class BencodeSpec: QuickSpec { + struct TestType: Codable, Equatable { + let intValue: Int + let stringValue: String + } + + // MARK: - Spec + + override func spec() { + describe("Bencode") { + context("when decoding") { + it("should decode a basic string") { + let basicStringData: Data = "5:howdy".data(using: .utf8)! + let result = try? Bencode.decode(String.self, from: basicStringData) + + expect(result).to(equal("howdy")) + } + + it("should decode a basic integer") { + let basicIntegerData: Data = "i3e".data(using: .utf8)! + let result = try? Bencode.decode(Int.self, from: basicIntegerData) + + expect(result).to(equal(3)) + } + + it("should decode a list of integers") { + let basicIntListData: Data = "li1ei2ee".data(using: .utf8)! + let result = try? Bencode.decode([Int].self, from: basicIntListData) + + expect(result).to(equal([1, 2])) + } + + it("should decode a basic dict") { + let basicDictData: Data = "d4:spaml1:a1:bee".data(using: .utf8)! + let result = try? Bencode.decode([String: [String]].self, from: basicDictData) + + expect(result).to(equal(["spam": ["a", "b"]])) + } + } + + context("when decoding a response") { + it("decodes successfully") { + let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e" + .data(using: .utf8)! + let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) + + expect(result) + .to(equal( + BencodeResponse( + info: TestType( + intValue: 100, + stringValue: "Test" + ), + data: Data([1, 2, 3, 4, 5]) + ) + )) + } + + it("decodes successfully with no body") { + let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}e" + .data(using: .utf8)! + let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) + + expect(result) + .to(equal( + BencodeResponse( + info: TestType( + intValue: 100, + stringValue: "Test" + ), + data: nil + ) + )) + } + + it("throws a parsing error when invalid") { + let data: Data = "l36:{\"INVALID\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e" + .data(using: .utf8)! + + expect { + let result: BencodeResponse = try Bencode.decodeResponse(from: data) + _ = result + }.to(throwError(HTTPError.parsingFailed)) + } + } + } + } +} From b3cad3e709587c346d7f7d037b33551f21d4b041 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 20 Jun 2023 12:31:03 +1000 Subject: [PATCH 04/17] Added in the new legacy endpoint --- Session.xcodeproj/project.pbxproj | 4 + .../Models/LegacyGroupOnlyRequest.swift | 12 +++ .../Notifications/PushNotificationAPI.swift | 77 ++++++++----------- .../Types/PushNotificationAPIEndpoint.swift | 5 +- .../General/SNUserDefaults.swift | 1 - 5 files changed, 50 insertions(+), 49 deletions(-) create mode 100644 SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupOnlyRequest.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 69124a8f7..3aeee3f0b 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -813,6 +813,7 @@ FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CC27BC641200C60D73 /* Set+Utilities.swift */; }; FDC6D6F32860607300B04575 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7542807C4BB004C14C5 /* Environment.swift */; }; FDC6D7602862B3F600B04575 /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC6D75F2862B3F600B04575 /* Dependencies.swift */; }; + FDCD2E032A41294E00964D6A /* LegacyGroupOnlyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */; }; FDCDB8DE2810F73B00352A0C /* Differentiable+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCDB8DD2810F73B00352A0C /* Differentiable+Utilities.swift */; }; FDCDB8E02811007F00352A0C /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCDB8DF2811007F00352A0C /* HomeViewModel.swift */; }; FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; }; @@ -1959,6 +1960,7 @@ FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMessageRequest.swift; sourceTree = ""; }; FDC438CC27BC641200C60D73 /* Set+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Utilities.swift"; sourceTree = ""; }; FDC6D75F2862B3F600B04575 /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = ""; }; + FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGroupOnlyRequest.swift; sourceTree = ""; }; FDCDB8DD2810F73B00352A0C /* Differentiable+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Differentiable+Utilities.swift"; sourceTree = ""; }; FDCDB8DF2811007F00352A0C /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DifferenceKit+Utilities.swift"; sourceTree = ""; }; @@ -4240,6 +4242,7 @@ FDC13D572A17207D007267C7 /* UnsubscribeResponse.swift */, FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */, FDC13D532A16FF29007267C7 /* LegacyGroupRequest.swift */, + FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */, FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */, FDC13D592A1721C5007267C7 /* LegacyNotifyRequest.swift */, FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */, @@ -5825,6 +5828,7 @@ FD8ECF8B2935DB4B00C0D1BB /* SharedConfigMessage.swift in Sources */, FD09798727FD1B7800936362 /* GroupMember.swift in Sources */, FDB4BBC92839BEF000B7C95D /* ProfileManagerError.swift in Sources */, + FDCD2E032A41294E00964D6A /* LegacyGroupOnlyRequest.swift in Sources */, FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */, FD09C5EC282B8F18000CE219 /* AttachmentError.swift in Sources */, FD17D79927F40AB800122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupOnlyRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupOnlyRequest.swift new file mode 100644 index 000000000..1a87dcf8e --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupOnlyRequest.swift @@ -0,0 +1,12 @@ +// 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/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index 66e28ae9a..befa6c45a 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -115,54 +115,39 @@ public enum PushNotificationAPI { } } ) - .flatMap { _ in - guard UserDefaults.standard[.hasUnregisteredForLegacyPushNotifications] != true else { - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - return PushNotificationAPI - .send( - request: PushNotificationAPIRequest( - endpoint: .legacyUnregister, - body: LegacyUnsubscribeRequest( - token: hexEncodedToken - ) - ) - ) - .retry(maxRetryCount) - .handleEvents( - receiveCompletion: { result in - switch result { - case .finished: - /// Save that we've already unsubscribed - /// - /// **Note:** The server can return an error (`response.code != 0`) but - /// that means the server properly processed the request and the error is likely - /// due to the device not actually being previously subscribed for notifications - /// rather than actually failing to unsubscribe - UserDefaults.standard[.hasUnregisteredForLegacyPushNotifications] = true - - case .failure: SNLog("Couldn't unsubscribe for legacy notifications.") - } - } - ) - .map { _ in () } - .eraseToAnyPublisher() - } - .eraseToAnyPublisher() - ].appending( + .map { _ in () } + .eraseToAnyPublisher(), // FIXME: Remove this once legacy groups are deprecated - contentsOf: legacyGroupIds - .map { legacyGroupId in - PushNotificationAPI.subscribeToLegacyGroup( - legacyGroupId: legacyGroupId, - currentUserPublicKey: currentUserPublicKey, - using: dependencies + PushNotificationAPI + .send( + request: PushNotificationAPIRequest( + endpoint: .legacyGroupsOnlySubscribe, + body: LegacyGroupOnlyRequest( + token: hexEncodedToken, + pubKey: currentUserPublicKey, + device: "ios", + legacyGroupPublicKeys: legacyGroupIds + ) ) - } - ) + ) + .decoded(as: LegacyPushServerResponse.self, using: dependencies) + .retry(maxRetryCount) + .handleEvents( + receiveOutput: { _, response in + guard response.code != 0 else { + return SNLog("Couldn't subscribe for legacy groups due to error: \(response.message ?? "nil").") + } + }, + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: SNLog("Couldn't subscribe for legacy groups.") + } + } + ) + .map { _ in () } + .eraseToAnyPublisher() + ] ) .collect() .map { _ in () } diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift index cc3068395..072abcc60 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift @@ -12,6 +12,7 @@ public extension PushNotificationAPI { case legacyNotify = "notify" case legacyRegister = "register" case legacyUnregister = "unregister" + case legacyGroupsOnlySubscribe = "register_legacy_groups_only" case legacyGroupSubscribe = "subscribe_closed_group" case legacyGroupUnsubscribe = "unsubscribe_closed_group" @@ -20,7 +21,7 @@ public extension PushNotificationAPI { var server: String { switch self { case .legacyNotify, .legacyRegister, .legacyUnregister, - .legacyGroupSubscribe, .legacyGroupUnsubscribe: + .legacyGroupsOnlySubscribe, .legacyGroupSubscribe, .legacyGroupUnsubscribe: return PushNotificationAPI.legacyServer default: return PushNotificationAPI.server @@ -30,7 +31,7 @@ public extension PushNotificationAPI { var serverPublicKey: String { switch self { case .legacyNotify, .legacyRegister, .legacyUnregister, - .legacyGroupSubscribe, .legacyGroupUnsubscribe: + .legacyGroupsOnlySubscribe, .legacyGroupSubscribe, .legacyGroupUnsubscribe: return PushNotificationAPI.legacyServerPublicKey default: return PushNotificationAPI.serverPublicKey diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index 910461a3d..82f18bd64 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -31,7 +31,6 @@ public enum SNUserDefaults { case hasSeenCallIPExposureWarning case hasSeenCallMissedTips case isUsingFullAPNs - case hasUnregisteredForLegacyPushNotifications case wasUnlinked case isMainAppActive case isCallOngoing From 01d77a515c36aae189eb3118209d0c9db9e66fb7 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 20 Jun 2023 12:42:54 +1000 Subject: [PATCH 05/17] Fixed a signature generation issue --- .../Notifications/Models/SubscribeRequest.swift | 8 ++++---- .../Notifications/Models/UnsubscribeRequest.swift | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift index 6c302a051..9417d232d 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift @@ -54,7 +54,7 @@ extension PushNotificationAPI { private let subkey: String? /// The signature unix timestamp (seconds, not ms) - private let timestamp: TimeInterval + private let timestamp: Int64 /// When the pubkey value starts with 05 (i.e. a session ID) this is the underlying ed25519 32-byte pubkey associated with the session /// ID. When not 05, this field should not be provided. @@ -82,7 +82,7 @@ extension PushNotificationAPI { self.serviceInfo = serviceInfo self.notificationsEncryptionKey = notificationsEncryptionKey self.subkey = subkey - self.timestamp = timestamp + self.timestamp = Int64(timestamp) // Server expects rounded seconds self.ed25519PublicKey = ed25519PublicKey self.ed25519SecretKey = ed25519SecretKey } @@ -99,7 +99,7 @@ extension PushNotificationAPI { try container.encodeIfPresent(subkey, forKey: .subkey) try container.encode(namespaces.map { $0.rawValue}.sorted(), forKey: .namespaces) try container.encode(includeMessageData, forKey: .includeMessageData) - try container.encode(Int64(timestamp), forKey: .timestamp) // Server expects rounded seconds + try container.encode(timestamp, forKey: .timestamp) try container.encode(signatureBase64, forKey: .signatureBase64) try container.encode(Service.apns, forKey: .service) try container.encode(serviceInfo, forKey: .serviceInfo) @@ -126,7 +126,7 @@ extension PushNotificationAPI { /// the `namespaces` parameter. let verificationBytes: [UInt8] = "MONITOR".bytes .appending(contentsOf: pubkey.bytes) - .appending(contentsOf: "\(Int64(timestamp))".bytes) // Server expects rounded seconds + .appending(contentsOf: "\(timestamp)".bytes) .appending(contentsOf: (includeMessageData ? "1" : "0").bytes) .appending( contentsOf: namespaces diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift index e26007fee..3d76f76ab 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift @@ -40,7 +40,7 @@ extension PushNotificationAPI { private let subkey: String? /// The signature unix timestamp (seconds, not ms) - private let timestamp: TimeInterval + private let timestamp: Int64 /// When the pubkey value starts with 05 (i.e. a session ID) this is the underlying ed25519 32-byte pubkey associated with the session /// ID. When not 05, this field should not be provided. @@ -62,7 +62,7 @@ extension PushNotificationAPI { self.pubkey = pubkey self.serviceInfo = serviceInfo self.subkey = subkey - self.timestamp = timestamp + self.timestamp = Int64(timestamp) // Server expects rounded seconds self.ed25519PublicKey = ed25519PublicKey self.ed25519SecretKey = ed25519SecretKey } From c7d090251a7f84c8f67acfa20bf54cf4fc6e8bbb Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 20 Jun 2023 15:21:30 +1000 Subject: [PATCH 06/17] Fixed an issue where subscribing to a new legacy group wouldn't have worked --- .../MessageReceiver+ClosedGroups.swift | 19 ++++-- .../MessageSender+ClosedGroups.swift | 15 ++++- .../Notifications/PushNotificationAPI.swift | 63 +++++++------------ 3 files changed, 50 insertions(+), 47 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index 2bfa9e813..9c3e9629e 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -192,11 +192,22 @@ extension MessageReceiver { // Start polling ClosedGroupPoller.shared.startIfNeeded(for: groupPublicKey) - // Subscribe for push notifications + // Resubscribe for group push notifications + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) + PushNotificationAPI - .subscribeToLegacyGroup( - legacyGroupId: groupPublicKey, - currentUserPublicKey: getUserHexEncodedPublicKey(db) + .subscribeToLegacyGroups( + currentUserPublicKey: currentUserPublicKey, + legacyGroupIds: try ClosedGroup + .select(.threadId) + .filter(!ClosedGroup.Columns.threadId.like("\(SessionId.Prefix.group.rawValue)%")) + .joining( + required: ClosedGroup.members + .filter(GroupMember.Columns.profileId == currentUserPublicKey) + ) + .asRequest(of: String.self) + .fetchSet(db) + .inserting(groupPublicKey) // Insert the new key just to be sure ) .sinkUntilComplete() } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index 9f4ed0dc2..9af3d016e 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -118,9 +118,18 @@ extension MessageSender { .map { MessageSender.sendImmediate(preparedSendData: $0) } .appending( // Subscribe for push notifications (if enabled) - PushNotificationAPI.subscribeToLegacyGroup( - legacyGroupId: groupPublicKey, - currentUserPublicKey: userPublicKey + PushNotificationAPI.subscribeToLegacyGroups( + currentUserPublicKey: userPublicKey, + legacyGroupIds: try ClosedGroup + .select(.threadId) + .filter(!ClosedGroup.Columns.threadId.like("\(SessionId.Prefix.group.rawValue)%")) + .joining( + required: ClosedGroup.members + .filter(GroupMember.Columns.profileId == userPublicKey) + ) + .asRequest(of: String.self) + .fetchSet(db) + .inserting(groupPublicKey) // Insert the new key just to be sure ) ) ) diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index befa6c45a..13af32d12 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -79,7 +79,7 @@ public enum PushNotificationAPI { .filter(!ClosedGroup.Columns.threadId.like("\(SessionId.Prefix.group.rawValue)%")) .joining( required: ClosedGroup.members - .filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db)) + .filter(GroupMember.Columns.profileId == currentUserPublicKey) ) .asRequest(of: String.self) .fetchSet(db) @@ -118,35 +118,12 @@ public enum PushNotificationAPI { .map { _ in () } .eraseToAnyPublisher(), // FIXME: Remove this once legacy groups are deprecated - PushNotificationAPI - .send( - request: PushNotificationAPIRequest( - endpoint: .legacyGroupsOnlySubscribe, - body: LegacyGroupOnlyRequest( - token: hexEncodedToken, - pubKey: currentUserPublicKey, - device: "ios", - legacyGroupPublicKeys: legacyGroupIds - ) - ) - ) - .decoded(as: LegacyPushServerResponse.self, using: dependencies) - .retry(maxRetryCount) - .handleEvents( - receiveOutput: { _, response in - guard response.code != 0 else { - return SNLog("Couldn't subscribe for legacy groups due to error: \(response.message ?? "nil").") - } - }, - receiveCompletion: { result in - switch result { - case .finished: break - case .failure: SNLog("Couldn't subscribe for legacy groups.") - } - } - ) - .map { _ in () } - .eraseToAnyPublisher() + PushNotificationAPI.subscribeToLegacyGroups( + forced: true, + token: hexEncodedToken, + currentUserPublicKey: currentUserPublicKey, + legacyGroupIds: legacyGroupIds + ) ] ) .collect() @@ -284,14 +261,20 @@ public enum PushNotificationAPI { // MARK: - Legacy Groups // FIXME: Remove this once legacy groups are deprecated - public static func subscribeToLegacyGroup( - legacyGroupId: String, + public static func subscribeToLegacyGroups( + forced: Bool = false, + token: String? = nil, currentUserPublicKey: String, + legacyGroupIds: Set, using dependencies: SSKDependencies = SSKDependencies() ) -> AnyPublisher { let isUsingFullAPNs = UserDefaults.standard[.isUsingFullAPNs] - guard isUsingFullAPNs else { + // Only continue if PNs are enabled and we have a device token + guard + (forced || isUsingFullAPNs), + let deviceToken: String = (token ?? UserDefaults.standard[.deviceToken]) + else { return Just(()) .setFailureType(to: Error.self) .eraseToAnyPublisher() @@ -300,10 +283,12 @@ public enum PushNotificationAPI { return PushNotificationAPI .send( request: PushNotificationAPIRequest( - endpoint: .legacyGroupSubscribe, - body: LegacyGroupRequest( + endpoint: .legacyGroupsOnlySubscribe, + body: LegacyGroupOnlyRequest( + token: deviceToken, pubKey: currentUserPublicKey, - closedGroupPublicKey: legacyGroupId + device: "ios", + legacyGroupPublicKeys: legacyGroupIds ) ) ) @@ -312,13 +297,13 @@ public enum PushNotificationAPI { .handleEvents( receiveOutput: { _, response in guard response.code != 0 else { - return SNLog("Couldn't subscribe for legacy group: \(legacyGroupId) due to error: \(response.message ?? "nil").") + return SNLog("Couldn't subscribe for legacy groups due to error: \(response.message ?? "nil").") } }, receiveCompletion: { result in switch result { case .finished: break - case .failure: SNLog("Couldn't subscribe for legacy group: \(legacyGroupId).") + case .failure: SNLog("Couldn't subscribe for legacy groups.") } } ) @@ -332,8 +317,6 @@ public enum PushNotificationAPI { currentUserPublicKey: String, using dependencies: SSKDependencies = SSKDependencies() ) -> AnyPublisher { - let isUsingFullAPNs = UserDefaults.standard[.isUsingFullAPNs] - return PushNotificationAPI .send( request: PushNotificationAPIRequest( From 66b94778e0af020cf7ba21701189b89b8fc6c2a7 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 1 Aug 2023 16:30:34 +1000 Subject: [PATCH 07/17] Fixed the build issues and a bug where a new legacy group wasn't subscribile --- .../PushRegistrationManager.swift | 9 +++---- Session/Notifications/SyncPushTokensJob.swift | 9 +++---- .../MessageSender+ClosedGroups.swift | 25 +++++++++++++------ .../ShareNavController.swift | 4 ++- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index 04d4a4e0b..a8ef2c345 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -128,10 +128,7 @@ public enum PushRegistrationError: Error { return true } - // FIXME: Might be nice to try to avoid having this required to run on the main thread (follow a similar approach to the 'SyncPushTokensJob' & `Atomic`?) private func registerForVanillaPushToken() -> AnyPublisher { - AssertIsOnMainThread() - // Use the existing publisher if it exists if let vanillaTokenPublisher: AnyPublisher = self.vanillaTokenPublisher { return vanillaTokenPublisher @@ -139,15 +136,17 @@ public enum PushRegistrationError: Error { .eraseToAnyPublisher() } - UIApplication.shared.registerForRemoteNotifications() - // No pending vanilla token yet; create a new publisher let publisher: AnyPublisher = Deferred { Future { self.vanillaTokenResolver = $0 } } + .shareReplay(1) .eraseToAnyPublisher() self.vanillaTokenPublisher = publisher + // Tell the device to register for remote notifications + DispatchQueue.main.sync { UIApplication.shared.registerForRemoteNotifications() } + return publisher .timeout( .seconds(10), diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 2df9657b3..0420bdb6a 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -58,7 +58,7 @@ public enum SyncPushTokensJob: JobExecutor { guard isUsingFullAPNs else { Just(Storage.shared[.lastRecordedPushToken]) .setFailureType(to: Error.self) - .flatMap { lastRecordedPushToken in + .flatMap { lastRecordedPushToken -> AnyPublisher in if let existingToken: String = lastRecordedPushToken { SNLog("[SyncPushTokensJob] Unregister using last recorded push token: \(redact(existingToken))") return Just(existingToken) @@ -71,7 +71,7 @@ public enum SyncPushTokensJob: JobExecutor { .map { token, _ in token } .eraseToAnyPublisher() } - .flatMap { pushToken in PushNotificationAPI.unregister(Data(hex: pushToken)) } + .flatMap { pushToken in PushNotificationAPI.unsubscribe(token: Data(hex: pushToken)) } .map { // Tell the device to unregister for remote notifications (essentially try to invalidate // the token if needed @@ -102,9 +102,8 @@ public enum SyncPushTokensJob: JobExecutor { PushRegistrationManager.shared.requestPushTokens() .flatMap { (pushToken: String, voipToken: String) -> AnyPublisher in PushNotificationAPI - .register( - with: Data(hex: pushToken), - publicKey: getUserHexEncodedPublicKey(), + .subscribe( + token: Data(hex: pushToken), isForcedUpdate: true ) .retry(3) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index 5cecfca71..c94831b18 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -16,7 +16,7 @@ extension MessageSender { members: Set ) -> AnyPublisher { Storage.shared - .writePublisher { db -> (String, SessionThread, [MessageSender.PreparedSendData]) in + .writePublisher { db -> (String, SessionThread, [MessageSender.PreparedSendData], Set) in let userPublicKey: String = getUserHexEncodedPublicKey(db) var members: Set = members @@ -111,21 +111,30 @@ extension MessageSender { interactionId: nil ) } + let allActiveLegacyGroupIds: Set = try ClosedGroup + .select(.threadId) + .filter(!ClosedGroup.Columns.threadId.like("\(SessionId.Prefix.group.rawValue)%")) + .joining( + required: ClosedGroup.members + .filter(GroupMember.Columns.profileId == userPublicKey) + ) + .asRequest(of: String.self) + .fetchSet(db) + .inserting(groupPublicKey) // Insert the new key just to be sure - return (userPublicKey, thread, memberSendData) + return (userPublicKey, thread, memberSendData, allActiveLegacyGroupIds) } - .flatMap { userPublicKey, thread, memberSendData in + .flatMap { userPublicKey, thread, memberSendData, allActiveLegacyGroupIds in Publishers .MergeMany( // Send a closed group update message to all members individually memberSendData .map { MessageSender.sendImmediate(preparedSendData: $0) } .appending( - // Notify the PN server - PushNotificationAPI.performOperation( - .subscribe, - for: thread.id, - publicKey: userPublicKey + // Resubscribe to all legacy groups + PushNotificationAPI.subscribeToLegacyGroups( + currentUserPublicKey: userPublicKey, + legacyGroupIds: allActiveLegacyGroupIds ) ) ) diff --git a/SessionShareExtension/ShareNavController.swift b/SessionShareExtension/ShareNavController.swift index 7b0c29ed5..07c698ccb 100644 --- a/SessionShareExtension/ShareNavController.swift +++ b/SessionShareExtension/ShareNavController.swift @@ -9,6 +9,7 @@ import SignalCoreKit final class ShareNavController: UINavigationController, ShareViewDelegate { public static var attachmentPrepPublisher: AnyPublisher<[SignalAttachment], Error>? + private let versionMigrationsComplete: Atomic = Atomic(false) // MARK: - Error @@ -84,7 +85,7 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { /// results in the `AppSetup` not actually running (and the UI not actually being loaded correctly) - in order to avoid this /// we call `checkIsAppReady` explicitly here assuming that either the `AppSetup` _hasn't_ complete or won't ever /// get run - checkIsAppReady() + checkIsAppReady(migrationsCompleted: versionMigrationsComplete.wrappedValue) } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { @@ -107,6 +108,7 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { } } + versionMigrationsComplete.mutate { $0 = true } checkIsAppReady(migrationsCompleted: true) } From 87668d86a1524c8ba984cbb3d6d0ac2273d77cd2 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 2 Aug 2023 14:35:59 +1000 Subject: [PATCH 08/17] Fixed an issue where the device might not reregister for push notifications --- Session/Notifications/SyncPushTokensJob.swift | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 6a5a1c245..5e83996b3 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -31,26 +31,6 @@ public enum SyncPushTokensJob: JobExecutor { return deferred(job, dependencies) } - // We need to check a UIApplication setting which needs to run on the main thread so synchronously - // retrieve the value so we can continue - let isRegisteredForRemoteNotifications: Bool = { - guard !Thread.isMainThread else { - return UIApplication.shared.isRegisteredForRemoteNotifications - } - - return DispatchQueue.main.sync { - return UIApplication.shared.isRegisteredForRemoteNotifications - } - }() - - // Apple's documentation states that we should re-register for notifications on every launch: - // https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/HandlingRemoteNotifications.html#//apple_ref/doc/uid/TP40008194-CH6-SW1 - guard job.behaviour == .runOnce || !isRegisteredForRemoteNotifications else { - SNLog("[SyncPushTokensJob] Deferred due to Fast Mode disabled") - deferred(job, dependencies) // Don't need to do anything if push notifications are already registered - return - } - // Determine if the device has 'Fast Mode' (APNS) enabled let isUsingFullAPNs: Bool = UserDefaults.standard[.isUsingFullAPNs] @@ -98,7 +78,10 @@ public enum SyncPushTokensJob: JobExecutor { return } - // Perform device registration + /// Perform device registration + /// + /// **Note:** Apple's documentation states that we should re-register for notifications on every launch: + /// https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/HandlingRemoteNotifications.html#//apple_ref/doc/uid/TP40008194-CH6-SW1 Logger.info("Re-registering for remote notifications.") PushRegistrationManager.shared.requestPushTokens() .flatMap { (pushToken: String, voipToken: String) -> AnyPublisher in From 0e952b40bb023a17c81529adbe7ddab641fbd11f Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 3 Aug 2023 09:09:33 +1000 Subject: [PATCH 09/17] Removed 'useSharedUtilForUserConfig' flag and most legacy config logic --- Session.xcodeproj/project.pbxproj | 8 - Session/Home/HomeVC.swift | 8 - Session/Meta/AppDelegate.swift | 31 -- Session/Onboarding/Onboarding.swift | 7 - SessionMessagingKit/Configuration.swift | 10 +- .../Database/LegacyDatabase/SMKLegacy.swift | 123 +------ .../Migrations/_003_YDBToGRDBMigration.swift | 8 - .../_014_GenerateInitialUserConfigDumps.swift | 2 - .../Jobs/Types/ConfigurationSyncJob.swift | 58 +--- .../ConfigurationMessage+Convenience.swift | 85 ----- .../ConfigurationMessage.swift | 313 +----------------- ...essageReceiver+ConfigurationMessages.swift | 235 ------------- .../Sending & Receiving/MessageReceiver.swift | 9 +- .../Notification+MessageReceiver.swift | 9 - .../Pollers/CurrentUserPoller.swift | 7 +- .../Config Handling/SessionUtil+Shared.swift | 11 +- .../QueryInterfaceRequest+Utilities.swift | 6 +- .../SessionUtil/SessionUtil.swift | 85 +---- .../Utilities/ProfileManager.swift | 25 +- .../General/SNUserDefaults.swift | 1 - SignalUtilitiesKit/Utilities/AppSetup.swift | 6 - 21 files changed, 29 insertions(+), 1018 deletions(-) delete mode 100644 SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift delete mode 100644 SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index cac7103f7..6795e47d3 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -128,7 +128,6 @@ 7B8C44C528B49DDA00FBE25F /* NewConversationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */; }; 7B8D5FC428332600008324D9 /* VisibleMessage+Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */; }; 7B93D06A27CF173D00811CB6 /* MessageRequestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */; }; - 7B93D07027CF194000811CB6 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */; }; 7B93D07127CF194000811CB6 /* MessageRequestResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06F27CF194000811CB6 /* MessageRequestResponse.swift */; }; 7B93D07727CF1A8A00811CB6 /* MockDataGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D07527CF1A8900811CB6 /* MockDataGenerator.swift */; }; 7B9F71C928470667006DFE7B /* ReactionListSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B9F71C828470667006DFE7B /* ReactionListSheet.swift */; }; @@ -652,7 +651,6 @@ FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */; }; FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */; }; FD5C72FD284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FC284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift */; }; - FD5C72FF284F0F120029977D /* MessageReceiver+ConfigurationMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FE284F0F120029977D /* MessageReceiver+ConfigurationMessages.swift */; }; FD5C7301284F0F7A0029977D /* MessageReceiver+UnsendRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C7300284F0F7A0029977D /* MessageReceiver+UnsendRequests.swift */; }; FD5C7303284F0FA50029977D /* MessageReceiver+Calls.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C7302284F0FA50029977D /* MessageReceiver+Calls.swift */; }; FD5C7305284F0FF30029977D /* MessageReceiver+VisibleMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C7304284F0FF30029977D /* MessageReceiver+VisibleMessages.swift */; }; @@ -1237,7 +1235,6 @@ 7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationVC.swift; sourceTree = ""; }; 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Reaction.swift"; sourceTree = ""; }; 7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = ""; }; - 7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = ""; }; 7B93D06F27CF194000811CB6 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = ""; }; 7B93D07527CF1A8900811CB6 /* MockDataGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockDataGenerator.swift; sourceTree = ""; }; 7B9F71C828470667006DFE7B /* ReactionListSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionListSheet.swift; sourceTree = ""; }; @@ -1771,7 +1768,6 @@ FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+TypingIndicators.swift"; sourceTree = ""; }; FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+DataExtractionNotification.swift"; sourceTree = ""; }; FD5C72FC284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ExpirationTimers.swift"; sourceTree = ""; }; - FD5C72FE284F0F120029977D /* MessageReceiver+ConfigurationMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ConfigurationMessages.swift"; sourceTree = ""; }; FD5C7300284F0F7A0029977D /* MessageReceiver+UnsendRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+UnsendRequests.swift"; sourceTree = ""; }; FD5C7302284F0FA50029977D /* MessageReceiver+Calls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+Calls.swift"; sourceTree = ""; }; FD5C7304284F0FF30029977D /* MessageReceiver+VisibleMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+VisibleMessages.swift"; sourceTree = ""; }; @@ -2730,7 +2726,6 @@ C34A977325A3E34A00852C71 /* ClosedGroupControlMessage.swift */, FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */, C3DA9C0625AE7396008F7C7E /* ConfigurationMessage.swift */, - 7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */, B8F5F60225EDE16F003BF8D4 /* DataExtractionNotification.swift */, C300A5E62554B07300555489 /* ExpirationTimerUpdate.swift */, 7B93D06F27CF194000811CB6 /* MessageRequestResponse.swift */, @@ -4002,7 +3997,6 @@ FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */, FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */, FD5C72FC284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift */, - FD5C72FE284F0F120029977D /* MessageReceiver+ConfigurationMessages.swift */, FD5C7300284F0F7A0029977D /* MessageReceiver+UnsendRequests.swift */, FD5C7302284F0FA50029977D /* MessageReceiver+Calls.swift */, FD5C7304284F0FF30029977D /* MessageReceiver+VisibleMessages.swift */, @@ -5864,7 +5858,6 @@ FD23CE222A661D000000B97C /* OpenGroupAPI+Crypto.swift in Sources */, FD245C652850665400B966DD /* ClosedGroupControlMessage.swift in Sources */, FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */, - 7B93D07027CF194000811CB6 /* ConfigurationMessage+Convenience.swift in Sources */, FD5C72FD284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift in Sources */, C32C5A88256DBCF9003C73A2 /* MessageReceiver+ClosedGroups.swift in Sources */, B8D0A25925E367AC00C1835E /* Notification+MessageReceiver.swift in Sources */, @@ -5874,7 +5867,6 @@ C32C599E256DB02B003C73A2 /* TypingIndicators.swift in Sources */, FD716E682850318E00C96BF4 /* CallMode.swift in Sources */, FD09799527FE7B8E00936362 /* Interaction.swift in Sources */, - FD5C72FF284F0F120029977D /* MessageReceiver+ConfigurationMessages.swift in Sources */, FD37EA0D28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift in Sources */, 7BAA7B6628D2DE4700AE1489 /* _009_OpenGroupPermission.swift in Sources */, FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */, diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 50a7b6151..21c87c49a 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -283,14 +283,6 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData // Start polling if needed (i.e. if the user just created or restored their Session ID) if Identity.userExists(), let appDelegate: AppDelegate = UIApplication.shared.delegate as? AppDelegate { appDelegate.startPollersIfNeeded() - - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - if !SessionUtil.userConfigsEnabled { - // Do this only if we created a new Session ID, or if we already received the initial configuration message - if UserDefaults.standard[.hasSyncedInitialConfiguration] { - appDelegate.syncConfigurationIfNeeded() - } - } } // Onion request path countries cache diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index a11d1f669..df4a88901 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -522,7 +522,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD startPollersIfNeeded() if CurrentAppContext().isMainApp { - syncConfigurationIfNeeded() handleAppActivatedWithOngoingCallIfNeeded() } } @@ -868,36 +867,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD presentingVC.present(callVC, animated: true, completion: nil) } - - // MARK: - Config Sync - - func syncConfigurationIfNeeded() { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard !SessionUtil.userConfigsEnabled else { return } - - let lastSync: Date = (UserDefaults.standard[.lastConfigurationSync] ?? .distantPast) - - guard Date().timeIntervalSince(lastSync) > (7 * 24 * 60 * 60) else { return } // Sync every 2 days - - Storage.shared - .writeAsync( - updates: { db in - ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db)) - }, - completion: { _, result in - switch result { - case .failure: break - case .success: - // Only update the 'lastConfigurationSync' timestamp if we have done the - // first sync (Don't want a new device config sync to override config - // syncs from other devices) - if UserDefaults.standard[.hasSyncedInitialConfiguration] { - UserDefaults.standard[.lastConfigurationSync] = Date() - } - } - } - ) - } } // MARK: - LifecycleMethod diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 123b2fd33..1a5956d29 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -30,13 +30,6 @@ enum Onboarding { _ requestId: UUID, using dependencies: Dependencies = Dependencies() ) -> AnyPublisher { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled else { - return Just(nil) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - let userPublicKey: String = getUserHexEncodedPublicKey() return SnodeAPI.getSwarm(for: userPublicKey) diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index 0aa4f5d37..314a5ca7e 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -31,14 +31,8 @@ public enum SNMessagingKit: MigratableTarget { // Just to make the external API _011_AddPendingReadReceipts.self, _012_AddFTSIfNeeded.self, _013_SessionUtilChanges.self, - // Wait until the feature is turned on before doing the migration that generates - // the config dump data - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - (Features.useSharedUtilForUserConfig(db) ? - _014_GenerateInitialUserConfigDumps.self : - (nil as Migration.Type?) - ) - ].compactMap { $0 } + _014_GenerateInitialUserConfigDumps.self + ] ] ) } diff --git a/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift b/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift index 7967974ac..f950fde8f 100644 --- a/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift +++ b/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift @@ -649,23 +649,18 @@ public enum SMKLegacy { @objc(SNConfigurationMessage) internal final class _ConfigurationMessage: _ControlMessage { - internal var closedGroups: Set<_CMClosedGroup> = [] - internal var openGroups: Set = [] internal var displayName: String? internal var profilePictureURL: String? internal var profileKey: Data? - internal var contacts: Set<_CMContact> = [] // MARK: NSCoding public required init?(coder: NSCoder) { super.init(coder: coder) - if let closedGroups = coder.decodeObject(forKey: "closedGroups") as! Set<_CMClosedGroup>? { self.closedGroups = closedGroups } - if let openGroups = coder.decodeObject(forKey: "openGroups") as! Set? { self.openGroups = openGroups } + if let displayName = coder.decodeObject(forKey: "displayName") as! String? { self.displayName = displayName } if let profilePictureURL = coder.decodeObject(forKey: "profilePictureURL") as! String? { self.profilePictureURL = profilePictureURL } if let profileKey = coder.decodeObject(forKey: "profileKey") as! Data? { self.profileKey = profileKey } - if let contacts = coder.decodeObject(forKey: "contacts") as! Set<_CMContact>? { self.contacts = contacts } } public override func encode(with coder: NSCoder) { @@ -679,126 +674,12 @@ public enum SMKLegacy { ConfigurationMessage( displayName: displayName, profilePictureUrl: profilePictureURL, - profileKey: profileKey, - closedGroups: closedGroups - .map { $0.toNonLegacy() } - .asSet(), - openGroups: openGroups, - contacts: contacts - .map { $0.toNonLegacy() } - .asSet() + profileKey: profileKey ) ) } } - // MARK: - Config Message Closed Group - - @objc(CMClosedGroup) - internal final class _CMClosedGroup: NSObject, NSCoding { - internal let publicKey: String - internal let name: String - internal let encryptionKeyPair: SUKLegacy.KeyPair - internal let members: Set - internal let admins: Set - internal let expirationTimer: UInt32 - - // MARK: NSCoding - - public required init?(coder: NSCoder) { - guard - let publicKey = coder.decodeObject(forKey: "publicKey") as! String?, - let name = coder.decodeObject(forKey: "name") as! String?, - let encryptionKeyPair = coder.decodeObject(forKey: "encryptionKeyPair") as! SUKLegacy.KeyPair?, - let members = coder.decodeObject(forKey: "members") as! Set?, - let admins = coder.decodeObject(forKey: "admins") as! Set? - else { return nil } - - self.publicKey = publicKey - self.name = name - self.encryptionKeyPair = encryptionKeyPair - self.members = members - self.admins = admins - self.expirationTimer = (coder.decodeObject(forKey: "expirationTimer") as? UInt32 ?? 0) - } - - public func encode(with coder: NSCoder) { - fatalError("encode(with:) should never be called for legacy types") - } - - // MARK: Non-Legacy Conversion - - internal func toNonLegacy() -> ConfigurationMessage.CMClosedGroup { - return ConfigurationMessage.CMClosedGroup( - publicKey: publicKey, - name: name, - encryptionKeyPublicKey: encryptionKeyPair.publicKey, - encryptionKeySecretKey: encryptionKeyPair.privateKey, - members: members, - admins: admins, - expirationTimer: expirationTimer - ) - } - } - - // MARK: - Config Message Contact - - @objc(SNConfigurationMessageContact) - internal final class _CMContact: NSObject, NSCoding { - internal var publicKey: String? - internal var displayName: String? - internal var profilePictureURL: String? - internal var profileKey: Data? - - internal var hasIsApproved: Bool - internal var isApproved: Bool - internal var hasIsBlocked: Bool - internal var isBlocked: Bool - internal var hasDidApproveMe: Bool - internal var didApproveMe: Bool - - // MARK: NSCoding - - public required init?(coder: NSCoder) { - guard - let publicKey = coder.decodeObject(forKey: "publicKey") as! String?, - let displayName = coder.decodeObject(forKey: "displayName") as! String? - else { return nil } - - self.publicKey = publicKey - self.displayName = displayName - self.profilePictureURL = coder.decodeObject(forKey: "profilePictureURL") as! String? - self.profileKey = coder.decodeObject(forKey: "profileKey") as! Data? - self.hasIsApproved = (coder.decodeObject(forKey: "hasIsApproved") as? Bool ?? false) - self.isApproved = (coder.decodeObject(forKey: "isApproved") as? Bool ?? false) - self.hasIsBlocked = (coder.decodeObject(forKey: "hasIsBlocked") as? Bool ?? false) - self.isBlocked = (coder.decodeObject(forKey: "isBlocked") as? Bool ?? false) - self.hasDidApproveMe = (coder.decodeObject(forKey: "hasDidApproveMe") as? Bool ?? false) - self.didApproveMe = (coder.decodeObject(forKey: "didApproveMe") as? Bool ?? false) - } - - public func encode(with coder: NSCoder) { - fatalError("encode(with:) should never be called for legacy types") - } - - // MARK: Non-Legacy Conversion - - internal func toNonLegacy() -> ConfigurationMessage.CMContact { - return ConfigurationMessage.CMContact( - publicKey: publicKey, - displayName: displayName, - profilePictureUrl: profilePictureURL, - profileKey: profileKey, - hasIsApproved: hasIsApproved, - isApproved: isApproved, - hasIsBlocked: hasIsBlocked, - isBlocked: isBlocked, - hasDidApproveMe: hasDidApproveMe, - didApproveMe: didApproveMe - ) - } - } - // MARK: - Unsend Request @objc(SNUnsendRequest) diff --git a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift index 8918c1c9b..4873cb107 100644 --- a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -1851,14 +1851,6 @@ enum _003_YDBToGRDBMigration: Migration { SMKLegacy._ConfigurationMessage.self, forClassName: "SNConfigurationMessage" ) - NSKeyedUnarchiver.setClass( - SMKLegacy._CMClosedGroup.self, - forClassName: "SNClosedGroup" - ) - NSKeyedUnarchiver.setClass( - SMKLegacy._CMContact.self, - forClassName: "SNConfigurationMessage.SNConfigurationMessageContact" - ) NSKeyedUnarchiver.setClass( SMKLegacy._UnsendRequest.self, forClassName: "SNUnsendRequest" diff --git a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift b/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift index 04b565056..442431a70 100644 --- a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift +++ b/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift @@ -6,8 +6,6 @@ import SessionUtil import SessionUtilitiesKit /// This migration goes through the current state of the database and generates config dumps for the user config types -/// -/// **Note:** This migration won't be run until the `useSharedUtilForUserConfig` feature flag is enabled enum _014_GenerateInitialUserConfigDumps: Migration { static let target: TargetMigrations.Identifier = .messagingKit static let identifier: String = "GenerateInitialUserConfigDumps" diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift index f19caf473..4b1d8011f 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -20,10 +20,7 @@ public enum ConfigurationSyncJob: JobExecutor { deferred: @escaping (Job, Dependencies) -> (), using dependencies: Dependencies ) { - guard - SessionUtil.userConfigsEnabled, - Identity.userCompletedRequiredOnboarding() - else { return success(job, true, dependencies) } + guard Identity.userCompletedRequiredOnboarding() else { return success(job, true, dependencies) } // It's possible for multiple ConfigSyncJob's with the same target (user/group) to try to run at the // same time since as soon as one is started we will enqueue a second one, rather than adding dependencies @@ -200,35 +197,6 @@ public extension ConfigurationSyncJob { publicKey: String, dependencies: Dependencies = Dependencies() ) { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled(db) else { - // If we don't have a userKeyPair (or name) yet then there is no need to sync the - // configuration as the user doesn't fully exist yet (this will get triggered on - // the first launch of a fresh install due to the migrations getting run and a few - // times during onboarding) - guard - Identity.userCompletedRequiredOnboarding(db), - let legacyConfigMessage: Message = try? ConfigurationMessage.getCurrent(db) - else { return } - - let publicKey: String = getUserHexEncodedPublicKey(db) - - dependencies.jobRunner.add( - db, - job: Job( - variant: .messageSend, - threadId: publicKey, - details: MessageSendJob.Details( - destination: Message.Destination.contact(publicKey: publicKey), - message: legacyConfigMessage - ) - ), - canStartJob: true, - using: dependencies - ) - return - } - // Upsert a config sync job if needed dependencies.jobRunner.upsert( db, @@ -268,30 +236,6 @@ public extension ConfigurationSyncJob { } static func run(using dependencies: Dependencies = Dependencies()) -> AnyPublisher { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled else { - return Storage.shared - .writePublisher { db -> MessageSender.PreparedSendData in - // If we don't have a userKeyPair yet then there is no need to sync the configuration - // as the user doesn't exist yet (this will get triggered on the first launch of a - // fresh install due to the migrations getting run) - guard Identity.userCompletedRequiredOnboarding(db) else { throw StorageError.generic } - - let publicKey: String = getUserHexEncodedPublicKey(db, using: dependencies) - - return try MessageSender.preparedSendData( - db, - message: try ConfigurationMessage.getCurrent(db), - to: Message.Destination.contact(publicKey: publicKey), - namespace: .default, - interactionId: nil, - using: dependencies - ) - } - .flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) } - .eraseToAnyPublisher() - } - // Trigger the job emitting the result when completed return Deferred { Future { resolver in diff --git a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift b/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift deleted file mode 100644 index 6c8c5977a..000000000 --- a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import GRDB -import SessionUtilitiesKit - -extension ConfigurationMessage { - public static func getCurrent(_ db: Database) throws -> ConfigurationMessage { - let currentUserProfile: Profile = Profile.fetchOrCreateCurrentUser(db) - let displayName: String = currentUserProfile.name - let profilePictureUrl: String? = currentUserProfile.profilePictureUrl - let profileKey: Data? = currentUserProfile.profileEncryptionKey - let closedGroups: Set = try ClosedGroup.fetchAll(db) - .compactMap { closedGroup -> CMClosedGroup? in - guard let latestKeyPair: ClosedGroupKeyPair = try closedGroup.fetchLatestKeyPair(db) else { - return nil - } - - return CMClosedGroup( - publicKey: closedGroup.publicKey, - name: closedGroup.name, - encryptionKeyPublicKey: latestKeyPair.publicKey, - encryptionKeySecretKey: latestKeyPair.secretKey, - members: try closedGroup.members - .select(GroupMember.Columns.profileId) - .asRequest(of: String.self) - .fetchSet(db), - admins: try closedGroup.admins - .select(GroupMember.Columns.profileId) - .asRequest(of: String.self) - .fetchSet(db), - expirationTimer: (try? DisappearingMessagesConfiguration - .fetchOne(db, id: closedGroup.threadId) - .map { ($0.isEnabled ? UInt32($0.durationSeconds) : 0) }) - .defaulting(to: 0) - ) - } - .asSet() - // The default room promise creates an OpenGroup with an empty `roomToken` value, - // we don't want to start a poller for this as the user hasn't actually joined a room - let openGroups: Set = try OpenGroup - .filter(OpenGroup.Columns.roomToken != "") - .filter(OpenGroup.Columns.isActive) - .fetchAll(db) - .compactMap { openGroup in - SessionUtil.communityUrlFor( - server: openGroup.server, - roomToken: openGroup.roomToken, - publicKey: openGroup.publicKey - ) - } - .asSet() - let contacts: Set = try Contact - .filter(Contact.Columns.id != currentUserProfile.id) - .fetchAll(db) - .map { contact -> CMContact in - // Can just default the 'hasX' values to true as they will be set to this - // when converting to proto anyway - let profile: Profile? = try? Profile.fetchOne(db, id: contact.id) - - return CMContact( - publicKey: contact.id, - displayName: (profile?.name ?? contact.id), - profilePictureUrl: profile?.profilePictureUrl, - profileKey: profile?.profileEncryptionKey, - hasIsApproved: true, - isApproved: contact.isApproved, - hasIsBlocked: true, - isBlocked: contact.isBlocked, - hasDidApproveMe: true, - didApproveMe: contact.didApproveMe - ) - } - .asSet() - - return ConfigurationMessage( - displayName: displayName, - profilePictureUrl: profilePictureUrl, - profileKey: profileKey, - closedGroups: closedGroups, - openGroups: openGroups, - contacts: contacts - ) - } -} diff --git a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage.swift b/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage.swift index 44977dd52..d161ef92e 100644 --- a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage.swift @@ -6,361 +6,80 @@ import SessionUtilitiesKit public final class ConfigurationMessage: ControlMessage { private enum CodingKeys: String, CodingKey { - case closedGroups - case openGroups case displayName case profilePictureUrl case profileKey - case contacts } - public var closedGroups: Set = [] - public var openGroups: Set = [] public var displayName: String? public var profilePictureUrl: String? public var profileKey: Data? - public var contacts: Set = [] public override var isSelfSendValid: Bool { true } - + // MARK: - Initialization public init( displayName: String?, profilePictureUrl: String?, - profileKey: Data?, - closedGroups: Set, - openGroups: Set, - contacts: Set + profileKey: Data? ) { super.init() - + self.displayName = displayName self.profilePictureUrl = profilePictureUrl self.profileKey = profileKey - self.closedGroups = closedGroups - self.openGroups = openGroups - self.contacts = contacts } - + // MARK: - Codable - + required init(from decoder: Decoder) throws { try super.init(from: decoder) - + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - - closedGroups = ((try? container.decode(Set.self, forKey: .closedGroups)) ?? []) - openGroups = ((try? container.decode(Set.self, forKey: .openGroups)) ?? []) + displayName = try? container.decode(String.self, forKey: .displayName) profilePictureUrl = try? container.decode(String.self, forKey: .profilePictureUrl) profileKey = try? container.decode(Data.self, forKey: .profileKey) - contacts = ((try? container.decode(Set.self, forKey: .contacts)) ?? []) } - + public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - - try container.encodeIfPresent(closedGroups, forKey: .closedGroups) - try container.encodeIfPresent(openGroups, forKey: .openGroups) + try container.encodeIfPresent(displayName, forKey: .displayName) try container.encodeIfPresent(profilePictureUrl, forKey: .profilePictureUrl) try container.encodeIfPresent(profileKey, forKey: .profileKey) - try container.encodeIfPresent(contacts, forKey: .contacts) } // MARK: - Proto Conversion public override class func fromProto(_ proto: SNProtoContent, sender: String) -> ConfigurationMessage? { guard let configurationProto = proto.configurationMessage else { return nil } + let displayName = configurationProto.displayName let profilePictureUrl = configurationProto.profilePicture let profileKey = configurationProto.profileKey - let closedGroups = Set(configurationProto.closedGroups.compactMap { CMClosedGroup.fromProto($0) }) - let openGroups = Set(configurationProto.openGroups) - let contacts = Set(configurationProto.contacts.compactMap { CMContact.fromProto($0) }) - + return ConfigurationMessage( displayName: displayName, profilePictureUrl: profilePictureUrl, - profileKey: profileKey, - closedGroups: closedGroups, - openGroups: openGroups, - contacts: contacts + profileKey: profileKey ) } - public override func toProto(_ db: Database) -> SNProtoContent? { - let configurationProto = SNProtoConfigurationMessage.builder() - if let displayName = displayName { configurationProto.setDisplayName(displayName) } - if let profilePictureUrl = profilePictureUrl { configurationProto.setProfilePicture(profilePictureUrl) } - if let profileKey = profileKey { configurationProto.setProfileKey(profileKey) } - configurationProto.setClosedGroups(closedGroups.compactMap { $0.toProto() }) - configurationProto.setOpenGroups([String](openGroups)) - configurationProto.setContacts(contacts.compactMap { $0.toProto() }) - let contentProto = SNProtoContent.builder() - do { - contentProto.setConfigurationMessage(try configurationProto.build()) - return try contentProto.build() - } catch { - SNLog("Couldn't construct configuration proto from: \(self).") - return nil - } - } + public override func toProto(_ db: Database) -> SNProtoContent? { return nil } // MARK: - Description public var description: String { """ - ConfigurationMessage( - closedGroups: \([CMClosedGroup](closedGroups).prettifiedDescription), - openGroups: \([String](openGroups).prettifiedDescription), + LegacyConfigurationMessage( displayName: \(displayName ?? "null"), profilePictureUrl: \(profilePictureUrl ?? "null"), - profileKey: \(profileKey?.toHexString() ?? "null"), - contacts: \([CMContact](contacts).prettifiedDescription) + profileKey: \(profileKey?.toHexString() ?? "null") ) """ } } - -// MARK: - Closed Group - -extension ConfigurationMessage { - public struct CMClosedGroup: Codable, Hashable, CustomStringConvertible { - private enum CodingKeys: String, CodingKey { - case publicKey - case name - case encryptionKeyPublicKey - case encryptionKeySecretKey - case members - case admins - case expirationTimer - } - - public let publicKey: String - public let name: String - public let encryptionKeyPublicKey: Data - public let encryptionKeySecretKey: Data - public let members: Set - public let admins: Set - public let expirationTimer: UInt32 - - public var isValid: Bool { !members.isEmpty && !admins.isEmpty } - - // MARK: - Initialization - - public init( - publicKey: String, - name: String, - encryptionKeyPublicKey: Data, - encryptionKeySecretKey: Data, - members: Set, - admins: Set, - expirationTimer: UInt32 - ) { - self.publicKey = publicKey - self.name = name - self.encryptionKeyPublicKey = encryptionKeyPublicKey - self.encryptionKeySecretKey = encryptionKeySecretKey - self.members = members - self.admins = admins - self.expirationTimer = expirationTimer - } - - // MARK: - Codable - - public init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - - publicKey = try container.decode(String.self, forKey: .publicKey) - name = try container.decode(String.self, forKey: .name) - encryptionKeyPublicKey = try container.decode(Data.self, forKey: .encryptionKeyPublicKey) - encryptionKeySecretKey = try container.decode(Data.self, forKey: .encryptionKeySecretKey) - members = try container.decode(Set.self, forKey: .members) - admins = try container.decode(Set.self, forKey: .admins) - expirationTimer = try container.decode(UInt32.self, forKey: .expirationTimer) - } - - public func encode(to encoder: Encoder) throws { - var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(publicKey, forKey: .publicKey) - try container.encode(name, forKey: .name) - try container.encode(encryptionKeyPublicKey, forKey: .encryptionKeyPublicKey) - try container.encode(encryptionKeySecretKey, forKey: .encryptionKeySecretKey) - try container.encode(members, forKey: .members) - try container.encode(admins, forKey: .admins) - try container.encode(expirationTimer, forKey: .expirationTimer) - } - - public static func fromProto(_ proto: SNProtoConfigurationMessageClosedGroup) -> CMClosedGroup? { - guard - let publicKey = proto.publicKey?.toHexString(), - let name = proto.name, - let encryptionKeyPairAsProto = proto.encryptionKeyPair - else { return nil } - - let members = Set(proto.members.map { $0.toHexString() }) - let admins = Set(proto.admins.map { $0.toHexString() }) - let expirationTimer = proto.expirationTimer - let result = CMClosedGroup( - publicKey: publicKey, - name: name, - encryptionKeyPublicKey: encryptionKeyPairAsProto.publicKey, - encryptionKeySecretKey: encryptionKeyPairAsProto.privateKey, - members: members, - admins: admins, - expirationTimer: expirationTimer - ) - - guard result.isValid else { return nil } - return result - } - - public func toProto() -> SNProtoConfigurationMessageClosedGroup? { - guard isValid else { return nil } - let result = SNProtoConfigurationMessageClosedGroup.builder() - result.setPublicKey(Data(hex: publicKey)) - result.setName(name) - do { - let encryptionKeyPairAsProto = try SNProtoKeyPair.builder( - publicKey: encryptionKeyPublicKey, - privateKey: encryptionKeySecretKey - ).build() - result.setEncryptionKeyPair(encryptionKeyPairAsProto) - } catch { - SNLog("Couldn't construct closed group proto from: \(self).") - return nil - } - result.setMembers(members.map { Data(hex: $0) }) - result.setAdmins(admins.map { Data(hex: $0) }) - result.setExpirationTimer(expirationTimer) - do { - return try result.build() - } catch { - SNLog("Couldn't construct closed group proto from: \(self).") - return nil - } - } - - public var description: String { name } - } -} - -// MARK: - Contact - -extension ConfigurationMessage { - public struct CMContact: Codable, Hashable, CustomStringConvertible { - private enum CodingKeys: String, CodingKey { - case publicKey - case displayName - case profilePictureUrl - case profileKey - - case hasIsApproved - case isApproved - case hasIsBlocked - case isBlocked - case hasDidApproveMe - case didApproveMe - } - - public var publicKey: String? - public var displayName: String? - public var profilePictureUrl: String? - public var profileKey: Data? - - public var hasIsApproved: Bool - public var isApproved: Bool - public var hasIsBlocked: Bool - public var isBlocked: Bool - public var hasDidApproveMe: Bool - public var didApproveMe: Bool - - public var isValid: Bool { publicKey != nil && displayName != nil } - - public init( - publicKey: String?, - displayName: String?, - profilePictureUrl: String?, - profileKey: Data?, - hasIsApproved: Bool, - isApproved: Bool, - hasIsBlocked: Bool, - isBlocked: Bool, - hasDidApproveMe: Bool, - didApproveMe: Bool - ) { - self.publicKey = publicKey - self.displayName = displayName - self.profilePictureUrl = profilePictureUrl - self.profileKey = profileKey - self.hasIsApproved = hasIsApproved - self.isApproved = isApproved - self.hasIsBlocked = hasIsBlocked - self.isBlocked = isBlocked - self.hasDidApproveMe = hasDidApproveMe - self.didApproveMe = didApproveMe - } - - // MARK: - Codable - - public init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - - publicKey = try? container.decode(String.self, forKey: .publicKey) - displayName = try? container.decode(String.self, forKey: .displayName) - profilePictureUrl = try? container.decode(String.self, forKey: .profilePictureUrl) - profileKey = try? container.decode(Data.self, forKey: .profileKey) - - hasIsApproved = try container.decode(Bool.self, forKey: .hasIsApproved) - isApproved = try container.decode(Bool.self, forKey: .isApproved) - hasIsBlocked = try container.decode(Bool.self, forKey: .hasIsBlocked) - isBlocked = try container.decode(Bool.self, forKey: .isBlocked) - hasDidApproveMe = try container.decode(Bool.self, forKey: .hasDidApproveMe) - didApproveMe = try container.decode(Bool.self, forKey: .didApproveMe) - } - - public static func fromProto(_ proto: SNProtoConfigurationMessageContact) -> CMContact? { - let result: CMContact = CMContact( - publicKey: proto.publicKey.toHexString(), - displayName: proto.name, - profilePictureUrl: proto.profilePicture, - profileKey: proto.profileKey, - hasIsApproved: proto.hasIsApproved, - isApproved: proto.isApproved, - hasIsBlocked: proto.hasIsBlocked, - isBlocked: proto.isBlocked, - hasDidApproveMe: proto.hasDidApproveMe, - didApproveMe: proto.didApproveMe - ) - - guard result.isValid else { return nil } - return result - } - - public func toProto() -> SNProtoConfigurationMessageContact? { - guard isValid else { return nil } - guard let publicKey = publicKey, let displayName = displayName else { return nil } - let result = SNProtoConfigurationMessageContact.builder(publicKey: Data(hex: publicKey), name: displayName) - if let profilePictureUrl = profilePictureUrl { result.setProfilePicture(profilePictureUrl) } - if let profileKey = profileKey { result.setProfileKey(profileKey) } - - if hasIsApproved { result.setIsApproved(isApproved) } - if hasIsBlocked { result.setIsBlocked(isBlocked) } - if hasDidApproveMe { result.setDidApproveMe(didApproveMe) } - - do { - return try result.build() - } catch { - SNLog("Couldn't construct contact proto from: \(self).") - return nil - } - } - - public var description: String { displayName ?? "" } - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift deleted file mode 100644 index 390bf8381..000000000 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import GRDB -import Sodium -import SessionUIKit -import SessionUtilitiesKit - -extension MessageReceiver { - internal static func handleLegacyConfigurationMessage( - _ db: Database, - message: ConfigurationMessage, - using dependencies: Dependencies - ) throws { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard !SessionUtil.userConfigsEnabled(db) else { - TopBannerController.show(warning: .outdatedUserConfig) - return - } - - let userPublicKey = getUserHexEncodedPublicKey(db) - - guard message.sender == userPublicKey else { return } - - SNLog("Configuration message received.") - - // Note: `message.sentTimestamp` is in ms (convert to TimeInterval before converting to - // seconds to maintain the accuracy) - let isInitialSync: Bool = (!UserDefaults.standard[.hasSyncedInitialConfiguration]) - let messageSentTimestamp: TimeInterval = TimeInterval((message.sentTimestamp ?? 0) / 1000) - let lastConfigTimestamp: TimeInterval = UserDefaults.standard[.lastConfigurationSync] - .defaulting(to: Date(timeIntervalSince1970: 0)) - .timeIntervalSince1970 - - // Handle user profile changes - try ProfileManager.updateProfileIfNeeded( - db, - publicKey: userPublicKey, - name: message.displayName, - avatarUpdate: { - guard - let profilePictureUrl: String = message.profilePictureUrl, - let profileKey: Data = message.profileKey - else { return .none } - - return .updateTo( - url: profilePictureUrl, - key: profileKey, - fileName: nil - ) - }(), - sentTimestamp: messageSentTimestamp, - calledFromConfigHandling: true, - using: dependencies - ) - - // Create a contact for the current user if needed (also force-approve the current user - // in case the account got into a weird state or restored directly from a migration) - let userContact: Contact = Contact.fetchOrCreate(db, id: userPublicKey) - - if !userContact.isTrusted || !userContact.isApproved || !userContact.didApproveMe { - try userContact.save(db) - try Contact - .filter(id: userPublicKey) - .updateAll( // Handling a config update so don't use `updateAllAndConfig` - db, - Contact.Columns.isTrusted.set(to: true), - Contact.Columns.isApproved.set(to: true), - Contact.Columns.didApproveMe.set(to: true) - ) - } - - if isInitialSync || messageSentTimestamp > lastConfigTimestamp { - if isInitialSync { - UserDefaults.standard[.hasSyncedInitialConfiguration] = true - NotificationCenter.default.post(name: .initialConfigurationMessageReceived, object: nil) - } - - UserDefaults.standard[.lastConfigurationSync] = Date(timeIntervalSince1970: messageSentTimestamp) - - // Contacts - try message.contacts.forEach { contactInfo in - guard let sessionId: String = contactInfo.publicKey else { return } - - // If the contact is a blinded contact then only add them if they haven't already been - // unblinded - if SessionId.Prefix(from: sessionId) == .blinded15 || SessionId.Prefix(from: sessionId) == .blinded25 { - let hasUnblindedContact: Bool = BlindedIdLookup - .filter(BlindedIdLookup.Columns.blindedId == sessionId) - .filter(BlindedIdLookup.Columns.sessionId != nil) - .isNotEmpty(db) - - if hasUnblindedContact { - return - } - } - - // Note: We only update the contact and profile records if the data has actually changed - // in order to avoid triggering UI updates for every thread on the home screen - let contact: Contact = Contact.fetchOrCreate(db, id: sessionId) - let profile: Profile = Profile.fetchOrCreate(db, id: sessionId) - - if - profile.name != contactInfo.displayName || - profile.profilePictureUrl != contactInfo.profilePictureUrl || - profile.profileEncryptionKey != contactInfo.profileKey - { - try profile.save(db) - try Profile - .filter(id: sessionId) - .updateAll( // Handling a config update so don't use `updateAllAndConfig` - db, - [ - Profile.Columns.name.set(to: contactInfo.displayName), - (contactInfo.profilePictureUrl == nil ? nil : - Profile.Columns.profilePictureUrl.set(to: contactInfo.profilePictureUrl) - ), - (contactInfo.profileKey == nil ? nil : - Profile.Columns.profileEncryptionKey.set(to: contactInfo.profileKey) - ) - ].compactMap { $0 } - ) - } - - /// We only update these values if the proto actually has values for them (this is to prevent an - /// edge case where an old client could override the values with default values since they aren't included) - /// - /// **Note:** Since message requests have no reverse, we should only handle setting `isApproved` - /// and `didApproveMe` to `true`. This may prevent some weird edge cases where a config message - /// swapping `isApproved` and `didApproveMe` to `false` - if - (contactInfo.hasIsApproved && (contact.isApproved != contactInfo.isApproved)) || - (contactInfo.hasIsBlocked && (contact.isBlocked != contactInfo.isBlocked)) || - (contactInfo.hasDidApproveMe && (contact.didApproveMe != contactInfo.didApproveMe)) - { - try contact.save(db) - try Contact - .filter(id: sessionId) - .updateAll( // Handling a config update so don't use `updateAllAndConfig` - db, - [ - (!contactInfo.hasIsApproved || !contactInfo.isApproved ? nil : - Contact.Columns.isApproved.set(to: true) - ), - (!contactInfo.hasIsBlocked ? nil : - Contact.Columns.isBlocked.set(to: contactInfo.isBlocked) - ), - (!contactInfo.hasDidApproveMe || !contactInfo.didApproveMe ? nil : - Contact.Columns.didApproveMe.set(to: contactInfo.didApproveMe) - ) - ].compactMap { $0 } - ) - } - - // If the contact is blocked - if contactInfo.hasIsBlocked && contactInfo.isBlocked { - // If this message changed them to the blocked state and there is an existing thread - // associated with them that is a message request thread then delete it (assume - // that the current user had deleted that message request) - if - contactInfo.isBlocked != contact.isBlocked, // 'contact.isBlocked' will be the old value - let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId), - thread.isMessageRequest(db) - { - _ = try thread.delete(db) - } - } - } - - // Closed groups - // - // Note: Only want to add these for initial sync to avoid re-adding closed groups the user - // intentionally left (any closed groups joined since the first processed sync message should - // get added via the 'handleNewClosedGroup' method anyway as they will have come through in the - // past two weeks) - if isInitialSync { - let existingClosedGroupsIds: [String] = (try? SessionThread - .filter(SessionThread.Columns.variant == SessionThread.Variant.legacyGroup) - .fetchAll(db)) - .defaulting(to: []) - .map { $0.id } - - try message.closedGroups.forEach { closedGroup in - guard !existingClosedGroupsIds.contains(closedGroup.publicKey) else { return } - - let keyPair: KeyPair = KeyPair( - publicKey: closedGroup.encryptionKeyPublicKey.bytes, - secretKey: closedGroup.encryptionKeySecretKey.bytes - ) - - try MessageReceiver.handleNewClosedGroup( - db, - groupPublicKey: closedGroup.publicKey, - name: closedGroup.name, - encryptionKeyPair: keyPair, - members: [String](closedGroup.members), - admins: [String](closedGroup.admins), - expirationTimer: closedGroup.expirationTimer, - formationTimestampMs: message.sentTimestamp!, - calledFromConfigHandling: false, // Legacy config isn't an issue - using: dependencies - ) - } - } - - // Open groups - for openGroupURL in message.openGroups { - if let (room, server, publicKey) = SessionUtil.parseCommunity(url: openGroupURL) { - let successfullyAddedGroup: Bool = OpenGroupManager.shared - .add( - db, - roomToken: room, - server: server, - publicKey: publicKey, - calledFromConfigHandling: true - ) - - if successfullyAddedGroup { - db.afterNextTransactionNested { _ in - OpenGroupManager.shared.performInitialRequestsAfterAdd( - successfullyAddedGroup: successfullyAddedGroup, - roomToken: room, - server: server, - publicKey: publicKey, - calledFromConfigHandling: false - ) - .subscribe(on: OpenGroupAPI.workQueue) - .sinkUntilComplete() - } - } - } - } - } - } -} diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index e1d1b8be8..5310c536b 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -3,6 +3,7 @@ import Foundation import GRDB import Sodium +import SessionUIKit import SessionUtilitiesKit import SessionSnodeKit @@ -242,13 +243,6 @@ public enum MessageReceiver { message: message ) - case let message as ConfigurationMessage: - try MessageReceiver.handleLegacyConfigurationMessage( - db, - message: message, - using: dependencies - ) - case let message as UnsendRequest: try MessageReceiver.handleUnsendRequest( db, @@ -282,6 +276,7 @@ public enum MessageReceiver { ) // SharedConfigMessages should be handled by the 'SharedUtil' instead of this + case is ConfigurationMessage: TopBannerController.show(warning: .outdatedUserConfig) case is SharedConfigMessage: throw MessageReceiverError.invalidSharedConfigMessageHandling default: fatalError() diff --git a/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift index d5c5b48fa..df890e31d 100644 --- a/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift @@ -3,18 +3,9 @@ import Foundation public extension Notification.Name { - - // FIXME: Remove once `useSharedUtilForUserConfig` is permanent - static let initialConfigurationMessageReceived = Notification.Name("initialConfigurationMessageReceived") static let missedCall = Notification.Name("missedCall") } public extension Notification.Key { static let senderId = Notification.Key("senderId") } - -@objc public extension NSNotification { - - // FIXME: Remove once `useSharedUtilForUserConfig` is permanent - @objc static let initialConfigurationMessageReceived = Notification.Name.initialConfigurationMessageReceived.rawValue as NSString -} diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift index 3e62ad28c..bf69dd878 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift @@ -14,12 +14,7 @@ public final class CurrentUserPoller: Poller { // MARK: - Settings - override var namespaces: [SnodeAPI.Namespace] { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled else { return [.default] } - - return CurrentUserPoller.namespaces - } + override var namespaces: [SnodeAPI.Namespace] { CurrentUserPoller.namespaces } /// After polling a given snode this many times we always switch to a new one. /// diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift index 909ea9ce7..25b2606e6 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift @@ -50,9 +50,6 @@ internal extension SessionUtil { publicKey: String, change: (UnsafeMutablePointer?) throws -> () ) throws { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled(db, ignoreRequirementsForRunningMigrations: true) else { return } - // Since we are doing direct memory manipulation we are using an `Atomic` // type which has blocking access in it's `mutate` closure let needsPush: Bool @@ -307,9 +304,6 @@ internal extension SessionUtil { targetConfig: ConfigDump.Variant, changeTimestampMs: Int64 ) -> Bool { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled(db) else { return true } - let targetPublicKey: String = { switch targetConfig { default: return getUserHexEncodedPublicKey(db) @@ -349,10 +343,7 @@ public extension SessionUtil { threadVariant: SessionThread.Variant, visibleOnly: Bool ) -> Bool { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled(db) else { return true } - - let userPublicKey: String = getUserHexEncodedPublicKey() + let userPublicKey: String = getUserHexEncodedPublicKey(db) let configVariant: ConfigDump.Variant = { switch threadVariant { case .contact: return (threadId == userPublicKey ? .userProfile : .contacts) diff --git a/SessionMessagingKit/SessionUtil/Database/QueryInterfaceRequest+Utilities.swift b/SessionMessagingKit/SessionUtil/Database/QueryInterfaceRequest+Utilities.swift index a8be039fe..a7285604e 100644 --- a/SessionMessagingKit/SessionUtil/Database/QueryInterfaceRequest+Utilities.swift +++ b/SessionMessagingKit/SessionUtil/Database/QueryInterfaceRequest+Utilities.swift @@ -91,11 +91,7 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table let updatedData: [RowDecoder] = try self.updateAndFetchAll(db, assignments.map { $0.assignment }) // Then check if any of the changes could affect the config - guard - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - SessionUtil.userConfigsEnabled(db, ignoreRequirementsForRunningMigrations: true), - SessionUtil.assignmentsRequireConfigUpdate(assignments) - else { return updatedData } + guard SessionUtil.assignmentsRequireConfigUpdate(assignments) else { return updatedData } defer { // If we changed a column that requires a config update then we may as well automatically diff --git a/SessionMessagingKit/SessionUtil/SessionUtil.swift b/SessionMessagingKit/SessionUtil/SessionUtil.swift index d933238a5..a353e8ed1 100644 --- a/SessionMessagingKit/SessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/SessionUtil/SessionUtil.swift @@ -6,25 +6,6 @@ import SessionSnodeKit import SessionUtil import SessionUtilitiesKit -// MARK: - Features - -public extension Features { - static func useSharedUtilForUserConfig(_ db: Database? = nil) -> Bool { - guard Date().timeIntervalSince1970 < 1690761600 else { return true } - guard !SessionUtil.hasCheckedMigrationsCompleted.wrappedValue else { - return SessionUtil.userConfigsEnabledIgnoringFeatureFlag - } - - if let db: Database = db { - return SessionUtil.refreshingUserConfigsEnabled(db) - } - - return Storage.shared - .read { db in SessionUtil.refreshingUserConfigsEnabled(db) } - .defaulting(to: false) - } -} - // MARK: - SessionUtil public enum SessionUtil { @@ -70,10 +51,7 @@ public enum SessionUtil { /// Returns `true` if there is a config which needs to be pushed, but returns `false` if the configs are all up to date or haven't been /// loaded yet (eg. fresh install) public static var needsSync: Bool { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled else { return false } - - return configStore + configStore .wrappedValue .contains { _, atomicConf in guard atomicConf.wrappedValue != nil else { return false } @@ -84,56 +62,6 @@ public enum SessionUtil { public static var libSessionVersion: String { String(cString: LIBSESSION_UTIL_VERSION_STR) } - fileprivate static let hasCheckedMigrationsCompleted: Atomic = Atomic(false) - private static let requiredMigrationsCompleted: Atomic = Atomic(false) - private static let requiredMigrationIdentifiers: Set = [ - TargetMigrations.Identifier.messagingKit.key(with: _013_SessionUtilChanges.self), - TargetMigrations.Identifier.messagingKit.key(with: _014_GenerateInitialUserConfigDumps.self) - ] - - public static var userConfigsEnabled: Bool { - return userConfigsEnabled(nil) - } - - public static func userConfigsEnabled(_ db: Database?) -> Bool { - Features.useSharedUtilForUserConfig(db) && - SessionUtil.userConfigsEnabledIgnoringFeatureFlag - } - - public static var userConfigsEnabledIgnoringFeatureFlag: Bool { - SessionUtil.requiredMigrationsCompleted.wrappedValue - } - - internal static func userConfigsEnabled( - _ db: Database, - ignoreRequirementsForRunningMigrations: Bool - ) -> Bool { - // First check if we are enabled regardless of what we want to ignore - guard - Features.useSharedUtilForUserConfig(db), - !SessionUtil.requiredMigrationsCompleted.wrappedValue, - !SessionUtil.refreshingUserConfigsEnabled(db), - ignoreRequirementsForRunningMigrations, - let currentlyRunningMigration: (identifier: TargetMigrations.Identifier, migration: Migration.Type) = Storage.shared.currentlyRunningMigration - else { return true } - - let nonIgnoredMigrationIdentifiers: Set = SessionUtil.requiredMigrationIdentifiers - .removing(currentlyRunningMigration.identifier.key(with: currentlyRunningMigration.migration)) - - return Storage.appliedMigrationIdentifiers(db) - .isSuperset(of: nonIgnoredMigrationIdentifiers) - } - - @discardableResult public static func refreshingUserConfigsEnabled(_ db: Database) -> Bool { - let result: Bool = Storage.appliedMigrationIdentifiers(db) - .isSuperset(of: SessionUtil.requiredMigrationIdentifiers) - - requiredMigrationsCompleted.mutate { $0 = result } - hasCheckedMigrationsCompleted.mutate { $0 = true } - - return result - } - internal static func lastError(_ conf: UnsafeMutablePointer?) -> String { return (conf?.pointee.last_error.map { String(cString: $0) } ?? "Unknown") } @@ -141,9 +69,6 @@ public enum SessionUtil { // MARK: - Loading public static func clearMemoryState() { - // Ensure we have a loaded state before we continue - guard !SessionUtil.configStore.wrappedValue.isEmpty else { return } - SessionUtil.configStore.mutate { confStore in confStore.removeAll() } @@ -169,9 +94,6 @@ public enum SessionUtil { return } - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled(db, ignoreRequirementsForRunningMigrations: true) else { return } - // Retrieve the existing dumps from the database let existingDumps: Set = ((try? ConfigDump.fetchSet(db)) ?? []) let existingDumpVariants: Set = existingDumps @@ -395,9 +317,6 @@ public enum SessionUtil { } public static func configHashes(for publicKey: String) -> [String] { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled else { return [] } - return Storage.shared .read { db -> Set in guard Identity.userExists(db) else { return [] } @@ -437,8 +356,6 @@ public enum SessionUtil { messages: [SharedConfigMessage], publicKey: String ) throws { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled(db) else { return } guard !messages.isEmpty else { return } guard !publicKey.isEmpty else { throw MessageReceiverError.noThread } diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 7266b30f6..e8407c8c7 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -509,8 +509,7 @@ public struct ProfileManager { // Name if let name: String = name, !name.isEmpty, name != profile.name { - // FIXME: Remove the `userConfigsEnabled` check once `useSharedUtilForUserConfig` is permanent - if sentTimestamp > profile.lastNameUpdate || (isCurrentUser && (calledFromConfigHandling || !SessionUtil.userConfigsEnabled(db))) { + if sentTimestamp > profile.lastNameUpdate || (isCurrentUser && calledFromConfigHandling) { profileChanges.append(Profile.Columns.name.set(to: name)) profileChanges.append(Profile.Columns.lastNameUpdate.set(to: sentTimestamp)) } @@ -520,8 +519,7 @@ public struct ProfileManager { var avatarNeedsDownload: Bool = false var targetAvatarUrl: String? = nil - // FIXME: Remove the `userConfigsEnabled` check once `useSharedUtilForUserConfig` is permanent - if sentTimestamp > profile.lastProfilePictureUpdate || (isCurrentUser && (calledFromConfigHandling || !SessionUtil.userConfigsEnabled(db))) { + if sentTimestamp > profile.lastProfilePictureUpdate || (isCurrentUser && calledFromConfigHandling) { switch avatarUpdate { case .none: break case .uploadImageData: preconditionFailure("Invalid options for this function") @@ -571,25 +569,6 @@ public struct ProfileManager { profileChanges ) } - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - else if !SessionUtil.userConfigsEnabled(db) { - // If we have a contact record for the profile (ie. it's a synced profile) then - // should should send an updated config message, otherwise we should just update - // the local state (the shared util has this logic build in to it's handling) - if (try? Contact.exists(db, id: publicKey)) == true { - try Profile - .filter(id: publicKey) - .updateAllAndConfig(db, profileChanges) - } - else { - try Profile - .filter(id: publicKey) - .updateAll( - db, - profileChanges - ) - } - } else { try Profile .filter(id: publicKey) diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index bc55e8231..7564c9753 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -37,7 +37,6 @@ public enum SNUserDefaults { } public enum Date: Swift.String { - case lastConfigurationSync case lastProfilePictureUpload case lastOpenGroupImageUpdate case lastOpen diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index 58f0c6856..f656ba41d 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -84,12 +84,6 @@ public enum AppSetup { ) } - // Refresh the migration state for 'SessionUtil' so it's logic can start running - // correctly when called (doing this here instead of automatically via the - // `SessionUtil.userConfigsEnabled` property to avoid having to use the correct - // method when calling within a database read/write closure) - Storage.shared.read { db in SessionUtil.refreshingUserConfigsEnabled(db) } - migrationsCompletion(result, (needsConfigSync || SessionUtil.needsSync)) // The 'if' is only there to prevent the "variable never read" warning from showing From 1a383ea850caae277aa9d3d3e2e7ab743a07e499 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 9 Aug 2023 15:22:42 +1000 Subject: [PATCH 10/17] Fixed a crash due to an assertion for encryption on the main thread Removed some commented out code --- .../PushRegistrationManager.swift | 8 ++++++-- SessionUtilitiesKit/JobRunner/JobRunner.swift | 20 ------------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index a8ef2c345..de1f6aee3 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -81,7 +81,9 @@ public enum PushRegistrationError: Error { return } - vanillaTokenResolver(Result.success(tokenData)) + DispatchQueue.global(qos: .default).async { + vanillaTokenResolver(Result.success(tokenData)) + } } // Vanilla push token is obtained from the system via AppDelegate @@ -92,7 +94,9 @@ public enum PushRegistrationError: Error { return } - vanillaTokenResolver(Result.failure(error)) + DispatchQueue.global(qos: .default).async { + vanillaTokenResolver(Result.failure(error)) + } } // MARK: helpers diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 69836b196..7c46c2730 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -681,26 +681,6 @@ public final class JobRunner: JobRunnerType { queues.wrappedValue[job.variant]?.removePendingJob(jobId) } - - //public static func hasPendingOrRunningJob( - // with variant: Job.Variant, - // threadId: String? = nil, - // interactionId: Int64? = nil, - // details: T? = nil - //) -> Bool { - // guard let targetQueue: JobQueue = queues.wrappedValue[variant] else { return false } - // - // // Ensure we can encode the details (if provided) - // let detailsData: Data? = details.map { try? JSONEncoder().encode($0) } - // - // guard details == nil || detailsData != nil else { return false } - // - // return targetQueue.hasPendingOrRunningJobWith( - // threadId: threadId, - // interactionId: interactionId, - // detailsData: detailsData - // ) - //} // MARK: - Convenience From 49f2d3bfe21a43b486ec34544e482b1cc673a3e7 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 9 Aug 2023 15:31:07 +1000 Subject: [PATCH 11/17] Removed another couple of main thread assertions --- Session/Notifications/PushRegistrationManager.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index de1f6aee3..b9d3f98c9 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -203,9 +203,8 @@ public enum PushRegistrationError: Error { } public func createVoipRegistryIfNecessary() { - AssertIsOnMainThread() - guard voipRegistry == nil else { return } + let voipRegistry = PKPushRegistry(queue: nil) self.voipRegistry = voipRegistry voipRegistry.desiredPushTypes = [.voIP] @@ -213,8 +212,6 @@ public enum PushRegistrationError: Error { } private func registerForVoipPushToken() -> AnyPublisher { - AssertIsOnMainThread() - // Use the existing publisher if it exists if let voipTokenPublisher: AnyPublisher = self.voipTokenPublisher { return voipTokenPublisher From 5285d8117777b6660fd2fd0888f9af78cb7a3844 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 10 Aug 2023 14:43:40 +1000 Subject: [PATCH 12/17] Fixed a few more PN logic issues Sorted out some more threading issues Removed a redundant SyncPushTokensJob run Fixed an issue where the NotificationServiceExtension could incorrectly setup the database before setting up it's context Fixed a few warnings Removed a bunch of legacy code Refactored the MainAppContext from Objective C into Swift --- Session.xcodeproj/project.pbxproj | 94 +--- .../Calls/Call Management/SessionCall.swift | 2 + .../SessionCallManager+Action.swift | 1 + .../Call Management/SessionCallManager.swift | 1 + .../Views & Modals/IncomingCallBanner.swift | 1 + .../Calls/Views & Modals/MiniCallView.swift | 1 + Session/Closed Groups/EditClosedGroupVC.swift | 1 + .../Context Menu/ContextMenuVC+Action.swift | 1 + .../Conversations/ConversationSearch.swift | 1 + .../ConversationVC+Interaction.swift | 1 + .../Input View/InputViewButton.swift | 1 + .../Message Cells/CallMessageCell.swift | 1 + .../Content Views/MediaAlbumView.swift | 1 + .../Content Views/MediaView.swift | 1 + .../Content Views/TypingIndicatorView.swift | 1 + .../Message Cells/MessageCell.swift | 1 + .../Settings/ThreadSettingsViewModel.swift | 1 + .../Views & Modals/ReactionListSheet.swift | 1 + Session/Emoji/Emoji+Available.swift | 1 + .../MessageRequestsViewController.swift | 1 + .../MessageRequestsViewModel.swift | 1 + Session/Home/New Conversation/NewDMVC.swift | 1 + .../CropScaleImageViewController.swift | 130 +++--- .../DocumentTitleViewController.swift | 1 + .../GIFs/GifPickerCell.swift | 1 + .../GIFs/GifPickerViewController.swift | 1 + .../GIFs/GiphyDownloader.swift | 1 + .../ImagePickerController.swift | 3 + .../MediaDetailViewController.swift | 1 + .../MediaPageViewController.swift | 2 + .../MediaTileViewController.swift | 1 + .../PhotoCaptureViewController.swift | 1 + .../PhotoLibrary.swift | 1 + .../SendMediaNavigationController.swift | 9 +- Session/Meta/AppDelegate.swift | 1 + Session/Meta/AppEnvironment.swift | 1 + Session/Meta/MainAppContext.h | 17 - Session/Meta/MainAppContext.m | 314 -------------- Session/Meta/MainAppContext.swift | 253 +++++++++++ Session/Meta/Signal-Bridging-Header.h | 1 - Session/Notifications/AppNotifications.swift | 2 + .../PushRegistrationManager.swift | 28 +- Session/Notifications/SyncPushTokensJob.swift | 40 +- .../UserNotificationsAdaptee.swift | 1 + Session/Onboarding/Onboarding.swift | 4 +- Session/Onboarding/PNModeVC.swift | 1 + Session/Onboarding/RegisterVC.swift | 1 + .../Settings/AppearanceViewController.swift | 1 + .../Settings/BlockedContactsViewModel.swift | 1 + Session/Settings/ImagePickerHandler.swift | 1 + Session/Settings/NukeDataModal.swift | 1 + Session/Shared/CaptionView.swift | 1 + Session/Shared/FullConversationCell.swift | 1 + .../Shared/Types/SessionCell+Styling.swift | 2 +- Session/Utilities/IP2Country.swift | 9 +- Session/Utilities/MockDataGenerator.swift | 1 + .../UIContextualAction+Utilities.swift | 1 + .../NSENotificationPresenter.swift | 1 + .../NotificationServiceExtension.swift | 35 +- .../NotificationServiceExtensionContext.swift | 9 +- .../ShareAppExtensionContext.swift | 7 +- SessionShareExtension/ThreadPickerVC.swift | 1 + .../Networking/OnionRequestAPI.swift | 2 +- SessionUtilitiesKit/General/AppContext.h | 9 - .../General/SNUserDefaults.swift | 4 +- SignalUtilitiesKit/Configuration.swift | 1 + .../AttachmentApprovalViewController.swift | 1 + .../AttachmentCaptionToolbar.swift | 22 +- .../AttachmentPrepViewController.swift | 2 + .../AttachmentTextToolbar.swift | 24 +- .../Image Editing/ImageEditorCanvasView.swift | 1 + .../ImageEditorCropViewController.swift | 5 +- .../Image Editing/ImageEditorModel.swift | 1 + .../ImageEditorPinchGestureRecognizer.swift | 1 + .../Image Editing/ImageEditorStrokeItem.swift | 1 + .../MediaMessageView.swift | 1 + SignalUtilitiesKit/Meta/SignalUtilitiesKit.h | 10 - .../Screen Lock/ScreenLock.swift | 14 +- SignalUtilitiesKit/Utilities/AppSetup.swift | 1 + SignalUtilitiesKit/Utilities/ByteParser.h | 40 -- SignalUtilitiesKit/Utilities/ByteParser.m | 143 ------ SignalUtilitiesKit/Utilities/FunctionalUtil.h | 27 -- SignalUtilitiesKit/Utilities/FunctionalUtil.m | 98 ----- .../NSURLSessionDataTask+StatusCode.h | 15 - .../NSURLSessionDataTask+StatusCode.m | 18 - SignalUtilitiesKit/Utilities/OWSError.h | 69 --- SignalUtilitiesKit/Utilities/OWSError.m | 68 --- SignalUtilitiesKit/Utilities/OWSOperation.h | 88 ---- SignalUtilitiesKit/Utilities/OWSOperation.m | 253 ----------- .../Utilities/ReachabilityManager.swift | 1 + .../Utilities/SignalIOS.pb.swift | 318 -------------- .../Utilities/SignalIOSProto.swift | 409 ------------------ .../Utilities/SwiftSingletons.swift | 1 + SignalUtilitiesKit/Utilities/TSConstants.h | 78 ---- SignalUtilitiesKit/Utilities/TSConstants.m | 20 - SignalUtilitiesKit/Utilities/UIView+OWS.swift | 1 + .../Utilities/UIViewController+OWS.swift | 1 + 97 files changed, 498 insertions(+), 2254 deletions(-) delete mode 100644 Session/Meta/MainAppContext.h delete mode 100644 Session/Meta/MainAppContext.m create mode 100644 Session/Meta/MainAppContext.swift delete mode 100644 SignalUtilitiesKit/Utilities/ByteParser.h delete mode 100644 SignalUtilitiesKit/Utilities/ByteParser.m delete mode 100644 SignalUtilitiesKit/Utilities/FunctionalUtil.h delete mode 100644 SignalUtilitiesKit/Utilities/FunctionalUtil.m delete mode 100644 SignalUtilitiesKit/Utilities/NSURLSessionDataTask+StatusCode.h delete mode 100644 SignalUtilitiesKit/Utilities/NSURLSessionDataTask+StatusCode.m delete mode 100644 SignalUtilitiesKit/Utilities/OWSError.h delete mode 100644 SignalUtilitiesKit/Utilities/OWSError.m delete mode 100644 SignalUtilitiesKit/Utilities/OWSOperation.h delete mode 100644 SignalUtilitiesKit/Utilities/OWSOperation.m delete mode 100644 SignalUtilitiesKit/Utilities/SignalIOS.pb.swift delete mode 100644 SignalUtilitiesKit/Utilities/SignalIOSProto.swift delete mode 100644 SignalUtilitiesKit/Utilities/TSConstants.h delete mode 100644 SignalUtilitiesKit/Utilities/TSConstants.m diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index b3fa2f5f6..ea1f920a9 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -19,7 +19,6 @@ 3496956021A2FC8100DCFE74 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3496955F21A2FC8100DCFE74 /* CloudKit.framework */; }; 34A6C28021E503E700B5B12E /* OWSImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */; }; 34A8B3512190A40E00218A25 /* MediaAlbumView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A8B3502190A40E00218A25 /* MediaAlbumView.swift */; }; - 34B0796D1FCF46B100E248C2 /* MainAppContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B0796B1FCF46B000E248C2 /* MainAppContext.m */; }; 34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */; }; 34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */; }; 34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2F1F7ABCF800D7438D /* GifPickerLayout.swift */; }; @@ -314,27 +313,13 @@ C33FDC29255A581F00E217F9 /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA6F255A57FA00E217F9 /* ReachabilityManager.swift */; }; C33FDC45255A581F00E217F9 /* AppVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA8B255A57FD00E217F9 /* AppVersion.m */; }; C33FDC58255A582000E217F9 /* ReverseDispatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */; }; - C33FDC78255A582000E217F9 /* TSConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDABE255A580100E217F9 /* TSConstants.m */; }; C33FDC98255A582000E217F9 /* SwiftSingletons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDADE255A580400E217F9 /* SwiftSingletons.swift */; }; - C33FDC9A255A582000E217F9 /* ByteParser.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAE0255A580400E217F9 /* ByteParser.m */; }; - C33FDCD1255A582000E217F9 /* FunctionalUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB17255A580800E217F9 /* FunctionalUtil.m */; }; - C33FDCFA255A582000E217F9 /* SignalIOSProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB40255A580C00E217F9 /* SignalIOSProto.swift */; }; C33FDD03255A582000E217F9 /* WeakTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB49255A580C00E217F9 /* WeakTimer.swift */; }; C33FDD06255A582000E217F9 /* AppVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB4C255A580D00E217F9 /* AppVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD23255A582000E217F9 /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB69255A580F00E217F9 /* FeatureFlags.swift */; }; - C33FDD32255A582000E217F9 /* OWSOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB78255A581000E217F9 /* OWSOperation.m */; }; C33FDD3A255A582000E217F9 /* Notification+Loki.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB80255A581100E217F9 /* Notification+Loki.swift */; }; C33FDD49255A582000E217F9 /* ParamParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB8F255A581200E217F9 /* ParamParser.swift */; }; - C33FDD5B255A582000E217F9 /* OWSOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBA1255A581400E217F9 /* OWSOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C33FDD6E255A582000E217F9 /* NSURLSessionDataTask+StatusCode.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBB4255A581600E217F9 /* NSURLSessionDataTask+StatusCode.m */; }; C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */; }; - C33FDD92255A582000E217F9 /* SignalIOS.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBD8255A581900E217F9 /* SignalIOS.pb.swift */; }; - C33FDDB0255A582000E217F9 /* NSURLSessionDataTask+StatusCode.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBF6255A581C00E217F9 /* NSURLSessionDataTask+StatusCode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C33FDDB3255A582000E217F9 /* OWSError.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBF9255A581C00E217F9 /* OWSError.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C33FDDBD255A582000E217F9 /* ByteParser.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDC03255A581D00E217F9 /* ByteParser.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C33FDDC5255A582000E217F9 /* OWSError.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDC0B255A581D00E217F9 /* OWSError.m */; }; - C33FDDCC255A582000E217F9 /* TSConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDC12255A581E00E217F9 /* TSConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C33FDDD0255A582000E217F9 /* FunctionalUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDC16255A581E00E217F9 /* FunctionalUtil.h */; settings = {ATTRIBUTES = (Public, ); }; }; C3402FE52559036600EA6424 /* SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C331FF1B2558F9D300070591 /* SessionUIKit.framework */; }; C3471ECB2555356A00297E91 /* MessageSender+Encryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3471ECA2555356A00297E91 /* MessageSender+Encryption.swift */; }; C3471F4C25553AB000297E91 /* MessageReceiver+Decryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3471F4B25553AB000297E91 /* MessageReceiver+Decryption.swift */; }; @@ -837,6 +822,7 @@ FDDCBDAD29E776BF00303C38 /* seed3-2023-2y.der in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA729E776BF00303C38 /* seed3-2023-2y.der */; }; 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 */; }; FDE658A129418C7900A33BC1 /* CryptoKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */; }; FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A229418E2F00A33BC1 /* KeyPair.swift */; }; FDE6E99829F8E63A00F93C5D /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE6E99729F8E63A00F93C5D /* Accessibility.swift */; }; @@ -1129,8 +1115,6 @@ 3496955F21A2FC8100DCFE74 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; 34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSImagePickerController.swift; sourceTree = ""; }; 34A8B3502190A40E00218A25 /* MediaAlbumView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaAlbumView.swift; sourceTree = ""; }; - 34B0796B1FCF46B000E248C2 /* MainAppContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainAppContext.m; sourceTree = ""; }; - 34B0796C1FCF46B000E248C2 /* MainAppContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainAppContext.h; sourceTree = ""; }; 34B0796E1FD07B1E00E248C2 /* SignalShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SignalShareExtension.entitlements; sourceTree = ""; }; 34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicatorView.swift; sourceTree = ""; }; 34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GifPickerViewController.swift; sourceTree = ""; }; @@ -1433,9 +1417,7 @@ C33FDA8E255A57FD00E217F9 /* OWSFileSystem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSFileSystem.m; sourceTree = ""; }; C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReverseDispatchQueue.swift; sourceTree = ""; }; C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BuildConfiguration.swift; sourceTree = ""; }; - C33FDABE255A580100E217F9 /* TSConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSConstants.m; sourceTree = ""; }; C33FDADE255A580400E217F9 /* SwiftSingletons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftSingletons.swift; sourceTree = ""; }; - C33FDAE0255A580400E217F9 /* ByteParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ByteParser.m; sourceTree = ""; }; C33FDAEF255A580500E217F9 /* NSData+Image.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Image.m"; sourceTree = ""; }; C33FDAF1255A580500E217F9 /* ThumbnailService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThumbnailService.swift; sourceTree = ""; }; C33FDAF2255A580500E217F9 /* ProxiedContentDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxiedContentDownloader.swift; sourceTree = ""; }; @@ -1443,7 +1425,6 @@ C33FDAFD255A580600E217F9 /* LRUCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LRUCache.swift; sourceTree = ""; }; C33FDB01255A580700E217F9 /* AppReadiness.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppReadiness.h; sourceTree = ""; }; C33FDB14255A580800E217F9 /* OWSMath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMath.h; sourceTree = ""; }; - C33FDB17255A580800E217F9 /* FunctionalUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FunctionalUtil.m; sourceTree = ""; }; C33FDB1C255A580900E217F9 /* UIImage+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+OWS.h"; sourceTree = ""; }; C33FDB22255A580900E217F9 /* OWSMediaUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSMediaUtils.swift; sourceTree = ""; }; C33FDB29255A580A00E217F9 /* NSData+Image.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Image.h"; sourceTree = ""; }; @@ -1451,7 +1432,6 @@ C33FDB38255A580B00E217F9 /* OWSBackgroundTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackgroundTask.h; sourceTree = ""; }; C33FDB3A255A580B00E217F9 /* Poller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Poller.swift; sourceTree = ""; }; C33FDB3F255A580C00E217F9 /* String+SSK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SSK.swift"; sourceTree = ""; }; - C33FDB40255A580C00E217F9 /* SignalIOSProto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalIOSProto.swift; sourceTree = ""; }; C33FDB41255A580C00E217F9 /* MIMETypeUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIMETypeUtil.m; sourceTree = ""; }; C33FDB49255A580C00E217F9 /* WeakTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakTimer.swift; sourceTree = ""; }; C33FDB4C255A580D00E217F9 /* AppVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppVersion.h; sourceTree = ""; }; @@ -1462,27 +1442,17 @@ C33FDB6B255A580F00E217F9 /* SNUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SNUserDefaults.swift; sourceTree = ""; }; C33FDB75255A581000E217F9 /* AppReadiness.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppReadiness.m; sourceTree = ""; }; C33FDB77255A581000E217F9 /* NSUserDefaults+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSUserDefaults+OWS.m"; sourceTree = ""; }; - C33FDB78255A581000E217F9 /* OWSOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOperation.m; sourceTree = ""; }; C33FDB80255A581100E217F9 /* Notification+Loki.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Notification+Loki.swift"; sourceTree = ""; }; C33FDB81255A581100E217F9 /* UIImage+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+OWS.m"; sourceTree = ""; }; C33FDB85255A581100E217F9 /* AppContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppContext.m; sourceTree = ""; }; C33FDB8A255A581200E217F9 /* AppContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppContext.h; sourceTree = ""; }; C33FDB8F255A581200E217F9 /* ParamParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParamParser.swift; sourceTree = ""; }; - C33FDBA1255A581400E217F9 /* OWSOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOperation.h; sourceTree = ""; }; C33FDBA8255A581500E217F9 /* LinkPreviewDraft.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkPreviewDraft.swift; sourceTree = ""; }; C33FDBAB255A581500E217F9 /* OWSFileSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSFileSystem.h; sourceTree = ""; }; - C33FDBB4255A581600E217F9 /* NSURLSessionDataTask+StatusCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLSessionDataTask+StatusCode.m"; sourceTree = ""; }; C33FDBB6255A581600E217F9 /* DataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataSource.m; sourceTree = ""; }; C33FDBBC255A581600E217F9 /* SSKKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSKKeychainStorage.swift; sourceTree = ""; }; C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSSignalAddress.swift; sourceTree = ""; }; - C33FDBD8255A581900E217F9 /* SignalIOS.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalIOS.pb.swift; sourceTree = ""; }; C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotificationAPI.swift; sourceTree = ""; }; - C33FDBF6255A581C00E217F9 /* NSURLSessionDataTask+StatusCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLSessionDataTask+StatusCode.h"; sourceTree = ""; }; - C33FDBF9255A581C00E217F9 /* OWSError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSError.h; sourceTree = ""; }; - C33FDC03255A581D00E217F9 /* ByteParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ByteParser.h; sourceTree = ""; }; - C33FDC0B255A581D00E217F9 /* OWSError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSError.m; sourceTree = ""; }; - C33FDC12255A581E00E217F9 /* TSConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSConstants.h; sourceTree = ""; }; - C33FDC16255A581E00E217F9 /* FunctionalUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FunctionalUtil.h; sourceTree = ""; }; C33FDC1B255A581F00E217F9 /* OWSBackgroundTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackgroundTask.m; sourceTree = ""; }; C3471ECA2555356A00297E91 /* MessageSender+Encryption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageSender+Encryption.swift"; sourceTree = ""; }; C3471F4B25553AB000297E91 /* MessageReceiver+Decryption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+Decryption.swift"; sourceTree = ""; }; @@ -1962,6 +1932,7 @@ FDDCBDA729E776BF00303C38 /* seed3-2023-2y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed3-2023-2y.der"; 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 = ""; }; FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CryptoKit+Utilities.swift"; sourceTree = ""; }; FDE658A229418E2F00A33BC1 /* KeyPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPair.swift; sourceTree = ""; }; FDE6E99729F8E63A00F93C5D /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = ""; }; @@ -2052,7 +2023,6 @@ FDFBB74A2A1EFF4900CA7350 /* Bencode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bencode.swift; sourceTree = ""; }; FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMetadata.swift; sourceTree = ""; }; FDFBB7532A2023EB00CA7350 /* BencodeSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BencodeSpec.swift; sourceTree = ""; }; - FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */ = {isa = PBXFileReference; explicitFileType = wrapper.xcframework; includeInIndex = 0; path = "libsession-util.xcframework"; sourceTree = BUILD_DIR; }; FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGeneralCache.swift; sourceTree = ""; }; FDFDE123282D04F20098B17F /* MediaDismissAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDismissAnimationController.swift; sourceTree = ""; }; @@ -3400,34 +3370,20 @@ FD71161D28D9772700B47552 /* UIViewController+OWS.swift */, C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */, C38EF307255B6DBE007E1867 /* UIGestureRecognizer+OWS.swift */, - C33FDBF9255A581C00E217F9 /* OWSError.h */, - C33FDC0B255A581D00E217F9 /* OWSError.m */, - C33FDBA1255A581400E217F9 /* OWSOperation.h */, - C33FDB78255A581000E217F9 /* OWSOperation.m */, C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */, C33FDA6F255A57FA00E217F9 /* ReachabilityManager.swift */, - C33FDBD8255A581900E217F9 /* SignalIOS.pb.swift */, - C33FDB40255A580C00E217F9 /* SignalIOSProto.swift */, - C33FDC12255A581E00E217F9 /* TSConstants.h */, - C33FDABE255A580100E217F9 /* TSConstants.m */, C33FDB4C255A580D00E217F9 /* AppVersion.h */, + C33FDA8B255A57FD00E217F9 /* AppVersion.m */, C38EF3E4255B6DF4007E1867 /* CommonStrings.swift */, C38EF304255B6DBE007E1867 /* ImageCache.swift */, C38EF2F2255B6DBC007E1867 /* Searcher.swift */, C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */, - C33FDA8B255A57FD00E217F9 /* AppVersion.m */, C33FDB69255A580F00E217F9 /* FeatureFlags.swift */, C33FDB80255A581100E217F9 /* Notification+Loki.swift */, - C33FDC16255A581E00E217F9 /* FunctionalUtil.h */, - C33FDB17255A580800E217F9 /* FunctionalUtil.m */, C33FDB8F255A581200E217F9 /* ParamParser.swift */, C33FDADE255A580400E217F9 /* SwiftSingletons.swift */, C33FDB49255A580C00E217F9 /* WeakTimer.swift */, C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */, - C33FDBF6255A581C00E217F9 /* NSURLSessionDataTask+StatusCode.h */, - C33FDBB4255A581600E217F9 /* NSURLSessionDataTask+StatusCode.m */, - C33FDC03255A581D00E217F9 /* ByteParser.h */, - C33FDAE0255A580400E217F9 /* ByteParser.m */, C38EF3DD255B6DF1007E1867 /* UIAlertController+OWS.swift */, C38EF241255B6D67007E1867 /* Collection+OWS.swift */, C38EF3AE255B6DE5007E1867 /* OrderedDictionary.swift */, @@ -3457,8 +3413,7 @@ 34330A581E7875FB00DF2FB9 /* Fonts */, B66DBF4919D5BBC8006EA940 /* Images.xcassets */, 45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */, - 34B0796C1FCF46B000E248C2 /* MainAppContext.h */, - 34B0796B1FCF46B000E248C2 /* MainAppContext.m */, + FDE125222A837E4E002DA685 /* MainAppContext.swift */, C3CA3AA0255CDA7000F4C6D4 /* Mnemonic */, B67EBF5C19194AC60084CCFD /* Settings.bundle */, B657DDC91911A40500F45B0C /* Signal.entitlements */, @@ -4495,12 +4450,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - C33FDDB0255A582000E217F9 /* NSURLSessionDataTask+StatusCode.h in Headers */, - C33FDDD0255A582000E217F9 /* FunctionalUtil.h in Headers */, - C33FDD5B255A582000E217F9 /* OWSOperation.h in Headers */, - C33FDDCC255A582000E217F9 /* TSConstants.h in Headers */, - C33FDDBD255A582000E217F9 /* ByteParser.h in Headers */, - C33FDDB3255A582000E217F9 /* OWSError.h in Headers */, C38EF35E255B6DCC007E1867 /* OWSViewController.h in Headers */, C33FD9AF255A548A00E217F9 /* SignalUtilitiesKit.h in Headers */, C33FDD06255A582000E217F9 /* AppVersion.h in Headers */, @@ -4801,6 +4750,7 @@ D221A080169C9E5E00537ABF /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; DefaultBuildSystemTypeForWorkspace = Original; LastSwiftUpdateCheck = 1430; LastTestingUpgradeCheck = 0600; @@ -5562,10 +5512,8 @@ C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */, C38EF363255B6DCC007E1867 /* ModalActivityIndicatorViewController.swift in Sources */, C38EF38A255B6DD2007E1867 /* AttachmentCaptionToolbar.swift in Sources */, - C33FDCD1255A582000E217F9 /* FunctionalUtil.m in Sources */, C38EF402255B6DF7007E1867 /* CommonStrings.swift in Sources */, C38EF3C1255B6DE7007E1867 /* ImageEditorBrushViewController.swift in Sources */, - C33FDD32255A582000E217F9 /* OWSOperation.m in Sources */, C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */, C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift in Sources */, C38EF388255B6DD2007E1867 /* AttachmentApprovalViewController.swift in Sources */, @@ -5573,7 +5521,6 @@ C33FDC29255A581F00E217F9 /* ReachabilityManager.swift in Sources */, C38EF407255B6DF7007E1867 /* Toast.swift in Sources */, C38EF38C255B6DD2007E1867 /* ApprovalRailCellView.swift in Sources */, - C33FDD92255A582000E217F9 /* SignalIOS.pb.swift in Sources */, C33FDC45255A581F00E217F9 /* AppVersion.m in Sources */, C38EF3C7255B6DE7007E1867 /* ImageEditorCanvasView.swift in Sources */, C38EF400255B6DF7007E1867 /* GalleryRailView.swift in Sources */, @@ -5582,7 +5529,6 @@ C38EF3BA255B6DE7007E1867 /* ImageEditorItem.swift in Sources */, C33FDD3A255A582000E217F9 /* Notification+Loki.swift in Sources */, C38EF24E255B6D67007E1867 /* Collection+OWS.swift in Sources */, - C33FDCFA255A582000E217F9 /* SignalIOSProto.swift in Sources */, C38EF24D255B6D67007E1867 /* UIView+OWS.swift in Sources */, C38EF38B255B6DD2007E1867 /* AttachmentPrepViewController.swift in Sources */, C38EF405255B6DF7007E1867 /* OWSButton.swift in Sources */, @@ -5598,9 +5544,7 @@ FD52090B28B59BB4006098F6 /* ScreenLockViewController.swift in Sources */, C38EF401255B6DF7007E1867 /* VideoPlayerView.swift in Sources */, C38EF3BD255B6DE7007E1867 /* ImageEditorTransform.swift in Sources */, - C33FDC9A255A582000E217F9 /* ByteParser.m in Sources */, C33FDC58255A582000E217F9 /* ReverseDispatchQueue.swift in Sources */, - C33FDC78255A582000E217F9 /* TSConstants.m in Sources */, C38EF324255B6DBF007E1867 /* Bench.swift in Sources */, FDCDB8DE2810F73B00352A0C /* Differentiable+Utilities.swift in Sources */, C38EF3F9255B6DF7007E1867 /* OWSLayerView.swift in Sources */, @@ -5612,12 +5556,10 @@ C38EF386255B6DD2007E1867 /* AttachmentApprovalInputAccessoryView.swift in Sources */, B8C2B2C82563685C00551B4D /* CircleView.swift in Sources */, C38EF331255B6DBF007E1867 /* UIGestureRecognizer+OWS.swift in Sources */, - C33FDDC5255A582000E217F9 /* OWSError.m in Sources */, FD848B9C284435D7000E298B /* AppSetup.swift in Sources */, C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */, C38EF2B3255B6D9C007E1867 /* UIViewController+Utilities.swift in Sources */, C38EF3BE255B6DE7007E1867 /* OrderedDictionary.swift in Sources */, - C33FDD6E255A582000E217F9 /* NSURLSessionDataTask+StatusCode.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6059,10 +6001,10 @@ B83F2B88240CB75A000A54AB /* UIImage+Scaling.swift in Sources */, 3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */, 4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */, + FDE125232A837E4E002DA685 /* MainAppContext.swift in Sources */, 7B9F71D12852EEE2006DFE7B /* EmojiWithSkinTones+String.swift in Sources */, 34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */, B886B4A92398BA1500211ABE /* QRCode.swift in Sources */, - 34B0796D1FCF46B100E248C2 /* MainAppContext.m in Sources */, 34A8B3512190A40E00218A25 /* MediaAlbumView.swift in Sources */, FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */, 3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */, @@ -6491,7 +6433,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 421; + CURRENT_PROJECT_VERSION = 422; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6515,7 +6457,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.3.2; + MARKETING_VERSION = 2.4.0; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -6563,7 +6505,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 421; + CURRENT_PROJECT_VERSION = 422; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6592,7 +6534,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.3.2; + MARKETING_VERSION = 2.4.0; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -6628,7 +6570,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 421; + CURRENT_PROJECT_VERSION = 422; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6651,7 +6593,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.3.2; + MARKETING_VERSION = 2.4.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6702,7 +6644,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 421; + CURRENT_PROJECT_VERSION = 422; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6730,7 +6672,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.3.2; + MARKETING_VERSION = 2.4.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -7662,7 +7604,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 421; + CURRENT_PROJECT_VERSION = 422; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7700,7 +7642,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 2.3.2; + MARKETING_VERSION = 2.4.0; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -7733,7 +7675,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 421; + CURRENT_PROJECT_VERSION = 422; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7771,7 +7713,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 2.3.2; + MARKETING_VERSION = 2.4.0; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 53b392657..e0b92096e 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -9,6 +9,8 @@ import WebRTC import SessionUIKit import SignalUtilitiesKit import SessionMessagingKit +import SessionUtilitiesKit +import SessionSnodeKit public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { @objc static let isEnabled = true diff --git a/Session/Calls/Call Management/SessionCallManager+Action.swift b/Session/Calls/Call Management/SessionCallManager+Action.swift index 6ac7c49cd..639c00130 100644 --- a/Session/Calls/Call Management/SessionCallManager+Action.swift +++ b/Session/Calls/Call Management/SessionCallManager+Action.swift @@ -2,6 +2,7 @@ import UIKit import GRDB +import SessionUtilitiesKit extension SessionCallManager { @discardableResult diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index c89cd5e23..81b3879a8 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -6,6 +6,7 @@ import GRDB import SessionMessagingKit import SignalCoreKit import SignalUtilitiesKit +import SessionUtilitiesKit public final class SessionCallManager: NSObject, CallManagerProtocol { let provider: CXProvider? diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index 7646903a2..221531644 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -4,6 +4,7 @@ import UIKit import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit +import SessionUtilitiesKit final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { private static let swipeToOperateThreshold: CGFloat = 60 diff --git a/Session/Calls/Views & Modals/MiniCallView.swift b/Session/Calls/Views & Modals/MiniCallView.swift index 8d060eed4..7c74a18df 100644 --- a/Session/Calls/Views & Modals/MiniCallView.swift +++ b/Session/Calls/Views & Modals/MiniCallView.swift @@ -3,6 +3,7 @@ import UIKit import WebRTC import SessionUIKit +import SessionUtilitiesKit final class MiniCallView: UIView, RTCVideoViewDelegate { var callVC: CallVC diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index 07475227c..2c408877e 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -7,6 +7,7 @@ import DifferenceKit import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit +import SessionUtilitiesKit final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate { private struct GroupMemberDisplayInfo: FetchableRecord, Equatable, Hashable, Decodable, Differentiable { diff --git a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift index 3fb3d0fb8..327a76c49 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift @@ -2,6 +2,7 @@ import UIKit import SessionMessagingKit +import SessionUtilitiesKit extension ContextMenuVC { struct Action { diff --git a/Session/Conversations/ConversationSearch.swift b/Session/Conversations/ConversationSearch.swift index 785578104..22c6539b4 100644 --- a/Session/Conversations/ConversationSearch.swift +++ b/Session/Conversations/ConversationSearch.swift @@ -5,6 +5,7 @@ import GRDB import SignalUtilitiesKit import SignalCoreKit import SessionUIKit +import SessionUtilitiesKit public class StyledSearchController: UISearchController { public override var preferredStatusBarStyle: UIStatusBarStyle { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index bb568d5dc..2b0def3f6 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -11,6 +11,7 @@ import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit +import SessionSnodeKit extension ConversationVC: InputViewDelegate, diff --git a/Session/Conversations/Input View/InputViewButton.swift b/Session/Conversations/Input View/InputViewButton.swift index 8bf158199..246510fed 100644 --- a/Session/Conversations/Input View/InputViewButton.swift +++ b/Session/Conversations/Input View/InputViewButton.swift @@ -2,6 +2,7 @@ import UIKit import SessionUIKit +import SessionUtilitiesKit final class InputViewButton: UIView { private let icon: UIImage? diff --git a/Session/Conversations/Message Cells/CallMessageCell.swift b/Session/Conversations/Message Cells/CallMessageCell.swift index ef88e2082..337862eb5 100644 --- a/Session/Conversations/Message Cells/CallMessageCell.swift +++ b/Session/Conversations/Message Cells/CallMessageCell.swift @@ -3,6 +3,7 @@ import UIKit import SessionUIKit import SessionMessagingKit +import SessionUtilitiesKit final class CallMessageCell: MessageCell { private static let iconSize: CGFloat = 16 diff --git a/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift b/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift index 87ee2f937..eb410d306 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift @@ -3,6 +3,7 @@ import UIKit import SessionMessagingKit import SignalCoreKit +import SessionUtilitiesKit public class MediaAlbumView: UIStackView { private let items: [Attachment] diff --git a/Session/Conversations/Message Cells/Content Views/MediaView.swift b/Session/Conversations/Message Cells/Content Views/MediaView.swift index 0af198314..838eb94d2 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaView.swift @@ -6,6 +6,7 @@ import SessionUIKit import SessionMessagingKit import SignalCoreKit import SignalUtilitiesKit +import SessionUtilitiesKit public class MediaView: UIView { static let contentMode: UIView.ContentMode = .scaleAspectFill diff --git a/Session/Conversations/Message Cells/Content Views/TypingIndicatorView.swift b/Session/Conversations/Message Cells/Content Views/TypingIndicatorView.swift index 9af77393f..08ccc733c 100644 --- a/Session/Conversations/Message Cells/Content Views/TypingIndicatorView.swift +++ b/Session/Conversations/Message Cells/Content Views/TypingIndicatorView.swift @@ -3,6 +3,7 @@ import UIKit import SessionUIKit import SignalCoreKit +import SessionUtilitiesKit @objc class TypingIndicatorView: UIStackView { // This represents the spacing between the dots diff --git a/Session/Conversations/Message Cells/MessageCell.swift b/Session/Conversations/Message Cells/MessageCell.swift index 63ccdf71d..e32f7fa7f 100644 --- a/Session/Conversations/Message Cells/MessageCell.swift +++ b/Session/Conversations/Message Cells/MessageCell.swift @@ -2,6 +2,7 @@ import UIKit import SessionMessagingKit +import SessionUtilitiesKit public enum SwipeState { case began diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 40792b3fc..24dcf2288 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -9,6 +9,7 @@ import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit import SessionUtilitiesKit +import SessionSnodeKit class ThreadSettingsViewModel: SessionTableViewModel { // MARK: - Config diff --git a/Session/Conversations/Views & Modals/ReactionListSheet.swift b/Session/Conversations/Views & Modals/ReactionListSheet.swift index e9e70e7b6..3ab37e2fa 100644 --- a/Session/Conversations/Views & Modals/ReactionListSheet.swift +++ b/Session/Conversations/Views & Modals/ReactionListSheet.swift @@ -5,6 +5,7 @@ import DifferenceKit import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit +import SessionUtilitiesKit final class ReactionListSheet: BaseVC { public struct ReactionSummary: Hashable, Differentiable { diff --git a/Session/Emoji/Emoji+Available.swift b/Session/Emoji/Emoji+Available.swift index 3619859c1..53df31d8a 100644 --- a/Session/Emoji/Emoji+Available.swift +++ b/Session/Emoji/Emoji+Available.swift @@ -1,5 +1,6 @@ import Foundation import SignalCoreKit +import SessionUtilitiesKit extension Emoji { private static let availableCache: Atomic<[Emoji:Bool]> = Atomic([:]) diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index 6b91630da..9684e44b1 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -6,6 +6,7 @@ import DifferenceKit import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit +import SessionUtilitiesKit class MessageRequestsViewController: BaseVC, SessionUtilRespondingViewController, UITableViewDelegate, UITableViewDataSource { private static let loadingHeaderHeight: CGFloat = 40 diff --git a/Session/Home/Message Requests/MessageRequestsViewModel.swift b/Session/Home/Message Requests/MessageRequestsViewModel.swift index 08e6eff32..f633e3b36 100644 --- a/Session/Home/Message Requests/MessageRequestsViewModel.swift +++ b/Session/Home/Message Requests/MessageRequestsViewModel.swift @@ -4,6 +4,7 @@ import Foundation import GRDB import DifferenceKit import SignalUtilitiesKit +import SessionUtilitiesKit public class MessageRequestsViewModel { public typealias SectionModel = ArraySection diff --git a/Session/Home/New Conversation/NewDMVC.swift b/Session/Home/New Conversation/NewDMVC.swift index e4765fee1..d369dd933 100644 --- a/Session/Home/New Conversation/NewDMVC.swift +++ b/Session/Home/New Conversation/NewDMVC.swift @@ -7,6 +7,7 @@ import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit +import SessionSnodeKit final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, QRScannerDelegate { private var shouldShowBackButton: Bool = true diff --git a/Session/Media Viewing & Editing/CropScaleImageViewController.swift b/Session/Media Viewing & Editing/CropScaleImageViewController.swift index df821b716..d307f9990 100644 --- a/Session/Media Viewing & Editing/CropScaleImageViewController.swift +++ b/Session/Media Viewing & Editing/CropScaleImageViewController.swift @@ -5,6 +5,7 @@ import MediaPlayer import SessionUIKit import SignalUtilitiesKit import SignalCoreKit +import SessionUtilitiesKit // This kind of view is tricky. I've tried to organize things in the // simplest possible way. @@ -359,54 +360,54 @@ import SignalCoreKit @objc func handlePinch(sender: UIPinchGestureRecognizer) { switch sender.state { - case .possible: - break - case .began: - srcTranslationAtPinchStart = srcTranslation - imageScaleAtPinchStart = imageScale + case .possible: break + case .began: + srcTranslationAtPinchStart = srcTranslation + imageScaleAtPinchStart = imageScale - lastPinchLocation = - sender.location(in: sender.view) - lastPinchScale = sender.scale - break - case .changed, .ended: - if sender.numberOfTouches > 1 { - let location = + lastPinchLocation = sender.location(in: sender.view) - let scaleDiff = sender.scale / lastPinchScale - - // Update scaling. - let srcCropSizeBeforeScalePoints = CGSize(width: srcDefaultCropSizePoints.width / imageScale, - height: srcDefaultCropSizePoints.height / imageScale) - imageScale = max(kMinImageScale, min(kMaxImageScale, imageScale * scaleDiff)) - let srcCropSizeAfterScalePoints = CGSize(width: srcDefaultCropSizePoints.width / imageScale, - height: srcDefaultCropSizePoints.height / imageScale) - // Since the translation state reflects the "upper left" corner of the crop region, we need to - // adjust the translation when scaling to preserve the "center" of the crop region. - srcTranslation.x += (srcCropSizeBeforeScalePoints.width - srcCropSizeAfterScalePoints.width) * 0.5 - srcTranslation.y += (srcCropSizeBeforeScalePoints.height - srcCropSizeAfterScalePoints.height) * 0.5 - - // Update translation. - let viewSizePoints = imageView.frame.size - let srcCropSizePoints = CGSize(width: srcDefaultCropSizePoints.width / imageScale, - height: srcDefaultCropSizePoints.height / imageScale) - - let viewToSrcRatio = srcCropSizePoints.width / viewSizePoints.width - - let gestureTranslation = CGPoint(x: location.x - lastPinchLocation.x, - y: location.y - lastPinchLocation.y) - - srcTranslation = CGPoint(x: srcTranslation.x + gestureTranslation.x * -viewToSrcRatio, - y: srcTranslation.y + gestureTranslation.y * -viewToSrcRatio) - - lastPinchLocation = location lastPinchScale = sender.scale - } - break - case .cancelled, .failed: - srcTranslation = srcTranslationAtPinchStart - imageScale = imageScaleAtPinchStart - break + + case .changed, .ended: + if sender.numberOfTouches > 1 { + let location = + sender.location(in: sender.view) + let scaleDiff = sender.scale / lastPinchScale + + // Update scaling. + let srcCropSizeBeforeScalePoints = CGSize(width: srcDefaultCropSizePoints.width / imageScale, + height: srcDefaultCropSizePoints.height / imageScale) + imageScale = max(kMinImageScale, min(kMaxImageScale, imageScale * scaleDiff)) + let srcCropSizeAfterScalePoints = CGSize(width: srcDefaultCropSizePoints.width / imageScale, + height: srcDefaultCropSizePoints.height / imageScale) + // Since the translation state reflects the "upper left" corner of the crop region, we need to + // adjust the translation when scaling to preserve the "center" of the crop region. + srcTranslation.x += (srcCropSizeBeforeScalePoints.width - srcCropSizeAfterScalePoints.width) * 0.5 + srcTranslation.y += (srcCropSizeBeforeScalePoints.height - srcCropSizeAfterScalePoints.height) * 0.5 + + // Update translation. + let viewSizePoints = imageView.frame.size + let srcCropSizePoints = CGSize(width: srcDefaultCropSizePoints.width / imageScale, + height: srcDefaultCropSizePoints.height / imageScale) + + let viewToSrcRatio = srcCropSizePoints.width / viewSizePoints.width + + let gestureTranslation = CGPoint(x: location.x - lastPinchLocation.x, + y: location.y - lastPinchLocation.y) + + srcTranslation = CGPoint(x: srcTranslation.x + gestureTranslation.x * -viewToSrcRatio, + y: srcTranslation.y + gestureTranslation.y * -viewToSrcRatio) + + lastPinchLocation = location + lastPinchScale = sender.scale + } + + case .cancelled, .failed: + srcTranslation = srcTranslationAtPinchStart + imageScale = imageScaleAtPinchStart + + @unknown default: break } updateImageLayout() @@ -416,29 +417,28 @@ import SignalCoreKit @objc func handlePan(sender: UIPanGestureRecognizer) { switch sender.state { - case .possible: - break - case .began: - srcTranslationAtPanStart = srcTranslation - break - case .changed, .ended: - let viewSizePoints = imageView.frame.size - let srcCropSizePoints = CGSize(width: srcDefaultCropSizePoints.width / imageScale, - height: srcDefaultCropSizePoints.height / imageScale) + case .possible: break + case .began: + srcTranslationAtPanStart = srcTranslation + + case .changed, .ended: + let viewSizePoints = imageView.frame.size + let srcCropSizePoints = CGSize(width: srcDefaultCropSizePoints.width / imageScale, + height: srcDefaultCropSizePoints.height / imageScale) - let viewToSrcRatio = srcCropSizePoints.width / viewSizePoints.width + let viewToSrcRatio = srcCropSizePoints.width / viewSizePoints.width - let gestureTranslation = - sender.translation(in: sender.view) + let gestureTranslation = + sender.translation(in: sender.view) - // Update translation. - srcTranslation = CGPoint(x: srcTranslationAtPanStart.x + gestureTranslation.x * -viewToSrcRatio, - y: srcTranslationAtPanStart.y + gestureTranslation.y * -viewToSrcRatio) - break - case .cancelled, .failed: - srcTranslation - = srcTranslationAtPanStart - break + // Update translation. + srcTranslation = CGPoint(x: srcTranslationAtPanStart.x + gestureTranslation.x * -viewToSrcRatio, + y: srcTranslationAtPanStart.y + gestureTranslation.y * -viewToSrcRatio) + + case .cancelled, .failed: + srcTranslation = srcTranslationAtPanStart + + @unknown default: break } updateImageLayout() diff --git a/Session/Media Viewing & Editing/DocumentTitleViewController.swift b/Session/Media Viewing & Editing/DocumentTitleViewController.swift index b651a62cc..12ba3248c 100644 --- a/Session/Media Viewing & Editing/DocumentTitleViewController.swift +++ b/Session/Media Viewing & Editing/DocumentTitleViewController.swift @@ -7,6 +7,7 @@ import DifferenceKit import SessionUIKit import SignalUtilitiesKit import SignalCoreKit +import SessionUtilitiesKit public class DocumentTileViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift index 0967e8020..20575c640 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift @@ -5,6 +5,7 @@ import Combine import YYImage import SignalUtilitiesKit import SignalCoreKit +import SessionUtilitiesKit class GifPickerCell: UICollectionViewCell { diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift index 4b2436258..d88e5bdb6 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift @@ -6,6 +6,7 @@ import Reachability import SignalUtilitiesKit import SessionUIKit import SignalCoreKit +import SessionUtilitiesKit class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegate, GifPickerLayoutDelegate { diff --git a/Session/Media Viewing & Editing/GIFs/GiphyDownloader.swift b/Session/Media Viewing & Editing/GIFs/GiphyDownloader.swift index 3dd50b0c0..3afd8d56a 100644 --- a/Session/Media Viewing & Editing/GIFs/GiphyDownloader.swift +++ b/Session/Media Viewing & Editing/GIFs/GiphyDownloader.swift @@ -2,6 +2,7 @@ import Foundation import SignalUtilitiesKit +import SessionUtilitiesKit public class GiphyDownloader: ProxiedContentDownloader { diff --git a/Session/Media Viewing & Editing/ImagePickerController.swift b/Session/Media Viewing & Editing/ImagePickerController.swift index 37fb7f4a8..6983de804 100644 --- a/Session/Media Viewing & Editing/ImagePickerController.swift +++ b/Session/Media Viewing & Editing/ImagePickerController.swift @@ -6,6 +6,7 @@ import Photos import SessionUIKit import SignalUtilitiesKit import SignalCoreKit +import SessionUtilitiesKit protocol ImagePickerGridControllerDelegate: AnyObject { func imagePickerDidCompleteSelection(_ imagePicker: ImagePickerGridController) @@ -155,6 +156,8 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat case .cancelled, .ended, .failed: collectionView.isUserInteractionEnabled = true collectionView.isScrollEnabled = true + + @unknown default: break } } diff --git a/Session/Media Viewing & Editing/MediaDetailViewController.swift b/Session/Media Viewing & Editing/MediaDetailViewController.swift index 1a7abeebe..f3eaf40e6 100644 --- a/Session/Media Viewing & Editing/MediaDetailViewController.swift +++ b/Session/Media Viewing & Editing/MediaDetailViewController.swift @@ -6,6 +6,7 @@ import SessionUIKit import SignalUtilitiesKit import SessionMessagingKit import SignalCoreKit +import SessionUtilitiesKit public enum MediaGalleryOption { case sliderEnabled diff --git a/Session/Media Viewing & Editing/MediaPageViewController.swift b/Session/Media Viewing & Editing/MediaPageViewController.swift index f0c725dae..1e14b2bdd 100644 --- a/Session/Media Viewing & Editing/MediaPageViewController.swift +++ b/Session/Media Viewing & Editing/MediaPageViewController.swift @@ -6,6 +6,8 @@ import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit import SignalCoreKit +import SessionUtilitiesKit +import SessionSnodeKit class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, MediaDetailViewControllerDelegate, InteractivelyDismissableViewController { class DynamicallySizedView: UIView { diff --git a/Session/Media Viewing & Editing/MediaTileViewController.swift b/Session/Media Viewing & Editing/MediaTileViewController.swift index 393e8411c..98d58dd5f 100644 --- a/Session/Media Viewing & Editing/MediaTileViewController.swift +++ b/Session/Media Viewing & Editing/MediaTileViewController.swift @@ -7,6 +7,7 @@ import DifferenceKit import SessionUIKit import SignalUtilitiesKit import SignalCoreKit +import SessionUtilitiesKit public class MediaTileViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { diff --git a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift index 91e43eb61..3dd46b425 100644 --- a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift +++ b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift @@ -6,6 +6,7 @@ import AVFoundation import SessionUIKit import SignalUtilitiesKit import SignalCoreKit +import SessionUtilitiesKit protocol PhotoCaptureViewControllerDelegate: AnyObject { func photoCaptureViewController(_ photoCaptureViewController: PhotoCaptureViewController, didFinishProcessingAttachment attachment: SignalAttachment) diff --git a/Session/Media Viewing & Editing/PhotoLibrary.swift b/Session/Media Viewing & Editing/PhotoLibrary.swift index 5bc29f7e5..0825a42cf 100644 --- a/Session/Media Viewing & Editing/PhotoLibrary.swift +++ b/Session/Media Viewing & Editing/PhotoLibrary.swift @@ -6,6 +6,7 @@ import Photos import CoreServices import SignalUtilitiesKit import SignalCoreKit +import SessionUtilitiesKit protocol PhotoLibraryDelegate: AnyObject { func photoLibraryDidChange(_ photoLibrary: PhotoLibrary) diff --git a/Session/Media Viewing & Editing/SendMediaNavigationController.swift b/Session/Media Viewing & Editing/SendMediaNavigationController.swift index 3ce3348fc..32cf44c16 100644 --- a/Session/Media Viewing & Editing/SendMediaNavigationController.swift +++ b/Session/Media Viewing & Editing/SendMediaNavigationController.swift @@ -6,6 +6,7 @@ import Photos import SignalUtilitiesKit import SignalCoreKit import SessionUIKit +import SessionUtilitiesKit class SendMediaNavigationController: UINavigationController { public override var preferredStatusBarStyle: UIStatusBarStyle { @@ -539,8 +540,8 @@ private struct MediaLibrarySelection: Hashable, Equatable { let asset: PHAsset let signalAttachmentPublisher: AnyPublisher - var hashValue: Int { - return asset.hashValue + func hash(into hasher: inout Hasher) { + asset.hash(into: &hasher) } var publisher: AnyPublisher { @@ -559,8 +560,8 @@ private struct MediaLibraryAttachment: Hashable, Equatable { let asset: PHAsset let signalAttachment: SignalAttachment - public var hashValue: Int { - return asset.hashValue + func hash(into hasher: inout Hasher) { + asset.hash(into: &hasher) } public static func == (lhs: MediaLibraryAttachment, rhs: MediaLibraryAttachment) -> Bool { diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index a11d1f669..59bbe4914 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -9,6 +9,7 @@ import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit import SignalCoreKit +import SessionSnodeKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { diff --git a/Session/Meta/AppEnvironment.swift b/Session/Meta/AppEnvironment.swift index afcb1e868..35e906c9f 100644 --- a/Session/Meta/AppEnvironment.swift +++ b/Session/Meta/AppEnvironment.swift @@ -4,6 +4,7 @@ import Foundation import SessionUtilitiesKit import SignalUtilitiesKit import SignalCoreKit +import SessionMessagingKit public class AppEnvironment { diff --git a/Session/Meta/MainAppContext.h b/Session/Meta/MainAppContext.h deleted file mode 100644 index 6fab6a1ad..000000000 --- a/Session/Meta/MainAppContext.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const ReportedApplicationStateDidChangeNotification; - -@interface MainAppContext : NSObject - -- (instancetype)init; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Session/Meta/MainAppContext.m b/Session/Meta/MainAppContext.m deleted file mode 100644 index 21daaa5f2..000000000 --- a/Session/Meta/MainAppContext.m +++ /dev/null @@ -1,314 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "MainAppContext.h" -#import "Session-Swift.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplicationStateDidChangeNotification"; - -@interface MainAppContext () - -@property (atomic) UIApplicationState reportedApplicationState; - -@property (nonatomic, nullable) NSMutableArray *appActiveBlocks; - -@end - -#pragma mark - - -@implementation MainAppContext - -@synthesize mainWindow = _mainWindow; -@synthesize appLaunchTime = _appLaunchTime; -@synthesize wasWokenUpByPushNotification = _wasWokenUpByPushNotification; - -- (instancetype)init -{ - self = [super init]; - - if (!self) { - return self; - } - - self.reportedApplicationState = UIApplicationStateInactive; - - _appLaunchTime = [NSDate new]; - _wasWokenUpByPushNotification = false; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillEnterForeground:) - name:UIApplicationWillEnterForegroundNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidEnterBackground:) - name:UIApplicationDidEnterBackgroundNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillResignActive:) - name:UIApplicationWillResignActiveNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidBecomeActive:) - name:UIApplicationDidBecomeActiveNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillTerminate:) - name:UIApplicationWillTerminateNotification - object:nil]; - - self.appActiveBlocks = [NSMutableArray new]; - - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -#pragma mark - Notifications - -- (void)setReportedApplicationState:(UIApplicationState)reportedApplicationState -{ - OWSAssertIsOnMainThread(); - - if (_reportedApplicationState == reportedApplicationState) { - return; - } - _reportedApplicationState = reportedApplicationState; - - [[NSNotificationCenter defaultCenter] postNotificationName:ReportedApplicationStateDidChangeNotification - object:nil - userInfo:nil]; -} - -- (void)applicationWillEnterForeground:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - self.reportedApplicationState = UIApplicationStateInactive; - - OWSLogInfo(@""); - - [NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationWillEnterForegroundNotification object:nil]; -} - -- (void)applicationDidEnterBackground:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - self.reportedApplicationState = UIApplicationStateBackground; - - OWSLogInfo(@""); - [DDLog flushLog]; - - [NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationDidEnterBackgroundNotification object:nil]; -} - -- (void)applicationWillResignActive:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - self.reportedApplicationState = UIApplicationStateInactive; - - OWSLogInfo(@""); - [DDLog flushLog]; - - [NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationWillResignActiveNotification object:nil]; -} - -- (void)applicationDidBecomeActive:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - self.reportedApplicationState = UIApplicationStateActive; - - OWSLogInfo(@""); - - [NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationDidBecomeActiveNotification object:nil]; - - [self runAppActiveBlocks]; -} - -- (void)applicationWillTerminate:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - OWSLogInfo(@""); - [DDLog flushLog]; -} - -#pragma mark - - -- (BOOL)isMainApp -{ - return YES; -} - -- (BOOL)isMainAppAndActive -{ - return [UIApplication sharedApplication].applicationState == UIApplicationStateActive; -} - -- (BOOL)isShareExtension { - return NO; -} - -- (BOOL)isRTL -{ - // FIXME: We should try to remove this as we've had to add a hack to ensure the first call to this runs on the main thread - static BOOL isRTL = NO; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - isRTL = [[UIApplication sharedApplication] userInterfaceLayoutDirection] - == UIUserInterfaceLayoutDirectionRightToLeft; - }); - return isRTL; -} - -- (void)setStatusBarHidden:(BOOL)isHidden animated:(BOOL)isAnimated -{ - [[UIApplication sharedApplication] setStatusBarHidden:isHidden animated:isAnimated]; -} - -- (CGFloat)statusBarHeight -{ - return [UIApplication sharedApplication].statusBarFrame.size.height; -} - -- (BOOL)isInBackground -{ - return self.reportedApplicationState == UIApplicationStateBackground; -} - -- (BOOL)isAppForegroundAndActive -{ - return self.reportedApplicationState == UIApplicationStateActive; -} - -- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler: - (BackgroundTaskExpirationHandler)expirationHandler -{ - return [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:expirationHandler]; -} - -- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)backgroundTaskIdentifier -{ - [UIApplication.sharedApplication endBackgroundTask:backgroundTaskIdentifier]; -} - -- (void)ensureSleepBlocking:(BOOL)shouldBeBlocking blockingObjects:(NSArray *)blockingObjects -{ - if (UIApplication.sharedApplication.isIdleTimerDisabled != shouldBeBlocking) { - if (shouldBeBlocking) { - NSMutableString *logString = - [NSMutableString stringWithFormat:@"Blocking sleep because of: %@", blockingObjects.firstObject]; - if (blockingObjects.count > 1) { - [logString appendString:[NSString stringWithFormat:@"(and %lu others)", blockingObjects.count - 1]]; - } - OWSLogInfo(@"%@", logString); - } else { - OWSLogInfo(@"Unblocking Sleep."); - } - } - UIApplication.sharedApplication.idleTimerDisabled = shouldBeBlocking; -} - -- (void)setMainAppBadgeNumber:(NSInteger)value -{ - [[UIApplication sharedApplication] setApplicationIconBadgeNumber:value]; - [[NSUserDefaults sharedLokiProject] setInteger:value forKey:@"currentBadgeNumber"]; - [[NSUserDefaults sharedLokiProject] synchronize]; -} - -- (nullable UIViewController *)frontmostViewController -{ - return UIApplication.sharedApplication.frontmostViewControllerIgnoringAlerts; -} - -- (nullable UIAlertAction *)openSystemSettingsAction -{ - return [UIAlertAction actionWithTitle:CommonStrings.openSettingsButton - accessibilityIdentifier:[NSString stringWithFormat:@"%@.%@", self.class, @"system_settings"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction *_Nonnull action) { - [UIApplication.sharedApplication openSystemSettings]; - }]; -} - -- (void)setNetworkActivityIndicatorVisible:(BOOL)value -{ - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:value]; -} - -#pragma mark - - -- (void)runNowOrWhenMainAppIsActive:(AppActiveBlock)block -{ - OWSAssertDebug(block); - - [Threading dispatchMainThreadSafe:^{ - if (self.isMainAppAndActive) { - // App active blocks typically will be used to safely access the - // shared data container, so use a background task to protect this - // work. - OWSBackgroundTask *_Nullable backgroundTask = - [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; - block(); - OWSAssertDebug(backgroundTask); - backgroundTask = nil; - return; - } - - [self.appActiveBlocks addObject:block]; - }]; -} - -- (void)runAppActiveBlocks -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(self.isMainAppAndActive); - - // App active blocks typically will be used to safely access the - // shared data container, so use a background task to protect this - // work. - OWSBackgroundTask *_Nullable backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; - - NSArray *appActiveBlocks = [self.appActiveBlocks copy]; - [self.appActiveBlocks removeAllObjects]; - for (AppActiveBlock block in appActiveBlocks) { - block(); - } - - OWSAssertDebug(backgroundTask); - backgroundTask = nil; -} - -- (NSString *)appDocumentDirectoryPath -{ - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSURL *documentDirectoryURL = - [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; - return [documentDirectoryURL path]; -} - -- (NSString *)appSharedDataDirectoryPath -{ - NSURL *groupContainerDirectoryURL = - [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:SignalApplicationGroup]; - return [groupContainerDirectoryURL path]; -} - -- (NSUserDefaults *)appUserDefaults -{ - return [[NSUserDefaults alloc] initWithSuiteName:SignalApplicationGroup]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Session/Meta/MainAppContext.swift b/Session/Meta/MainAppContext.swift new file mode 100644 index 000000000..949176192 --- /dev/null +++ b/Session/Meta/MainAppContext.swift @@ -0,0 +1,253 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import UIKit +import SignalCoreKit +import SessionUtilitiesKit + +final class MainAppContext: NSObject, AppContext { + var reportedApplicationState: UIApplication.State + + let appLaunchTime = Date() + let isMainApp: Bool = true + var isMainAppAndActive: Bool { UIApplication.shared.applicationState == .active } + var isShareExtension: Bool = false + var appActiveBlocks: [AppActiveBlock] = [] + + var mainWindow: UIWindow? + var wasWokenUpByPushNotification: Bool = false + + private static var _isRTL: Bool = { + return (UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft) + }() + + var isRTL: Bool { return MainAppContext._isRTL } + + var statusBarHeight: CGFloat { UIApplication.shared.statusBarFrame.size.height } + var openSystemSettingsAction: UIAlertAction? { + let result = UIAlertAction( + title: "OPEN_SETTINGS_BUTTON".localized(), + style: .default + ) { _ in UIApplication.shared.openSystemSettings() } + result.accessibilityIdentifier = "\(type(of: self)).system_settings" + + return result + } + + // MARK: - Initialization + + override init() { + self.reportedApplicationState = .inactive + + super.init() + + NotificationCenter.default.addObserver( + self, + selector: #selector(applicationWillEnterForeground(notification:)), + name: UIApplication.willEnterForegroundNotification, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(applicationDidEnterBackground(notification:)), + name: UIApplication.didEnterBackgroundNotification, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(applicationWillResignActive(notification:)), + name: UIApplication.willResignActiveNotification, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(applicationDidBecomeActive(notification:)), + name: UIApplication.didBecomeActiveNotification, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(applicationWillTerminate(notification:)), + name: UIApplication.willTerminateNotification, + object: nil + ) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + // MARK: - Notifications + + @objc private func applicationWillEnterForeground(notification: NSNotification) { + AssertIsOnMainThread() + + self.reportedApplicationState = .inactive + OWSLogger.info("") + + NotificationCenter.default.post( + name: .OWSApplicationWillEnterForeground, + object: nil + ) + } + + @objc private func applicationDidEnterBackground(notification: NSNotification) { + AssertIsOnMainThread() + + self.reportedApplicationState = .background + + OWSLogger.info("") + DDLog.flushLog() + + NotificationCenter.default.post( + name: .OWSApplicationDidEnterBackground, + object: nil + ) + } + + @objc private func applicationWillResignActive(notification: NSNotification) { + AssertIsOnMainThread() + + self.reportedApplicationState = .inactive + + OWSLogger.info("") + DDLog.flushLog() + + NotificationCenter.default.post( + name: .OWSApplicationWillResignActive, + object: nil + ) + } + + @objc private func applicationDidBecomeActive(notification: NSNotification) { + AssertIsOnMainThread() + + self.reportedApplicationState = .active + + OWSLogger.info("") + + NotificationCenter.default.post( + name: .OWSApplicationDidBecomeActive, + object: nil + ) + + self.runAppActiveBlocks() + } + + @objc private func applicationWillTerminate(notification: NSNotification) { + AssertIsOnMainThread() + + OWSLogger.info("") + DDLog.flushLog() + } + + // MARK: - AppContext Functions + + func setStatusBarHidden(_ isHidden: Bool, animated isAnimated: Bool) { + UIApplication.shared.setStatusBarHidden(isHidden, with: (isAnimated ? .slide : .none)) + } + + func isAppForegroundAndActive() -> Bool { + return (reportedApplicationState == .active) + } + + func isInBackground() -> Bool { + return (reportedApplicationState == .background) + } + + func beginBackgroundTask(expirationHandler: @escaping BackgroundTaskExpirationHandler) -> UIBackgroundTaskIdentifier { + return UIApplication.shared.beginBackgroundTask(expirationHandler: expirationHandler) + } + + func endBackgroundTask(_ backgroundTaskIdentifier: UIBackgroundTaskIdentifier) { + UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier) + } + + func ensureSleepBlocking(_ shouldBeBlocking: Bool, blockingObjects: [Any]) { + if UIApplication.shared.isIdleTimerDisabled != shouldBeBlocking { + if shouldBeBlocking { + var logString: String = "Blocking sleep because of: \(String(describing: blockingObjects.first))" + + if blockingObjects.count > 1 { + logString = "\(logString) (and \(blockingObjects.count - 1) others)" + } + OWSLogger.info(logString) + } + else { + OWSLogger.info("Unblocking Sleep.") + } + } + UIApplication.shared.isIdleTimerDisabled = shouldBeBlocking + } + + func setMainAppBadgeNumber(_ value: Int) { + UIApplication.shared.applicationIconBadgeNumber = value + UserDefaults.sharedLokiProject?.setValue(value, forKey: "currentBadgeNumber") + } + + func frontmostViewController() -> UIViewController? { + UIApplication.shared.frontmostViewControllerIgnoringAlerts + } + + func setNetworkActivityIndicatorVisible(_ value: Bool) { + UIApplication.shared.isNetworkActivityIndicatorVisible = value + } + + // MARK: - + + func runNowOr(whenMainAppIsActive block: @escaping AppActiveBlock) { + Threading.dispatchMainThreadSafe { [weak self] in + if self?.isMainAppAndActive == true { + // App active blocks typically will be used to safely access the + // shared data container, so use a background task to protect this + // work. + var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: #function) + block() + if backgroundTask != nil { backgroundTask = nil } + return + } + + self?.appActiveBlocks.append(block) + } + } + + func runAppActiveBlocks() { + // App active blocks typically will be used to safely access the + // shared data container, so use a background task to protect this + // work. + var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: #function) + + let appActiveBlocks: [AppActiveBlock] = self.appActiveBlocks + self.appActiveBlocks.removeAll() + + appActiveBlocks.forEach { $0() } + if backgroundTask != nil { backgroundTask = nil } + } + + func appDocumentDirectoryPath() -> String { + let targetPath: String? = FileManager.default + .urls( + for: .documentDirectory, + in: .userDomainMask + ) + .last? + .path + owsAssertDebug(targetPath != nil) + + return (targetPath ?? "") + } + + func appSharedDataDirectoryPath() -> String { + let targetPath: String? = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: UserDefaults.applicationGroup)? + .path + owsAssertDebug(targetPath != nil) + + return (targetPath ?? "") + } + + func appUserDefaults() -> UserDefaults { + owsAssertDebug(UserDefaults.sharedLokiProject != nil) + + return (UserDefaults.sharedLokiProject ?? UserDefaults.standard) + } +} diff --git a/Session/Meta/Signal-Bridging-Header.h b/Session/Meta/Signal-Bridging-Header.h index a745cd256..c9b6f4634 100644 --- a/Session/Meta/Signal-Bridging-Header.h +++ b/Session/Meta/Signal-Bridging-Header.h @@ -7,4 +7,3 @@ #import "OWSBezierPathView.h" #import "OWSMessageTimerView.h" #import "OWSWindowManager.h" -#import "MainAppContext.h" diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index bb8129f9a..2c0774346 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -6,6 +6,8 @@ import GRDB import SessionMessagingKit import SignalUtilitiesKit import SignalCoreKit +import SessionUtilitiesKit +import SessionSnodeKit /// There are two primary components in our system notification integration: /// diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index b9d3f98c9..6645428a5 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -4,8 +4,10 @@ import Foundation import Combine import PushKit import GRDB +import SessionMessagingKit import SignalUtilitiesKit import SignalCoreKit +import SessionUtilitiesKit public enum PushRegistrationError: Error { case assertionError(description: String) @@ -53,8 +55,6 @@ public enum PushRegistrationError: Error { Logger.info("") return registerUserNotificationSettings() - .subscribe(on: DispatchQueue.global(qos: .default)) - .receive(on: DispatchQueue.main) // MUST be on main thread .setFailureType(to: Error.self) .tryFlatMap { _ -> AnyPublisher<(pushToken: String, voipToken: String), Error> in #if targetEnvironment(simulator) @@ -75,26 +75,25 @@ public enum PushRegistrationError: Error { // MARK: Vanilla push token // Vanilla push token is obtained from the system via AppDelegate - public func didReceiveVanillaPushToken(_ tokenData: Data) { + public func didReceiveVanillaPushToken(_ tokenData: Data, using dependencies: Dependencies = Dependencies()) { guard let vanillaTokenResolver = self.vanillaTokenResolver else { owsFailDebug("publisher completion in \(#function) unexpectedly nil") return } - DispatchQueue.global(qos: .default).async { + DispatchQueue.global(qos: .default).async(using: dependencies) { vanillaTokenResolver(Result.success(tokenData)) } } // Vanilla push token is obtained from the system via AppDelegate - @objc - public func didFailToReceiveVanillaPushToken(error: Error) { + public func didFailToReceiveVanillaPushToken(error: Error, using dependencies: Dependencies = Dependencies()) { guard let vanillaTokenResolver = self.vanillaTokenResolver else { owsFailDebug("publisher completion in \(#function) unexpectedly nil") return } - DispatchQueue.global(qos: .default).async { + DispatchQueue.global(qos: .default).async(using: dependencies) { vanillaTokenResolver(Result.failure(error)) } } @@ -115,9 +114,8 @@ public enum PushRegistrationError: Error { * in this case we've verified that we *have* properly registered notification settings. */ private var isSusceptibleToFailedPushRegistration: Bool { - // Only affects users who have disabled both: background refresh *and* notifications - guard UIApplication.shared.backgroundRefreshStatus == .denied else { + guard DispatchQueue.main.sync(execute: { UIApplication.shared.backgroundRefreshStatus }) == .denied else { return false } @@ -142,19 +140,21 @@ public enum PushRegistrationError: Error { // No pending vanilla token yet; create a new publisher let publisher: AnyPublisher = Deferred { - Future { self.vanillaTokenResolver = $0 } + Future { + self.vanillaTokenResolver = $0 + + // Tell the device to register for remote notifications + DispatchQueue.main.sync { UIApplication.shared.registerForRemoteNotifications() } + } } .shareReplay(1) .eraseToAnyPublisher() self.vanillaTokenPublisher = publisher - // Tell the device to register for remote notifications - DispatchQueue.main.sync { UIApplication.shared.registerForRemoteNotifications() } - return publisher .timeout( .seconds(10), - scheduler: DispatchQueue.main, + scheduler: DispatchQueue.global(qos: .default), customError: { PushRegistrationError.timeout } ) .catch { error -> AnyPublisher in diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 5e83996b3..fac7107fa 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -39,29 +39,29 @@ public enum SyncPushTokensJob: JobExecutor { guard isUsingFullAPNs else { Just(dependencies.storage[.lastRecordedPushToken]) .setFailureType(to: Error.self) - .flatMap { lastRecordedPushToken -> AnyPublisher in - if let existingToken: String = lastRecordedPushToken { - SNLog("[SyncPushTokensJob] Unregister using last recorded push token: \(redact(existingToken))") - return Just(existingToken) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - SNLog("[SyncPushTokensJob] Unregister using live token provided from device") - return PushRegistrationManager.shared.requestPushTokens() - .map { token, _ in token } - .eraseToAnyPublisher() - } - .flatMap { pushToken in PushNotificationAPI.unsubscribe(token: Data(hex: pushToken)) } - .map { + .flatMap { lastRecordedPushToken -> AnyPublisher in // Tell the device to unregister for remote notifications (essentially try to invalidate - // the token if needed + // the token if needed - we do this first to avoid wrid race conditions which could be + // triggered by the user immediately re-registering) DispatchQueue.main.sync { UIApplication.shared.unregisterForRemoteNotifications() } + // Clear the old token dependencies.storage.write(using: dependencies) { db in db[.lastRecordedPushToken] = nil } - return () + + // Unregister from our server + if let existingToken: String = lastRecordedPushToken { + SNLog("[SyncPushTokensJob] Unregister using last recorded push token: \(redact(existingToken))") + return PushNotificationAPI.unsubscribe(token: Data(hex: existingToken)) + .map { _ in () } + .eraseToAnyPublisher() + } + + SNLog("[SyncPushTokensJob] No previous token stored just triggering device unregister") + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } .subscribe(on: queue, using: dependencies) .sinkUntilComplete( @@ -151,5 +151,9 @@ extension SyncPushTokensJob { // MARK: - Convenience private func redact(_ string: String) -> String { - return OWSIsDebugBuild() ? string : "[ READACTED \(string.prefix(2))...\(string.suffix(2)) ]" +#if DEBUG + return string +#else + return "[ READACTED \(string.prefix(2))...\(string.suffix(2)) ]" +#endif } diff --git a/Session/Notifications/UserNotificationsAdaptee.swift b/Session/Notifications/UserNotificationsAdaptee.swift index 15c14a53a..383d1877f 100644 --- a/Session/Notifications/UserNotificationsAdaptee.swift +++ b/Session/Notifications/UserNotificationsAdaptee.swift @@ -6,6 +6,7 @@ import UserNotifications import SessionMessagingKit import SignalCoreKit import SignalUtilitiesKit +import SessionUtilitiesKit class UserNotificationConfig { diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 123b2fd33..8c75f230b 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -258,9 +258,9 @@ enum Onboarding { // Notify the app that registration is complete Identity.didRegister() - // Now that we have registered get the Snode pool and sync push tokens + // Now that we have registered get the Snode pool (just in case) - other non-blocking + // launch jobs will automatically be run because the app activation was triggered GetSnodePoolJob.run() - SyncPushTokensJob.run(uploadOnlyIfStale: false) } } } diff --git a/Session/Onboarding/PNModeVC.swift b/Session/Onboarding/PNModeVC.swift index bf4884a29..c389e7b9b 100644 --- a/Session/Onboarding/PNModeVC.swift +++ b/Session/Onboarding/PNModeVC.swift @@ -6,6 +6,7 @@ import SessionUIKit import SessionMessagingKit import SessionSnodeKit import SignalUtilitiesKit +import SessionUtilitiesKit final class PNModeVC: BaseVC, OptionViewDelegate { private let flow: Onboarding.Flow diff --git a/Session/Onboarding/RegisterVC.swift b/Session/Onboarding/RegisterVC.swift index 52cc441a6..ef9cb8228 100644 --- a/Session/Onboarding/RegisterVC.swift +++ b/Session/Onboarding/RegisterVC.swift @@ -4,6 +4,7 @@ import UIKit import Sodium import SessionUIKit import SignalUtilitiesKit +import SessionUtilitiesKit final class RegisterVC : BaseVC { private var seed: Data! { didSet { updateKeyPair() } } diff --git a/Session/Settings/AppearanceViewController.swift b/Session/Settings/AppearanceViewController.swift index 10336d3ed..cca4a0b8f 100644 --- a/Session/Settings/AppearanceViewController.swift +++ b/Session/Settings/AppearanceViewController.swift @@ -3,6 +3,7 @@ import UIKit import SessionUIKit import SignalUtilitiesKit +import SessionUtilitiesKit final class AppearanceViewController: BaseVC { // MARK: - Components diff --git a/Session/Settings/BlockedContactsViewModel.swift b/Session/Settings/BlockedContactsViewModel.swift index 871bae502..bf4a46bea 100644 --- a/Session/Settings/BlockedContactsViewModel.swift +++ b/Session/Settings/BlockedContactsViewModel.swift @@ -6,6 +6,7 @@ import GRDB import DifferenceKit import SessionUIKit import SignalUtilitiesKit +import SessionUtilitiesKit class BlockedContactsViewModel: SessionTableViewModel { // MARK: - Section diff --git a/Session/Settings/ImagePickerHandler.swift b/Session/Settings/ImagePickerHandler.swift index 6182f6fc2..59966f5ed 100644 --- a/Session/Settings/ImagePickerHandler.swift +++ b/Session/Settings/ImagePickerHandler.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import SessionUtilitiesKit class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate & UINavigationControllerDelegate { private let onTransition: (UIViewController, TransitionType) -> Void diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift index 3942b5221..371aba884 100644 --- a/Session/Settings/NukeDataModal.swift +++ b/Session/Settings/NukeDataModal.swift @@ -5,6 +5,7 @@ import SessionUIKit import SessionSnodeKit import SessionMessagingKit import SignalUtilitiesKit +import SessionUtilitiesKit final class NukeDataModal: Modal { // MARK: - Initialization diff --git a/Session/Shared/CaptionView.swift b/Session/Shared/CaptionView.swift index 1b66232ca..004da8593 100644 --- a/Session/Shared/CaptionView.swift +++ b/Session/Shared/CaptionView.swift @@ -2,6 +2,7 @@ import UIKit import SessionUIKit +import SessionUtilitiesKit public protocol CaptionContainerViewDelegate: AnyObject { func captionContainerViewDidUpdateText(_ captionContainerView: CaptionContainerView) diff --git a/Session/Shared/FullConversationCell.swift b/Session/Shared/FullConversationCell.swift index e1e5c5d30..bd8bfafbc 100644 --- a/Session/Shared/FullConversationCell.swift +++ b/Session/Shared/FullConversationCell.swift @@ -4,6 +4,7 @@ import UIKit import SessionUIKit import SignalUtilitiesKit import SessionMessagingKit +import SessionUtilitiesKit public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticCell { public static let mutePrefix: String = "\u{e067} " diff --git a/Session/Shared/Types/SessionCell+Styling.swift b/Session/Shared/Types/SessionCell+Styling.swift index 948cd1631..e7a454388 100644 --- a/Session/Shared/Types/SessionCell+Styling.swift +++ b/Session/Shared/Types/SessionCell+Styling.swift @@ -1,6 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -import Foundation +import UIKit import SessionUIKit // MARK: - Main Types diff --git a/Session/Utilities/IP2Country.swift b/Session/Utilities/IP2Country.swift index b27698f5c..326afc83e 100644 --- a/Session/Utilities/IP2Country.swift +++ b/Session/Utilities/IP2Country.swift @@ -1,6 +1,7 @@ import Foundation import GRDB import SessionSnodeKit +import SessionUtilitiesKit final class IP2Country { static var isInitialized = false @@ -12,16 +13,16 @@ final class IP2Country { /// the **lower** bound of an IP range and the "registered_country_geoname_id" column contains the ID of the country corresponding /// to that range. We look up an IP by finding the first index in the network column where the value is greater than the IP we're looking /// up (converted to an integer). The IP we're looking up must then be in the range **before** that range. - private lazy var ipv4Table: [String:[Int]] = { + private lazy var ipv4Table: [String: [Int]] = { let url = Bundle.main.url(forResource: "GeoLite2-Country-Blocks-IPv4", withExtension: nil)! let data = try! Data(contentsOf: url) - return try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [String:[Int]] + return try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [String: [Int]] }() - private lazy var countryNamesTable: [String:[String]] = { + private lazy var countryNamesTable: [String: [String]] = { let url = Bundle.main.url(forResource: "GeoLite2-Country-Locations-English", withExtension: nil)! let data = try! Data(contentsOf: url) - return try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [String:[String]] + return try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [String: [String]] }() // MARK: Lifecycle diff --git a/Session/Utilities/MockDataGenerator.swift b/Session/Utilities/MockDataGenerator.swift index 1cbf94fbe..b268786c3 100644 --- a/Session/Utilities/MockDataGenerator.swift +++ b/Session/Utilities/MockDataGenerator.swift @@ -4,6 +4,7 @@ import Foundation import GRDB import Curve25519Kit import SessionMessagingKit +import SessionUtilitiesKit enum MockDataGenerator { // MARK: - Generation diff --git a/Session/Utilities/UIContextualAction+Utilities.swift b/Session/Utilities/UIContextualAction+Utilities.swift index 1d33a47c6..85997e42a 100644 --- a/Session/Utilities/UIContextualAction+Utilities.swift +++ b/Session/Utilities/UIContextualAction+Utilities.swift @@ -3,6 +3,7 @@ import UIKit import SessionMessagingKit import SessionUIKit +import SessionUtilitiesKit protocol SwipeActionOptimisticCell { func optimisticUpdate(isMuted: Bool?, isBlocked: Bool?, isPinned: Bool?, hasUnread: Bool?) diff --git a/SessionNotificationServiceExtension/NSENotificationPresenter.swift b/SessionNotificationServiceExtension/NSENotificationPresenter.swift index acff494bb..f091f4eb6 100644 --- a/SessionNotificationServiceExtension/NSENotificationPresenter.swift +++ b/SessionNotificationServiceExtension/NSENotificationPresenter.swift @@ -5,6 +5,7 @@ import GRDB import UserNotifications import SignalUtilitiesKit import SessionMessagingKit +import SessionUtilitiesKit public class NSENotificationPresenter: NSObject, NotificationsProtocol { private var notifications: [String: UNNotificationRequest] = [:] diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index fa3c9b1e1..b4f40742a 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -9,6 +9,7 @@ import BackgroundTasks import SessionMessagingKit import SignalUtilitiesKit import SignalCoreKit +import SessionUtilitiesKit public final class NotificationServiceExtension: UNNotificationServiceExtension { private var didPerformSetup = false @@ -25,8 +26,6 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension self.contentHandler = contentHandler self.request = request - Storage.resumeDatabaseAccess() - guard let notificationContent = request.content.mutableCopy() as? UNMutableNotificationContent else { return self.completeSilenty() } @@ -36,10 +35,16 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension return self.completeSilenty() } + /// Create the context if we don't have it (needed before _any_ interaction with the database) + if !HasAppContext() { + SetCurrentAppContext(NotificationServiceExtensionContext()) + } + let isCallOngoing: Bool = (UserDefaults.sharedLokiProject?[.isCallOngoing]) .defaulting(to: false) // Perform main setup + Storage.resumeDatabaseAccess() DispatchQueue.main.sync { self.setUpIfNecessary() { } } // Handle the push notification @@ -224,20 +229,10 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension didPerformSetup = true - // This should be the first thing we do. - SetCurrentAppContext(NotificationServiceExtensionContext()) - _ = AppVersion.sharedInstance() Cryptography.seedRandom() - // We should never receive a non-voip notification on an app that doesn't support - // app extensions since we have to inform the service we wanted these, so in theory - // this path should never occur. However, the service does have our push token - // so it is possible that could change in the future. If it does, do nothing - // and don't disturb the user. Messages will be processed when they open the app. - guard Storage.shared[.isReadyForAppExtensions] else { return completeSilenty() } - AppSetup.setupEnvironment( appSpecificBlock: { Environment.shared?.notificationsManager.mutate { @@ -247,8 +242,22 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension migrationsCompletion: { [weak self] result, needsConfigSync in switch result { // Only 'NSLog' works in the extension - viewable via Console.app - case .failure: NSLog("[NotificationServiceExtension] Failed to complete migrations") + case .failure(let error): + NSLog("[NotificationServiceExtension] Failed to complete migrations") + self?.completeSilenty() + case .success: + // We should never receive a non-voip notification on an app that doesn't support + // app extensions since we have to inform the service we wanted these, so in theory + // this path should never occur. However, the service does have our push token + // so it is possible that could change in the future. If it does, do nothing + // and don't disturb the user. Messages will be processed when they open the app. + guard Storage.shared[.isReadyForAppExtensions] else { + NSLog("[NotificationServiceExtension] Not ready for extensions") + self?.completeSilenty() + return + } + DispatchQueue.main.async { self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync) } diff --git a/SessionNotificationServiceExtension/NotificationServiceExtensionContext.swift b/SessionNotificationServiceExtension/NotificationServiceExtensionContext.swift index 7c150570b..d642a984c 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtensionContext.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtensionContext.swift @@ -4,6 +4,7 @@ import Foundation import SignalUtilitiesKit +import SessionUtilitiesKit final class NotificationServiceExtensionContext : NSObject, AppContext { let appLaunchTime = Date() @@ -31,10 +32,6 @@ final class NotificationServiceExtensionContext : NSObject, AppContext { func isInBackground() -> Bool { true } func mainApplicationStateOnLaunch() -> UIApplication.State { .inactive } - func appDatabaseBaseDirectoryPath() -> String { - return appSharedDataDirectoryPath() - } - func appDocumentDirectoryPath() -> String { guard let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last else { preconditionFailure("Couldn't get document directory.") @@ -43,14 +40,14 @@ final class NotificationServiceExtensionContext : NSObject, AppContext { } func appSharedDataDirectoryPath() -> String { - guard let groupContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: SignalApplicationGroup) else { + guard let groupContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: UserDefaults.applicationGroup) else { preconditionFailure("Couldn't get shared data directory.") } return groupContainerURL.path } func appUserDefaults() -> UserDefaults { - guard let userDefaults = UserDefaults(suiteName: SignalApplicationGroup) else { + guard let userDefaults = UserDefaults.sharedLokiProject else { preconditionFailure("Couldn't set up shared user defaults.") } return userDefaults diff --git a/SessionShareExtension/ShareAppExtensionContext.swift b/SessionShareExtension/ShareAppExtensionContext.swift index 4f3417642..4c647fdc7 100644 --- a/SessionShareExtension/ShareAppExtensionContext.swift +++ b/SessionShareExtension/ShareAppExtensionContext.swift @@ -157,7 +157,7 @@ final class ShareAppExtensionContext: NSObject, AppContext { func appSharedDataDirectoryPath() -> String { let targetPath: String? = FileManager.default - .containerURL(forSecurityApplicationGroupIdentifier: SignalApplicationGroup)? + .containerURL(forSecurityApplicationGroupIdentifier: UserDefaults.applicationGroup)? .path owsAssertDebug(targetPath != nil) @@ -165,10 +165,9 @@ final class ShareAppExtensionContext: NSObject, AppContext { } func appUserDefaults() -> UserDefaults { - let targetUserDefaults: UserDefaults? = UserDefaults(suiteName: SignalApplicationGroup) - owsAssertDebug(targetUserDefaults != nil) + owsAssertDebug(UserDefaults.sharedLokiProject != nil) - return (targetUserDefaults ?? UserDefaults.standard) + return (UserDefaults.sharedLokiProject ?? UserDefaults.standard) } func setStatusBarHidden(_ isHidden: Bool, animated isAnimated: Bool) { diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index 3008a89e1..afc92180a 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -7,6 +7,7 @@ import DifferenceKit import SessionUIKit import SignalUtilitiesKit import SessionMessagingKit +import SessionSnodeKit final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableViewDelegate, AttachmentApprovalViewControllerDelegate { private let viewModel: ThreadPickerViewModel = ThreadPickerViewModel() diff --git a/SessionSnodeKit/Networking/OnionRequestAPI.swift b/SessionSnodeKit/Networking/OnionRequestAPI.swift index d07233b29..4d0e13b3d 100644 --- a/SessionSnodeKit/Networking/OnionRequestAPI.swift +++ b/SessionSnodeKit/Networking/OnionRequestAPI.swift @@ -275,7 +275,7 @@ public enum OnionRequestAPI { if let snode = snode { if let path = paths.first(where: { !$0.contains(snode) }) { buildPaths(reusing: paths, using: dependencies) // Re-build paths in the background - .subscribe(on: DispatchQueue.global(qos: .background)) + .subscribe(on: DispatchQueue.global(qos: .background), using: dependencies) .sink(receiveCompletion: { _ in cancellable = [] }, receiveValue: { _ in }) .store(in: &cancellable) diff --git a/SessionUtilitiesKit/General/AppContext.h b/SessionUtilitiesKit/General/AppContext.h index 82c90d809..dff051bd0 100755 --- a/SessionUtilitiesKit/General/AppContext.h +++ b/SessionUtilitiesKit/General/AppContext.h @@ -2,15 +2,6 @@ NS_ASSUME_NONNULL_BEGIN -static inline BOOL OWSIsDebugBuild() -{ -#ifdef DEBUG - return YES; -#else - return NO; -#endif -} - // These are fired whenever the corresponding "main app" or "app extension" // notification is fired. // diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index bc55e8231..21d729da8 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -62,8 +62,10 @@ public enum SNUserDefaults { } public extension UserDefaults { + public static let applicationGroup: String = "group.com.loki-project.loki-messenger" + @objc static var sharedLokiProject: UserDefaults? { - UserDefaults(suiteName: "group.com.loki-project.loki-messenger") + UserDefaults(suiteName: UserDefaults.applicationGroup) } } diff --git a/SignalUtilitiesKit/Configuration.swift b/SignalUtilitiesKit/Configuration.swift index 4ab7e2d50..651bd49cd 100644 --- a/SignalUtilitiesKit/Configuration.swift +++ b/SignalUtilitiesKit/Configuration.swift @@ -4,6 +4,7 @@ import Foundation import SessionUIKit import SessionSnodeKit import SessionMessagingKit +import SessionUtilitiesKit public enum Configuration { public static func performMainSetup() { diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift index 39f6c73f5..a6b1a8782 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift @@ -9,6 +9,7 @@ import CoreServices import SessionUIKit import SessionMessagingKit import SignalCoreKit +import SessionUtilitiesKit public protocol AttachmentApprovalViewControllerDelegate: AnyObject { func attachmentApproval( diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift index 4c3ad9f57..dcd4f2044 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift @@ -4,6 +4,7 @@ import Foundation import UIKit import SessionUIKit import SignalCoreKit +import SessionUtilitiesKit protocol AttachmentCaptionToolbarDelegate: AnyObject { func attachmentCaptionToolbarDidEdit(_ attachmentCaptionToolbar: AttachmentCaptionToolbar) @@ -150,26 +151,7 @@ class AttachmentCaptionToolbar: UIView, UITextViewDelegate { public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { let existingText: String = textView.text ?? "" let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text) - - // Don't complicate things by mixing media attachments with oversize text attachments - guard proposedText.utf8.count < kOversizeTextMessageSizeThreshold else { - Logger.debug("long text was truncated") - self.lengthLimitLabel.isHidden = false - - // `range` represents the section of the existing text we will replace. We can re-use that space. - // Range is in units of NSStrings's standard UTF-16 characters. Since some of those chars could be - // represented as single bytes in utf-8, while others may be 8 or more, the only way to be sure is - // to just measure the utf8 encoded bytes of the replaced substring. - let bytesAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").utf8.count - - // Accept as much of the input as we can - let byteBudget: Int = Int(kOversizeTextMessageSizeThreshold) - bytesAfterDelete - if byteBudget >= 0, let acceptableNewText = text.truncated(toByteCount: UInt(byteBudget)) { - textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText) - } - - return false - } + self.lengthLimitLabel.isHidden = true // After verifying the byte-length is sufficiently small, verify the character count is within bounds. diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentPrepViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentPrepViewController.swift index db65cd7e2..4b45d2e1d 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentPrepViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentPrepViewController.swift @@ -5,6 +5,8 @@ import UIKit import AVFoundation import SessionUIKit import SignalCoreKit +import SessionMessagingKit +import SessionUtilitiesKit protocol AttachmentPrepViewControllerDelegate: AnyObject { func prepViewControllerUpdateNavigationBar() diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift index 79ac67d63..736c9049b 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift @@ -2,9 +2,10 @@ import Foundation import UIKit -import SessionUIKit -import SignalCoreKit import PureLayout +import SignalCoreKit +import SessionUIKit +import SessionUtilitiesKit // Coincides with Android's max text message length let kMaxMessageBodyCharacterCount = 2000 @@ -228,25 +229,6 @@ class AttachmentTextToolbar: UIView, UITextViewDelegate { let existingText: String = textView.text ?? "" let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text) - // Don't complicate things by mixing media attachments with oversize text attachments - guard proposedText.utf8.count < kOversizeTextMessageSizeThreshold else { - Logger.debug("long text was truncated") - self.lengthLimitLabel.isHidden = false - - // `range` represents the section of the existing text we will replace. We can re-use that space. - // Range is in units of NSStrings's standard UTF-16 characters. Since some of those chars could be - // represented as single bytes in utf-8, while others may be 8 or more, the only way to be sure is - // to just measure the utf8 encoded bytes of the replaced substring. - let bytesAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").utf8.count - - // Accept as much of the input as we can - let byteBudget: Int = Int(kOversizeTextMessageSizeThreshold) - bytesAfterDelete - if byteBudget >= 0, let acceptableNewText = text.truncated(toByteCount: UInt(byteBudget)) { - textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText) - } - - return false - } self.lengthLimitLabel.isHidden = true // After verifying the byte-length is sufficiently small, verify the character count is within bounds. diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift index e0acf8bba..903c886cf 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift @@ -3,6 +3,7 @@ import UIKit import SessionUIKit import SignalCoreKit +import SessionUtilitiesKit public class EditorTextLayer: CATextLayer { let itemId: String diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift index ad415e5e6..c2f3585fe 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift @@ -3,6 +3,7 @@ import UIKit import SessionUIKit import SignalCoreKit +import SessionUtilitiesKit public protocol ImageEditorCropViewControllerDelegate: AnyObject { func cropDidComplete(transform: ImageEditorTransform) @@ -200,7 +201,7 @@ class ImageEditorCropViewController: OWSViewController { case .topRight, .bottomRight: cropCornerView.autoPinEdge(toSuperviewEdge: .right) default: - owsFailDebug("Invalid crop region: \(cropRegion)") + owsFailDebug("Invalid crop region: \(String(describing: cropRegion))") } switch cropCornerView.cropRegion { case .topLeft, .topRight: @@ -208,7 +209,7 @@ class ImageEditorCropViewController: OWSViewController { case .bottomLeft, .bottomRight: cropCornerView.autoPinEdge(toSuperviewEdge: .bottom) default: - owsFailDebug("Invalid crop region: \(cropRegion)") + owsFailDebug("Invalid crop region: \(String(describing: cropRegion))") } } diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorModel.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorModel.swift index d2c8b062b..9ac29b78b 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorModel.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorModel.swift @@ -2,6 +2,7 @@ import UIKit import SignalCoreKit +import SessionUtilitiesKit // Used to represent undo/redo operations. // diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPinchGestureRecognizer.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPinchGestureRecognizer.swift index 01e580ecb..8d6551f4b 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPinchGestureRecognizer.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPinchGestureRecognizer.swift @@ -2,6 +2,7 @@ import UIKit import SignalCoreKit +import SessionUtilitiesKit public struct ImageEditorPinchState { public let centroid: CGPoint diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorStrokeItem.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorStrokeItem.swift index f86940d22..50b627eea 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorStrokeItem.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorStrokeItem.swift @@ -3,6 +3,7 @@ // import UIKit +import SessionUtilitiesKit @objc public class ImageEditorStrokeItem: ImageEditorItem { diff --git a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift index 6089077cc..f0be90aec 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift @@ -8,6 +8,7 @@ import NVActivityIndicatorView import SessionUIKit import SessionMessagingKit import SignalCoreKit +import SessionUtilitiesKit public protocol MediaMessageViewAudioDelegate: AnyObject { func progressChanged(_ progressSeconds: CGFloat, durationSeconds: CGFloat) diff --git a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h index b856fa9a0..73cd95cff 100644 --- a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h +++ b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h @@ -3,15 +3,5 @@ FOUNDATION_EXPORT double SignalUtilitiesKitVersionNumber; FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[]; -@import SessionMessagingKit; -@import SessionSnodeKit; -@import SessionUtilitiesKit; - #import -#import -#import -#import -#import -#import #import -#import diff --git a/SignalUtilitiesKit/Screen Lock/ScreenLock.swift b/SignalUtilitiesKit/Screen Lock/ScreenLock.swift index c1ed4654a..c87fb370d 100644 --- a/SignalUtilitiesKit/Screen Lock/ScreenLock.swift +++ b/SignalUtilitiesKit/Screen Lock/ScreenLock.swift @@ -7,6 +7,10 @@ import SessionMessagingKit import SignalCoreKit public class ScreenLock { + public enum ScreenLockError: Error { + case general(description: String) + } + public enum Outcome { case success case cancel @@ -54,11 +58,11 @@ public class ScreenLock { switch outcome { case .failure(let error): Logger.error("local authentication failed with error: \(error)") - failure(self.authenticationError(errorDescription: error)) + failure(ScreenLockError.general(description: error)) case .unexpectedFailure(let error): Logger.error("local authentication failed with unexpected error: \(error)") - unexpectedFailure(self.authenticationError(errorDescription: error)) + unexpectedFailure(ScreenLockError.general(description: error)) case .success: Logger.verbose("local authentication succeeded.") @@ -203,11 +207,7 @@ public class ScreenLock { } } - return .failure(error:defaultErrorDescription) - } - - private func authenticationError(errorDescription: String) -> Error { - return OWSErrorWithCodeDescription(.localAuthenticationError, errorDescription) + return .failure(error: defaultErrorDescription) } // MARK: - Context diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index f62f78a89..81eeb4026 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -5,6 +5,7 @@ import GRDB import SessionMessagingKit import SessionUtilitiesKit import SessionUIKit +import SessionSnodeKit public enum AppSetup { private static let hasRun: Atomic = Atomic(false) diff --git a/SignalUtilitiesKit/Utilities/ByteParser.h b/SignalUtilitiesKit/Utilities/ByteParser.h deleted file mode 100644 index c30c8c86c..000000000 --- a/SignalUtilitiesKit/Utilities/ByteParser.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ByteParser : NSObject - -@property (nonatomic, readonly) BOOL hasError; - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithData:(NSData *)data littleEndian:(BOOL)littleEndian; - -#pragma mark - Short - -- (uint16_t)shortAtIndex:(NSUInteger)index; -- (uint16_t)nextShort; - -#pragma mark - Int - -- (uint32_t)intAtIndex:(NSUInteger)index; -- (uint32_t)nextInt; - -#pragma mark - Long - -- (uint64_t)longAtIndex:(NSUInteger)index; -- (uint64_t)nextLong; - -#pragma mark - - -- (BOOL)readZero:(NSUInteger)length; - -- (nullable NSData *)readBytes:(NSUInteger)length; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/ByteParser.m b/SignalUtilitiesKit/Utilities/ByteParser.m deleted file mode 100644 index 4dd7c38db..000000000 --- a/SignalUtilitiesKit/Utilities/ByteParser.m +++ /dev/null @@ -1,143 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "ByteParser.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ByteParser () - -@property (nonatomic, readonly) BOOL littleEndian; -@property (nonatomic, readonly) NSData *data; -@property (nonatomic) NSUInteger cursor; -@property (nonatomic) BOOL hasError; - -@end - -#pragma mark - - -@implementation ByteParser - -- (instancetype)initWithData:(NSData *)data littleEndian:(BOOL)littleEndian -{ - if (self = [super init]) { - _littleEndian = littleEndian; - _data = data; - } - - return self; -} - -#pragma mark - Short - -- (uint16_t)shortAtIndex:(NSUInteger)index -{ - uint16_t value; - const size_t valueSize = sizeof(value); - OWSAssertDebug(valueSize == 2); - if (index + valueSize > self.data.length) { - self.hasError = YES; - return 0; - } - [self.data getBytes:&value range:NSMakeRange(index, valueSize)]; - if (self.littleEndian) { - return CFSwapInt16LittleToHost(value); - } else { - return CFSwapInt16BigToHost(value); - } -} - -- (uint16_t)nextShort -{ - uint16_t value = [self shortAtIndex:self.cursor]; - self.cursor += sizeof(value); - return value; -} - -#pragma mark - Int - -- (uint32_t)intAtIndex:(NSUInteger)index -{ - uint32_t value; - const size_t valueSize = sizeof(value); - OWSAssertDebug(valueSize == 4); - if (index + valueSize > self.data.length) { - self.hasError = YES; - return 0; - } - [self.data getBytes:&value range:NSMakeRange(index, valueSize)]; - if (self.littleEndian) { - return CFSwapInt32LittleToHost(value); - } else { - return CFSwapInt32BigToHost(value); - } -} - -- (uint32_t)nextInt -{ - uint32_t value = [self intAtIndex:self.cursor]; - self.cursor += sizeof(value); - return value; -} - -#pragma mark - Long - -- (uint64_t)longAtIndex:(NSUInteger)index -{ - uint64_t value; - const size_t valueSize = sizeof(value); - OWSAssertDebug(valueSize == 8); - if (index + valueSize > self.data.length) { - self.hasError = YES; - return 0; - } - [self.data getBytes:&value range:NSMakeRange(index, valueSize)]; - if (self.littleEndian) { - return CFSwapInt64LittleToHost(value); - } else { - return CFSwapInt64BigToHost(value); - } -} - -- (uint64_t)nextLong -{ - uint64_t value = [self longAtIndex:self.cursor]; - self.cursor += sizeof(value); - return value; -} - -#pragma mark - - -- (BOOL)readZero:(NSUInteger)length -{ - NSData *_Nullable subdata = [self readBytes:length]; - if (!subdata) { - return NO; - } - uint8_t bytes[length]; - [subdata getBytes:bytes range:NSMakeRange(0, length)]; - for (int i = 0; i < length; i++) { - if (bytes[i] != 0) { - return NO; - } - } - return YES; -} - -- (nullable NSData *)readBytes:(NSUInteger)length -{ - NSUInteger index = self.cursor; - if (index + length > self.data.length) { - self.hasError = YES; - return nil; - } - NSData *_Nullable subdata = [self.data subdataWithRange:NSMakeRange(index, length)]; - self.cursor += length; - return subdata; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/FunctionalUtil.h b/SignalUtilitiesKit/Utilities/FunctionalUtil.h deleted file mode 100644 index e86ed911a..000000000 --- a/SignalUtilitiesKit/Utilities/FunctionalUtil.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface NSArray (FunctionalUtil) - -/// Returns true when any of the items in this array match the given predicate. -- (bool)any:(int (^)(id item))predicate; - -/// Returns true when all of the items in this array match the given predicate. -- (bool)all:(int (^)(id item))predicate; - -/// Returns an array of all the results of passing items from this array through the given projection function. -- (NSArray *)map:(id (^)(id item))projection; - -/// Returns an array of all the results of passing items from this array through the given projection function. -- (NSArray *)filter:(int (^)(id item))predicate; - -- (NSDictionary *)groupBy:(id (^)(id value))keySelector; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/FunctionalUtil.m b/SignalUtilitiesKit/Utilities/FunctionalUtil.m deleted file mode 100644 index 65fe6dc5c..000000000 --- a/SignalUtilitiesKit/Utilities/FunctionalUtil.m +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "FunctionalUtil.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface FUBadArgument : NSException - -+ (FUBadArgument *) new:(NSString *)reason; -+ (void)raise:(NSString *)message; - -@end - -@implementation FUBadArgument - -+ (FUBadArgument *) new:(NSString *)reason { - return [[FUBadArgument alloc] initWithName:@"Invalid Argument" reason:reason userInfo:nil]; -} -+ (void)raise:(NSString *)message { - [FUBadArgument raise:@"Invalid Argument" format:@"%@", message]; -} - -@end - -#define tskit_require(expr) \ - if (!(expr)) { \ - NSString *reason = \ - [NSString stringWithFormat:@"require %@ (in %s at line %d)", (@ #expr), __FILE__, __LINE__]; \ - OWSLogError(@"%@", reason); \ - [FUBadArgument raise:reason]; \ - }; - - -@implementation NSArray (FunctionalUtil) -- (bool)any:(int (^)(id item))predicate { - tskit_require(predicate != nil); - for (id e in self) { - if (predicate(e)) { - return true; - } - } - return false; -} -- (bool)all:(int (^)(id item))predicate { - tskit_require(predicate != nil); - for (id e in self) { - if (!predicate(e)) { - return false; - } - } - return true; -} -- (NSArray *)map:(id (^)(id item))projection { - tskit_require(projection != nil); - - NSMutableArray *r = [NSMutableArray arrayWithCapacity:self.count]; - for (id e in self) { - [r addObject:projection(e)]; - } - return r; -} -- (NSArray *)filter:(int (^)(id item))predicate { - tskit_require(predicate != nil); - - NSMutableArray *r = [NSMutableArray array]; - for (id e in self) { - if (predicate(e)) { - [r addObject:e]; - } - } - return r; -} - -- (NSDictionary *)groupBy:(id (^)(id value))keySelector { - tskit_require(keySelector != nil); - - NSMutableDictionary *result = [NSMutableDictionary dictionary]; - - for (id item in self) { - id key = keySelector(item); - - NSMutableArray *group = result[key]; - if (group == nil) { - group = [NSMutableArray array]; - result[key] = group; - } - [group addObject:item]; - } - - return result; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/NSURLSessionDataTask+StatusCode.h b/SignalUtilitiesKit/Utilities/NSURLSessionDataTask+StatusCode.h deleted file mode 100644 index 62718ffe3..000000000 --- a/SignalUtilitiesKit/Utilities/NSURLSessionDataTask+StatusCode.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface NSURLSessionTask (StatusCode) - -- (long)statusCode; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/NSURLSessionDataTask+StatusCode.m b/SignalUtilitiesKit/Utilities/NSURLSessionDataTask+StatusCode.m deleted file mode 100644 index 212eeac55..000000000 --- a/SignalUtilitiesKit/Utilities/NSURLSessionDataTask+StatusCode.m +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "NSURLSessionDataTask+StatusCode.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation NSURLSessionTask (StatusCode) - -- (long)statusCode { - NSHTTPURLResponse *response = (NSHTTPURLResponse *)self.response; - return response.statusCode; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/OWSError.h b/SignalUtilitiesKit/Utilities/OWSError.h deleted file mode 100644 index e4772fc17..000000000 --- a/SignalUtilitiesKit/Utilities/OWSError.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const OWSSignalServiceKitErrorDomain; - -typedef NS_ENUM(NSInteger, OWSErrorCode) { - OWSErrorCodeInvalidMethodParameters = 11, - OWSErrorCodeUnableToProcessServerResponse = 12, - OWSErrorCodeFailedToDecodeJson = 13, - OWSErrorCodeFailedToEncodeJson = 14, - OWSErrorCodeFailedToDecodeQR = 15, - OWSErrorCodePrivacyVerificationFailure = 20, - OWSErrorCodeUntrustedIdentity = 25, - OWSErrorCodeFailedToSendOutgoingMessage = 30, - OWSErrorCodeAssertionFailure = 31, - OWSErrorCodeFailedToDecryptMessage = 100, - OWSErrorCodeFailedToDecryptUDMessage = 101, - OWSErrorCodeFailedToEncryptMessage = 110, - OWSErrorCodeFailedToEncryptUDMessage = 111, - OWSErrorCodeSignalServiceFailure = 1001, - OWSErrorCodeSignalServiceRateLimited = 1010, - OWSErrorCodeUserError = 2001, - OWSErrorCodeMessageSendDisabledDueToPreKeyUpdateFailures = 777405, - OWSErrorCodeMessageSendFailedToBlockList = 777406, - OWSErrorCodeMessageSendNoValidRecipients = 777407, - OWSErrorCodeContactsUpdaterRateLimit = 777408, - OWSErrorCodeCouldNotWriteAttachmentData = 777409, - OWSErrorCodeMessageDeletedBeforeSent = 777410, - OWSErrorCodeDatabaseConversionFatalError = 777411, - OWSErrorCodeMoveFileToSharedDataContainerError = 777412, - OWSErrorCodeRegistrationMissing2FAPIN = 777413, - OWSErrorCodeDebugLogUploadFailed = 777414, - // A non-recoverable error occured while exporting a backup. - OWSErrorCodeExportBackupFailed = 777415, - // A possibly recoverable error occured while exporting a backup. - OWSErrorCodeExportBackupError = 777416, - // A non-recoverable error occured while importing a backup. - OWSErrorCodeImportBackupFailed = 777417, - // A possibly recoverable error occured while importing a backup. - OWSErrorCodeImportBackupError = 777418, - // A non-recoverable while importing or exporting a backup. - OWSErrorCodeBackupFailure = 777419, - OWSErrorCodeLocalAuthenticationError = 777420, - OWSErrorCodeMessageRequestFailed = 777421, - OWSErrorCodeMessageResponseFailed = 777422, - OWSErrorCodeInvalidMessage = 777423, - OWSErrorCodeProfileUpdateFailed = 777424, - OWSErrorCodeAvatarWriteFailed = 777425, - OWSErrorCodeAvatarUploadFailed = 777426, - OWSErrorCodeNoSessionForTransientMessage, -}; - -extern NSString *const OWSErrorRecipientIdentifierKey; - -extern NSError *OWSErrorWithCodeDescription(OWSErrorCode code, NSString *description); -extern NSError *OWSErrorMakeUntrustedIdentityError(NSString *description, NSString *recipientId); -extern NSError *OWSErrorMakeUnableToProcessServerResponseError(void); -extern NSError *OWSErrorMakeFailedToSendOutgoingMessageError(void); -extern NSError *OWSErrorMakeAssertionError(NSString *description); -extern NSError *OWSErrorMakeMessageSendDisabledDueToPreKeyUpdateFailuresError(void); -extern NSError *OWSErrorMakeMessageSendFailedDueToBlockListError(void); -extern NSError *OWSErrorMakeWriteAttachmentDataError(void); - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/OWSError.m b/SignalUtilitiesKit/Utilities/OWSError.m deleted file mode 100644 index f8096d70e..000000000 --- a/SignalUtilitiesKit/Utilities/OWSError.m +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSError.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const OWSSignalServiceKitErrorDomain = @"OWSSignalServiceKitErrorDomain"; -NSString *const OWSErrorRecipientIdentifierKey = @"OWSErrorKeyRecipientIdentifier"; - -NSError *OWSErrorWithCodeDescription(OWSErrorCode code, NSString *description) -{ - return [NSError errorWithDomain:OWSSignalServiceKitErrorDomain - code:code - userInfo:@{ NSLocalizedDescriptionKey: description }]; -} - -NSError *OWSErrorMakeUnableToProcessServerResponseError() -{ - return OWSErrorWithCodeDescription(OWSErrorCodeUnableToProcessServerResponse, - NSLocalizedString(@"ERROR_DESCRIPTION_SERVER_FAILURE", @"Generic server error")); -} - -NSError *OWSErrorMakeFailedToSendOutgoingMessageError() -{ - return OWSErrorWithCodeDescription(OWSErrorCodeFailedToSendOutgoingMessage, - NSLocalizedString(@"ERROR_DESCRIPTION_CLIENT_SENDING_FAILURE", @"Generic notice when message failed to send.")); -} - -NSError *OWSErrorMakeAssertionError(NSString *description) -{ - OWSCFailDebug(@"Assertion failed: %@", description); - return OWSErrorWithCodeDescription(OWSErrorCodeAssertionFailure, - NSLocalizedString(@"ERROR_DESCRIPTION_UNKNOWN_ERROR", @"Worst case generic error message")); -} - -NSError *OWSErrorMakeUntrustedIdentityError(NSString *description, NSString *recipientId) -{ - return [NSError - errorWithDomain:OWSSignalServiceKitErrorDomain - code:OWSErrorCodeUntrustedIdentity - userInfo:@{ NSLocalizedDescriptionKey : description, OWSErrorRecipientIdentifierKey : recipientId }]; -} - -NSError *OWSErrorMakeMessageSendDisabledDueToPreKeyUpdateFailuresError() -{ - return OWSErrorWithCodeDescription(OWSErrorCodeMessageSendDisabledDueToPreKeyUpdateFailures, - NSLocalizedString(@"ERROR_DESCRIPTION_MESSAGE_SEND_DISABLED_PREKEY_UPDATE_FAILURES", - @"Error message indicating that message send is disabled due to prekey update failures")); -} - -NSError *OWSErrorMakeMessageSendFailedDueToBlockListError() -{ - return OWSErrorWithCodeDescription(OWSErrorCodeMessageSendFailedToBlockList, - NSLocalizedString(@"ERROR_DESCRIPTION_MESSAGE_SEND_FAILED_DUE_TO_BLOCK_LIST", - @"Error message indicating that message send failed due to block list")); -} - -NSError *OWSErrorMakeWriteAttachmentDataError() -{ - return OWSErrorWithCodeDescription(OWSErrorCodeCouldNotWriteAttachmentData, - NSLocalizedString(@"ERROR_DESCRIPTION_MESSAGE_SEND_FAILED_DUE_TO_FAILED_ATTACHMENT_WRITE", - @"Error message indicating that message send failed due to failed attachment write")); -} - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/OWSOperation.h b/SignalUtilitiesKit/Utilities/OWSOperation.h deleted file mode 100644 index ebeeaa53f..000000000 --- a/SignalUtilitiesKit/Utilities/OWSOperation.h +++ /dev/null @@ -1,88 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSInteger, OWSOperationState) { - OWSOperationStateNew, - OWSOperationStateExecuting, - OWSOperationStateFinished -}; - -// A base class for implementing retryable operations. -// To utilize the retryable behavior: -// Set remainingRetries to something greater than 0, and when you're reporting an error, -// set `error.isRetryable = YES`. -// If the failure is one that will not succeed upon retry, set `error.isFatal = YES`. -// -// isRetryable and isFatal are opposites but not redundant. -// -// If a group message send fails, the send will be retried if any of the errors were retryable UNLESS -// any of the errors were fatal. Fatal errors trump retryable errors. -@interface OWSOperation : NSOperation - -@property (readonly, nullable) NSError *failingError; - -// Defaults to 0, set to greater than 0 in init if you'd like the operation to be retryable. -@property NSUInteger remainingRetries; - -#pragma mark - Mandatory Subclass Overrides - -// Called every retry, this is where the bulk of the operation's work should go. -- (void)run; - -#pragma mark - Optional Subclass Overrides - -// Called one time only -- (nullable NSError *)checkForPreconditionError; - -// Called at most one time. -- (void)didSucceed; - -// Called at most one time. -- (void)didCancel; - -// Called zero or more times, retry may be possible -- (void)didReportError:(NSError *)error; - -// Called at most one time, once retry is no longer possible. -- (void)didFailWithError:(NSError *)error NS_SWIFT_NAME(didFail(error:)); - -// How long to wait before retry, if possible -- (NSTimeInterval)retryInterval; - -#pragma mark - Success/Error - Do Not Override - -// Runs now if a retry timer has been set by a previous failure, -// otherwise assumes we're currently running and does nothing. -- (void)runAnyQueuedRetry; - -// Report that the operation completed successfully. -// -// Each invocation of `run` must make exactly one call to one of: `reportSuccess`, `reportCancelled`, or `reportError:` -- (void)reportSuccess; - -// Call this when you abort before completion due to being cancelled. -// -// Each invocation of `run` must make exactly one call to one of: `reportSuccess`, `reportCancelled`, or `reportError:` -- (void)reportCancelled; - -// Report that the operation failed to complete due to an error. -// -// Each invocation of `run` must make exactly one call to one of: `reportSuccess`, `reportCancelled`, or `reportError:` -// You must ensure that `run` cannot succeed after calling `reportError`, e.g. generally you'll write something like -// this: -// -// [self reportError:someError]; -// return; -// -// If the error is terminal, and you want to avoid retry, report an error with `error.isFatal = YES` otherwise the -// operation will retry if possible. -- (void)reportError:(NSError *)error; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/OWSOperation.m b/SignalUtilitiesKit/Utilities/OWSOperation.m deleted file mode 100644 index 47e511990..000000000 --- a/SignalUtilitiesKit/Utilities/OWSOperation.m +++ /dev/null @@ -1,253 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSOperation.h" -#import "OWSError.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const OWSOperationKeyIsExecuting = @"isExecuting"; -NSString *const OWSOperationKeyIsFinished = @"isFinished"; - -@interface OWSOperation () - -@property (nullable) NSError *failingError; -@property (atomic) OWSOperationState operationState; -@property (nonatomic) OWSBackgroundTask *backgroundTask; - -// This property should only be accessed on the main queue. -@property (nonatomic) NSTimer *_Nullable retryTimer; - -@end - -@implementation OWSOperation - -- (instancetype)init -{ - self = [super init]; - if (!self) { - return self; - } - - _operationState = OWSOperationStateNew; - _backgroundTask = [OWSBackgroundTask backgroundTaskWithLabel:self.logTag]; - - // Operations are not retryable by default. - _remainingRetries = 0; - - return self; -} - -- (void)dealloc -{ - OWSLogDebug(@"in dealloc"); -} - -#pragma mark - Subclass Overrides - -// Called one time only -- (nullable NSError *)checkForPreconditionError -{ - // OWSOperation have a notion of failure, which is inferred by the presence of a `failingError`. - // - // By default, any failing dependency cascades that failure to it's dependent. - // If you'd like different behavior, override this method (`checkForPreconditionError`) without calling `super`. - for (NSOperation *dependency in self.dependencies) { - if (![dependency isKindOfClass:[OWSOperation class]]) { - // Native operations, like NSOperation and NSBlockOperation have no notion of "failure". - // So there's no `failingError` to cascade. - continue; - } - - OWSOperation *dependentOperation = (OWSOperation *)dependency; - - // Don't proceed if dependency failed - surface the dependency's error. - NSError *_Nullable dependencyError = dependentOperation.failingError; - if (dependencyError != nil) { - return dependencyError; - } - } - - return nil; -} - -// Called every retry, this is where the bulk of the operation's work should go. -- (void)run -{ - OWSAbstractMethod(); -} - -// Called at most one time. -- (void)didSucceed -{ - // no-op - // Override in subclass if necessary -} - -// Called at most one time. -- (void)didCancel -{ - // no-op - // Override in subclass if necessary -} - -// Called zero or more times, retry may be possible -- (void)didReportError:(NSError *)error -{ - // no-op - // Override in subclass if necessary -} - -// Called at most one time, once retry is no longer possible. -- (void)didFailWithError:(NSError *)error -{ - // no-op - // Override in subclass if necessary -} - -#pragma mark - NSOperation overrides - -// Do not override this method in a subclass instead, override `run` -- (void)main -{ - OWSLogDebug(@"started."); - NSError *_Nullable preconditionError = [self checkForPreconditionError]; - if (preconditionError) { - [self failOperationWithError:preconditionError]; - return; - } - - if (self.isCancelled) { - [self reportCancelled]; - return; - } - - [self run]; -} - -- (void)runAnyQueuedRetry -{ - dispatch_async(dispatch_get_main_queue(), ^{ - NSTimer *_Nullable retryTimer = self.retryTimer; - self.retryTimer = nil; - [retryTimer invalidate]; - - if (retryTimer != nil) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self run]; - }); - } else { - OWSLogVerbose(@"not re-running since operation is already running."); - } - }); -} - -#pragma mark - Public Methods - -// These methods are not intended to be subclassed -- (void)reportSuccess -{ - OWSLogDebug(@"succeeded."); - [self didSucceed]; - [self markAsComplete]; -} - -// These methods are not intended to be subclassed -- (void)reportCancelled -{ - OWSLogDebug(@"cancelled."); - [self didCancel]; - [self markAsComplete]; -} - -- (void)reportError:(NSError *)error -{ - [self didReportError:error]; - - if (self.remainingRetries == 0) { - [self failOperationWithError:error]; - return; - } - - self.remainingRetries--; - - dispatch_async(dispatch_get_main_queue(), ^{ - OWSAssertDebug(self.retryTimer == nil); - [self.retryTimer invalidate]; - - // The `scheduledTimerWith*` methods add the timer to the current thread's RunLoop. - // Since Operations typically run on a background thread, that would mean the background - // thread's RunLoop. However, the OS can spin down background threads if there's no work - // being done, so we run the risk of the timer's RunLoop being deallocated before it's - // fired. - // - // To ensure the timer's thread sticks around, we schedule it while on the main RunLoop. - self.retryTimer = [NSTimer weakScheduledTimerWithTimeInterval:self.retryInterval - target:self - selector:@selector(runAnyQueuedRetry) - userInfo:nil - repeats:NO]; - }); -} - -// Override in subclass if you want something more sophisticated, e.g. exponential backoff -- (NSTimeInterval)retryInterval -{ - return 0.1; -} - -#pragma mark - Life Cycle - -- (void)failOperationWithError:(NSError *)error -{ - OWSLogDebug(@"failed terminally."); - self.failingError = error; - - [self didFailWithError:error]; - [self markAsComplete]; -} - -- (BOOL)isExecuting -{ - return self.operationState == OWSOperationStateExecuting; -} - -- (BOOL)isFinished -{ - return self.operationState == OWSOperationStateFinished; -} - -- (void)start -{ - [self willChangeValueForKey:OWSOperationKeyIsExecuting]; - self.operationState = OWSOperationStateExecuting; - [self didChangeValueForKey:OWSOperationKeyIsExecuting]; - - [self main]; -} - -- (void)markAsComplete -{ - [self willChangeValueForKey:OWSOperationKeyIsExecuting]; - [self willChangeValueForKey:OWSOperationKeyIsFinished]; - - // Ensure we call the success or failure handler exactly once. - @synchronized(self) - { - OWSAssertDebug(self.operationState != OWSOperationStateFinished); - - self.operationState = OWSOperationStateFinished; - } - - [self didChangeValueForKey:OWSOperationKeyIsExecuting]; - [self didChangeValueForKey:OWSOperationKeyIsFinished]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/ReachabilityManager.swift b/SignalUtilitiesKit/Utilities/ReachabilityManager.swift index c9b884db8..6f683bcb7 100644 --- a/SignalUtilitiesKit/Utilities/ReachabilityManager.swift +++ b/SignalUtilitiesKit/Utilities/ReachabilityManager.swift @@ -3,6 +3,7 @@ import Foundation import Reachability import SignalCoreKit +import SessionMessagingKit /// **Warning:** The simulator doesn't detect reachability correctly so if you are seeing odd/incorrect reachability states double /// check on an actual device before trying to replace this implementation diff --git a/SignalUtilitiesKit/Utilities/SignalIOS.pb.swift b/SignalUtilitiesKit/Utilities/SignalIOS.pb.swift deleted file mode 100644 index 588c729ed..000000000 --- a/SignalUtilitiesKit/Utilities/SignalIOS.pb.swift +++ /dev/null @@ -1,318 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: SignalIOS.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -//* -// Copyright (C) 2014-2016 Open Whisper Systems -// -// Licensed according to the LICENSE file in this repository. - -/// iOS - since we use a modern proto-compiler, we must specify -/// the legacy proto format. - -import Foundation -import SwiftProtobuf - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -struct IOSProtos_BackupSnapshot { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var entity: [IOSProtos_BackupSnapshot.BackupEntity] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - struct BackupEntity { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var type: IOSProtos_BackupSnapshot.BackupEntity.TypeEnum { - get {return _type ?? .unknown} - set {_type = newValue} - } - /// Returns true if `type` has been explicitly set. - var hasType: Bool {return self._type != nil} - /// Clears the value of `type`. Subsequent reads from it will return its default value. - mutating func clearType() {self._type = nil} - - /// @required - var entityData: Data { - get {return _entityData ?? SwiftProtobuf.Internal.emptyData} - set {_entityData = newValue} - } - /// Returns true if `entityData` has been explicitly set. - var hasEntityData: Bool {return self._entityData != nil} - /// Clears the value of `entityData`. Subsequent reads from it will return its default value. - mutating func clearEntityData() {self._entityData = nil} - - /// @required - var collection: String { - get {return _collection ?? String()} - set {_collection = newValue} - } - /// Returns true if `collection` has been explicitly set. - var hasCollection: Bool {return self._collection != nil} - /// Clears the value of `collection`. Subsequent reads from it will return its default value. - mutating func clearCollection() {self._collection = nil} - - /// @required - var key: String { - get {return _key ?? String()} - set {_key = newValue} - } - /// Returns true if `key` has been explicitly set. - var hasKey: Bool {return self._key != nil} - /// Clears the value of `key`. Subsequent reads from it will return its default value. - mutating func clearKey() {self._key = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum TypeEnum: SwiftProtobuf.Enum { - typealias RawValue = Int - case unknown // = 0 - case migration // = 1 - case thread // = 2 - case interaction // = 3 - case attachment // = 4 - case misc // = 5 - - init() { - self = .unknown - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unknown - case 1: self = .migration - case 2: self = .thread - case 3: self = .interaction - case 4: self = .attachment - case 5: self = .misc - default: return nil - } - } - - var rawValue: Int { - switch self { - case .unknown: return 0 - case .migration: return 1 - case .thread: return 2 - case .interaction: return 3 - case .attachment: return 4 - case .misc: return 5 - } - } - - } - - init() {} - - fileprivate var _type: IOSProtos_BackupSnapshot.BackupEntity.TypeEnum? = nil - fileprivate var _entityData: Data? = nil - fileprivate var _collection: String? = nil - fileprivate var _key: String? = nil - } - - init() {} -} - -#if swift(>=4.2) - -extension IOSProtos_BackupSnapshot.BackupEntity.TypeEnum: CaseIterable { - // Support synthesized by the compiler. -} - -#endif // swift(>=4.2) - -struct IOSProtos_DeviceName { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var ephemeralPublic: Data { - get {return _ephemeralPublic ?? SwiftProtobuf.Internal.emptyData} - set {_ephemeralPublic = newValue} - } - /// Returns true if `ephemeralPublic` has been explicitly set. - var hasEphemeralPublic: Bool {return self._ephemeralPublic != nil} - /// Clears the value of `ephemeralPublic`. Subsequent reads from it will return its default value. - mutating func clearEphemeralPublic() {self._ephemeralPublic = nil} - - /// @required - var syntheticIv: Data { - get {return _syntheticIv ?? SwiftProtobuf.Internal.emptyData} - set {_syntheticIv = newValue} - } - /// Returns true if `syntheticIv` has been explicitly set. - var hasSyntheticIv: Bool {return self._syntheticIv != nil} - /// Clears the value of `syntheticIv`. Subsequent reads from it will return its default value. - mutating func clearSyntheticIv() {self._syntheticIv = nil} - - /// @required - var ciphertext: Data { - get {return _ciphertext ?? SwiftProtobuf.Internal.emptyData} - set {_ciphertext = newValue} - } - /// Returns true if `ciphertext` has been explicitly set. - var hasCiphertext: Bool {return self._ciphertext != nil} - /// Clears the value of `ciphertext`. Subsequent reads from it will return its default value. - mutating func clearCiphertext() {self._ciphertext = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _ephemeralPublic: Data? = nil - fileprivate var _syntheticIv: Data? = nil - fileprivate var _ciphertext: Data? = nil -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "IOSProtos" - -extension IOSProtos_BackupSnapshot: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".BackupSnapshot" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "entity"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeRepeatedMessageField(value: &self.entity) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.entity.isEmpty { - try visitor.visitRepeatedMessageField(value: self.entity, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: IOSProtos_BackupSnapshot, rhs: IOSProtos_BackupSnapshot) -> Bool { - if lhs.entity != rhs.entity {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension IOSProtos_BackupSnapshot.BackupEntity: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = IOSProtos_BackupSnapshot.protoMessageName + ".BackupEntity" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "type"), - 2: .same(proto: "entityData"), - 3: .same(proto: "collection"), - 4: .same(proto: "key"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularEnumField(value: &self._type) - case 2: try decoder.decodeSingularBytesField(value: &self._entityData) - case 3: try decoder.decodeSingularStringField(value: &self._collection) - case 4: try decoder.decodeSingularStringField(value: &self._key) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._type { - try visitor.visitSingularEnumField(value: v, fieldNumber: 1) - } - if let v = self._entityData { - try visitor.visitSingularBytesField(value: v, fieldNumber: 2) - } - if let v = self._collection { - try visitor.visitSingularStringField(value: v, fieldNumber: 3) - } - if let v = self._key { - try visitor.visitSingularStringField(value: v, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: IOSProtos_BackupSnapshot.BackupEntity, rhs: IOSProtos_BackupSnapshot.BackupEntity) -> Bool { - if lhs._type != rhs._type {return false} - if lhs._entityData != rhs._entityData {return false} - if lhs._collection != rhs._collection {return false} - if lhs._key != rhs._key {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension IOSProtos_BackupSnapshot.BackupEntity.TypeEnum: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNKNOWN"), - 1: .same(proto: "MIGRATION"), - 2: .same(proto: "THREAD"), - 3: .same(proto: "INTERACTION"), - 4: .same(proto: "ATTACHMENT"), - 5: .same(proto: "MISC"), - ] -} - -extension IOSProtos_DeviceName: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".DeviceName" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "ephemeralPublic"), - 2: .same(proto: "syntheticIv"), - 3: .same(proto: "ciphertext"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularBytesField(value: &self._ephemeralPublic) - case 2: try decoder.decodeSingularBytesField(value: &self._syntheticIv) - case 3: try decoder.decodeSingularBytesField(value: &self._ciphertext) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._ephemeralPublic { - try visitor.visitSingularBytesField(value: v, fieldNumber: 1) - } - if let v = self._syntheticIv { - try visitor.visitSingularBytesField(value: v, fieldNumber: 2) - } - if let v = self._ciphertext { - try visitor.visitSingularBytesField(value: v, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: IOSProtos_DeviceName, rhs: IOSProtos_DeviceName) -> Bool { - if lhs._ephemeralPublic != rhs._ephemeralPublic {return false} - if lhs._syntheticIv != rhs._syntheticIv {return false} - if lhs._ciphertext != rhs._ciphertext {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/SignalUtilitiesKit/Utilities/SignalIOSProto.swift b/SignalUtilitiesKit/Utilities/SignalIOSProto.swift deleted file mode 100644 index 5761fbda7..000000000 --- a/SignalUtilitiesKit/Utilities/SignalIOSProto.swift +++ /dev/null @@ -1,409 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -// WARNING: This code is generated. Only edit within the markers. - -public enum SignalIOSProtoError: Error { - case invalidProtobuf(description: String) -} - -// MARK: - SignalIOSProtoBackupSnapshotBackupEntity - -@objc public class SignalIOSProtoBackupSnapshotBackupEntity: NSObject { - - // MARK: - SignalIOSProtoBackupSnapshotBackupEntityType - - @objc public enum SignalIOSProtoBackupSnapshotBackupEntityType: Int32 { - case unknown = 0 - case migration = 1 - case thread = 2 - case interaction = 3 - case attachment = 4 - case misc = 5 - } - - private class func SignalIOSProtoBackupSnapshotBackupEntityTypeWrap(_ value: IOSProtos_BackupSnapshot.BackupEntity.TypeEnum) -> SignalIOSProtoBackupSnapshotBackupEntityType { - switch value { - case .unknown: return .unknown - case .migration: return .migration - case .thread: return .thread - case .interaction: return .interaction - case .attachment: return .attachment - case .misc: return .misc - } - } - - private class func SignalIOSProtoBackupSnapshotBackupEntityTypeUnwrap(_ value: SignalIOSProtoBackupSnapshotBackupEntityType) -> IOSProtos_BackupSnapshot.BackupEntity.TypeEnum { - switch value { - case .unknown: return .unknown - case .migration: return .migration - case .thread: return .thread - case .interaction: return .interaction - case .attachment: return .attachment - case .misc: return .misc - } - } - - // MARK: - SignalIOSProtoBackupSnapshotBackupEntityBuilder - - @objc public class func builder(type: SignalIOSProtoBackupSnapshotBackupEntityType, entityData: Data, collection: String, key: String) -> SignalIOSProtoBackupSnapshotBackupEntityBuilder { - return SignalIOSProtoBackupSnapshotBackupEntityBuilder(type: type, entityData: entityData, collection: collection, key: key) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SignalIOSProtoBackupSnapshotBackupEntityBuilder { - let builder = SignalIOSProtoBackupSnapshotBackupEntityBuilder(type: type, entityData: entityData, collection: collection, key: key) - return builder - } - - @objc public class SignalIOSProtoBackupSnapshotBackupEntityBuilder: NSObject { - - private var proto = IOSProtos_BackupSnapshot.BackupEntity() - - @objc fileprivate override init() {} - - @objc fileprivate init(type: SignalIOSProtoBackupSnapshotBackupEntityType, entityData: Data, collection: String, key: String) { - super.init() - - setType(type) - setEntityData(entityData) - setCollection(collection) - setKey(key) - } - - @objc public func setType(_ valueParam: SignalIOSProtoBackupSnapshotBackupEntityType) { - proto.type = SignalIOSProtoBackupSnapshotBackupEntityTypeUnwrap(valueParam) - } - - @objc public func setEntityData(_ valueParam: Data) { - proto.entityData = valueParam - } - - @objc public func setCollection(_ valueParam: String) { - proto.collection = valueParam - } - - @objc public func setKey(_ valueParam: String) { - proto.key = valueParam - } - - @objc public func build() throws -> SignalIOSProtoBackupSnapshotBackupEntity { - return try SignalIOSProtoBackupSnapshotBackupEntity.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SignalIOSProtoBackupSnapshotBackupEntity.parseProto(proto).serializedData() - } - } - - fileprivate let proto: IOSProtos_BackupSnapshot.BackupEntity - - @objc public let type: SignalIOSProtoBackupSnapshotBackupEntityType - - @objc public let entityData: Data - - @objc public let collection: String - - @objc public let key: String - - private init(proto: IOSProtos_BackupSnapshot.BackupEntity, - type: SignalIOSProtoBackupSnapshotBackupEntityType, - entityData: Data, - collection: String, - key: String) { - self.proto = proto - self.type = type - self.entityData = entityData - self.collection = collection - self.key = key - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SignalIOSProtoBackupSnapshotBackupEntity { - let proto = try IOSProtos_BackupSnapshot.BackupEntity(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: IOSProtos_BackupSnapshot.BackupEntity) throws -> SignalIOSProtoBackupSnapshotBackupEntity { - guard proto.hasType else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") - } - let type = SignalIOSProtoBackupSnapshotBackupEntityTypeWrap(proto.type) - - guard proto.hasEntityData else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: entityData") - } - let entityData = proto.entityData - - guard proto.hasCollection else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: collection") - } - let collection = proto.collection - - guard proto.hasKey else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: key") - } - let key = proto.key - - // MARK: - Begin Validation Logic for SignalIOSProtoBackupSnapshotBackupEntity - - - // MARK: - End Validation Logic for SignalIOSProtoBackupSnapshotBackupEntity - - - let result = SignalIOSProtoBackupSnapshotBackupEntity(proto: proto, - type: type, - entityData: entityData, - collection: collection, - key: key) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SignalIOSProtoBackupSnapshotBackupEntity { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SignalIOSProtoBackupSnapshotBackupEntity.SignalIOSProtoBackupSnapshotBackupEntityBuilder { - @objc public func buildIgnoringErrors() -> SignalIOSProtoBackupSnapshotBackupEntity? { - return try! self.build() - } -} - -#endif - -// MARK: - SignalIOSProtoBackupSnapshot - -@objc public class SignalIOSProtoBackupSnapshot: NSObject { - - // MARK: - SignalIOSProtoBackupSnapshotBuilder - - @objc public class func builder() -> SignalIOSProtoBackupSnapshotBuilder { - return SignalIOSProtoBackupSnapshotBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SignalIOSProtoBackupSnapshotBuilder { - let builder = SignalIOSProtoBackupSnapshotBuilder() - builder.setEntity(entity) - return builder - } - - @objc public class SignalIOSProtoBackupSnapshotBuilder: NSObject { - - private var proto = IOSProtos_BackupSnapshot() - - @objc fileprivate override init() {} - - @objc public func addEntity(_ valueParam: SignalIOSProtoBackupSnapshotBackupEntity) { - var items = proto.entity - items.append(valueParam.proto) - proto.entity = items - } - - @objc public func setEntity(_ wrappedItems: [SignalIOSProtoBackupSnapshotBackupEntity]) { - proto.entity = wrappedItems.map { $0.proto } - } - - @objc public func build() throws -> SignalIOSProtoBackupSnapshot { - return try SignalIOSProtoBackupSnapshot.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SignalIOSProtoBackupSnapshot.parseProto(proto).serializedData() - } - } - - fileprivate let proto: IOSProtos_BackupSnapshot - - @objc public let entity: [SignalIOSProtoBackupSnapshotBackupEntity] - - private init(proto: IOSProtos_BackupSnapshot, - entity: [SignalIOSProtoBackupSnapshotBackupEntity]) { - self.proto = proto - self.entity = entity - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SignalIOSProtoBackupSnapshot { - let proto = try IOSProtos_BackupSnapshot(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: IOSProtos_BackupSnapshot) throws -> SignalIOSProtoBackupSnapshot { - var entity: [SignalIOSProtoBackupSnapshotBackupEntity] = [] - entity = try proto.entity.map { try SignalIOSProtoBackupSnapshotBackupEntity.parseProto($0) } - - // MARK: - Begin Validation Logic for SignalIOSProtoBackupSnapshot - - - // MARK: - End Validation Logic for SignalIOSProtoBackupSnapshot - - - let result = SignalIOSProtoBackupSnapshot(proto: proto, - entity: entity) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SignalIOSProtoBackupSnapshot { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SignalIOSProtoBackupSnapshot.SignalIOSProtoBackupSnapshotBuilder { - @objc public func buildIgnoringErrors() -> SignalIOSProtoBackupSnapshot? { - return try! self.build() - } -} - -#endif - -// MARK: - SignalIOSProtoDeviceName - -@objc public class SignalIOSProtoDeviceName: NSObject { - - // MARK: - SignalIOSProtoDeviceNameBuilder - - @objc public class func builder(ephemeralPublic: Data, syntheticIv: Data, ciphertext: Data) -> SignalIOSProtoDeviceNameBuilder { - return SignalIOSProtoDeviceNameBuilder(ephemeralPublic: ephemeralPublic, syntheticIv: syntheticIv, ciphertext: ciphertext) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SignalIOSProtoDeviceNameBuilder { - let builder = SignalIOSProtoDeviceNameBuilder(ephemeralPublic: ephemeralPublic, syntheticIv: syntheticIv, ciphertext: ciphertext) - return builder - } - - @objc public class SignalIOSProtoDeviceNameBuilder: NSObject { - - private var proto = IOSProtos_DeviceName() - - @objc fileprivate override init() {} - - @objc fileprivate init(ephemeralPublic: Data, syntheticIv: Data, ciphertext: Data) { - super.init() - - setEphemeralPublic(ephemeralPublic) - setSyntheticIv(syntheticIv) - setCiphertext(ciphertext) - } - - @objc public func setEphemeralPublic(_ valueParam: Data) { - proto.ephemeralPublic = valueParam - } - - @objc public func setSyntheticIv(_ valueParam: Data) { - proto.syntheticIv = valueParam - } - - @objc public func setCiphertext(_ valueParam: Data) { - proto.ciphertext = valueParam - } - - @objc public func build() throws -> SignalIOSProtoDeviceName { - return try SignalIOSProtoDeviceName.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SignalIOSProtoDeviceName.parseProto(proto).serializedData() - } - } - - fileprivate let proto: IOSProtos_DeviceName - - @objc public let ephemeralPublic: Data - - @objc public let syntheticIv: Data - - @objc public let ciphertext: Data - - private init(proto: IOSProtos_DeviceName, - ephemeralPublic: Data, - syntheticIv: Data, - ciphertext: Data) { - self.proto = proto - self.ephemeralPublic = ephemeralPublic - self.syntheticIv = syntheticIv - self.ciphertext = ciphertext - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SignalIOSProtoDeviceName { - let proto = try IOSProtos_DeviceName(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: IOSProtos_DeviceName) throws -> SignalIOSProtoDeviceName { - guard proto.hasEphemeralPublic else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: ephemeralPublic") - } - let ephemeralPublic = proto.ephemeralPublic - - guard proto.hasSyntheticIv else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: syntheticIv") - } - let syntheticIv = proto.syntheticIv - - guard proto.hasCiphertext else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: ciphertext") - } - let ciphertext = proto.ciphertext - - // MARK: - Begin Validation Logic for SignalIOSProtoDeviceName - - - // MARK: - End Validation Logic for SignalIOSProtoDeviceName - - - let result = SignalIOSProtoDeviceName(proto: proto, - ephemeralPublic: ephemeralPublic, - syntheticIv: syntheticIv, - ciphertext: ciphertext) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SignalIOSProtoDeviceName { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SignalIOSProtoDeviceName.SignalIOSProtoDeviceNameBuilder { - @objc public func buildIgnoringErrors() -> SignalIOSProtoDeviceName? { - return try! self.build() - } -} - -#endif diff --git a/SignalUtilitiesKit/Utilities/SwiftSingletons.swift b/SignalUtilitiesKit/Utilities/SwiftSingletons.swift index 5af0a3177..40bee1b66 100644 --- a/SignalUtilitiesKit/Utilities/SwiftSingletons.swift +++ b/SignalUtilitiesKit/Utilities/SwiftSingletons.swift @@ -2,6 +2,7 @@ import Foundation import SignalCoreKit +import SessionUtilitiesKit public class SwiftSingletons: NSObject { public static let shared = SwiftSingletons() diff --git a/SignalUtilitiesKit/Utilities/TSConstants.h b/SignalUtilitiesKit/Utilities/TSConstants.h deleted file mode 100644 index 3d8542585..000000000 --- a/SignalUtilitiesKit/Utilities/TSConstants.h +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -#ifndef TextSecureKit_Constants_h -#define TextSecureKit_Constants_h - -extern const NSUInteger kOversizeTextMessageSizeThreshold; - -typedef NS_ENUM(NSInteger, TSWhisperMessageType) { - TSUnknownMessageType = 0, - TSEncryptedWhisperMessageType = 1, - TSIgnoreOnIOSWhisperMessageType = 2, // on droid this is the prekey bundle message irrelevant for us - TSPreKeyWhisperMessageType = 3, - TSUnencryptedWhisperMessageType = 4, - TSUnidentifiedSenderMessageType = 6, - TSClosedGroupCiphertextMessageType = 7, - TSFallbackMessageType = 101 -}; - -#pragma mark Server Address - -#define textSecureHTTPTimeOut 10 - -#define kLegalTermsUrlString @"https://getsession.org/privacy-policy/" - -//#ifndef DEBUG - -// Production -#define textSecureWebSocketAPI @"wss://textsecure-service.whispersystems.org/v1/websocket/" -#define textSecureCDNServerURL @"https://cdn.signal.org" -// Use same reflector for service and CDN -#define textSecureServiceReflectorHost @"europe-west1-signal-cdn-reflector.cloudfunctions.net" -#define textSecureCDNReflectorHost @"europe-west1-signal-cdn-reflector.cloudfunctions.net" -#define contactDiscoveryURL @"https://api.directory.signal.org" -#define kUDTrustRoot @"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF" -#define USING_PRODUCTION_SERVICE - -//#else - -// Staging -//#define textSecureWebSocketAPI @"wss://textsecure-service-staging.whispersystems.org/v1/websocket/" -//#define textSecureServerURL @"https://textsecure-service-staging.whispersystems.org/" -//#define textSecureCDNServerURL @"https://cdn-staging.signal.org" -//#define textSecureServiceReflectorHost @"meek-signal-service-staging.appspot.com"; -//#define textSecureCDNReflectorHost @"meek-signal-cdn-staging.appspot.com"; -//#define contactDiscoveryURL @"https://api-staging.directory.signal.org" -//#define kUDTrustRoot @"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx" - -//#endif - -BOOL IsUsingProductionService(void); - -#define textSecureAccountsAPI @"v1/accounts" -#define textSecureAttributesAPI @"/attributes/" - -#define textSecureMessagesAPI @"v1/messages/" -#define textSecureKeysAPI @"v2/keys" -#define textSecureSignedKeysAPI @"v2/keys/signed" -#define textSecureDirectoryAPI @"v1/directory" -#define textSecureAttachmentsAPI @"v1/attachments" -#define textSecureDeviceProvisioningCodeAPI @"v1/devices/provisioning/code" -#define textSecureDeviceProvisioningAPIFormat @"v1/provisioning/%@" -#define textSecureDevicesAPIFormat @"v1/devices/%@" -#define textSecureProfileAPIFormat @"v1/profile/%@" -#define textSecureSetProfileNameAPIFormat @"v1/profile/name/%@" -#define textSecureProfileAvatarFormAPI @"v1/profile/form/avatar" -#define textSecure2FAAPI @"/v1/accounts/pin" - -#define SignalApplicationGroup @"group.com.loki-project.loki-messenger" - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/TSConstants.m b/SignalUtilitiesKit/Utilities/TSConstants.m deleted file mode 100644 index fbd6607e9..000000000 --- a/SignalUtilitiesKit/Utilities/TSConstants.m +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSConstants.h" - -NS_ASSUME_NONNULL_BEGIN - -const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024; - -BOOL IsUsingProductionService() -{ -#ifdef USING_PRODUCTION_SERVICE - return YES; -#else - return NO; -#endif -} - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/UIView+OWS.swift b/SignalUtilitiesKit/Utilities/UIView+OWS.swift index de1a7c5cd..b158c207c 100644 --- a/SignalUtilitiesKit/Utilities/UIView+OWS.swift +++ b/SignalUtilitiesKit/Utilities/UIView+OWS.swift @@ -3,6 +3,7 @@ import Foundation import SessionUIKit import SignalCoreKit +import SessionUtilitiesKit public extension UIEdgeInsets { init(top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat) { diff --git a/SignalUtilitiesKit/Utilities/UIViewController+OWS.swift b/SignalUtilitiesKit/Utilities/UIViewController+OWS.swift index 6791a15e1..d2904ab77 100644 --- a/SignalUtilitiesKit/Utilities/UIViewController+OWS.swift +++ b/SignalUtilitiesKit/Utilities/UIViewController+OWS.swift @@ -2,6 +2,7 @@ import UIKit import SessionUIKit +import SessionUtilitiesKit public extension UIViewController { func findFrontmostViewController(ignoringAlerts: Bool) -> UIViewController { From c63a9d399493cfe9d61139865015b9e5e5804c4a Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 10 Aug 2023 16:44:28 +1000 Subject: [PATCH 13/17] Fixed an issue preventing notifications from working Fixed an issue where Storage could be left in an invalid state when it was completed silently before properly getting setup --- .../NotificationServiceExtension.swift | 12 ++++++++---- SessionUtilitiesKit/Database/Storage.swift | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index b4f40742a..22c6f4948 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -227,6 +227,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // to process new messages. guard !didPerformSetup else { return } + NSLog("[NotificationServiceExtension] Performing setup") didPerformSetup = true _ = AppVersion.sharedInstance() @@ -243,7 +244,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension switch result { // Only 'NSLog' works in the extension - viewable via Console.app case .failure(let error): - NSLog("[NotificationServiceExtension] Failed to complete migrations") + NSLog("[NotificationServiceExtension] Failed to complete migrations: \(error)") self?.completeSilenty() case .success: @@ -288,7 +289,11 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension guard !AppReadiness.isAppReady() else { return } // App isn't ready until storage is ready AND all version migrations are complete. - guard Storage.shared.isValid && migrationsCompleted else { return } + guard Storage.shared.isValid && migrationsCompleted else { + NSLog("[NotificationServiceExtension] Storage invalid") + self.completeSilenty() + return + } SignalUtilitiesKit.Configuration.performMainSetup() @@ -305,8 +310,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension } private func completeSilenty() { - SNLog("Complete silenty") - + NSLog("[NotificationServiceExtension] Complete silently") Storage.suspendDatabaseAccess() self.contentHandler!(.init()) diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 94acc226b..464bf8eb5 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -375,14 +375,14 @@ open class Storage { /// database and other files into the App folder public static func suspendDatabaseAccess(using dependencies: Dependencies = Dependencies()) { NotificationCenter.default.post(name: Database.suspendNotification, object: self) - dependencies.storage.isSuspendedUnsafe = true + if Storage.hasCreatedValidInstance { dependencies.storage.isSuspendedUnsafe = true } } /// This method reverses the database suspension used to prevent the `0xdead10cc` exception (see `suspendDatabaseAccess()` /// above for more information public static func resumeDatabaseAccess(using dependencies: Dependencies = Dependencies()) { NotificationCenter.default.post(name: Database.resumeNotification, object: self) - dependencies.storage.isSuspendedUnsafe = false + if Storage.hasCreatedValidInstance { dependencies.storage.isSuspendedUnsafe = false } } public static func resetAllStorage() { From d863004e6dc9b6f5c5ce86309b16ab0fd58ba817 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 11 Aug 2023 18:02:06 +1000 Subject: [PATCH 14/17] Added a setting to control community message request polling Added logic to broadcast the community message request acceptance to SOGS so we can communicate it to message request senders Fixed an issue where database setting changes wouldn't trigger a live update on a settings screen Fixed an issue where some setting toggles wouldn't animate the state change Fixed a rarw force-unwrap crash --- LibSession-Util | 2 +- Session.xcodeproj/project.pbxproj | 8 ++ Session/Conversations/ConversationVC.swift | 48 ++++---- .../Settings/ThreadSettingsViewModel.swift | 18 ++- .../Translations/de.lproj/Localizable.strings | 4 + .../Translations/en.lproj/Localizable.strings | 4 + .../Translations/es.lproj/Localizable.strings | 4 + .../Translations/fa.lproj/Localizable.strings | 4 + .../Translations/fi.lproj/Localizable.strings | 4 + .../Translations/fr.lproj/Localizable.strings | 4 + .../Translations/hi.lproj/Localizable.strings | 4 + .../Translations/hr.lproj/Localizable.strings | 4 + .../id-ID.lproj/Localizable.strings | 4 + .../Translations/it.lproj/Localizable.strings | 4 + .../Translations/ja.lproj/Localizable.strings | 4 + .../Translations/nl.lproj/Localizable.strings | 4 + .../Translations/pl.lproj/Localizable.strings | 4 + .../pt_BR.lproj/Localizable.strings | 4 + .../Translations/ru.lproj/Localizable.strings | 4 + .../Translations/si.lproj/Localizable.strings | 4 + .../Translations/sk.lproj/Localizable.strings | 4 + .../Translations/sv.lproj/Localizable.strings | 4 + .../Translations/th.lproj/Localizable.strings | 4 + .../vi-VN.lproj/Localizable.strings | 4 + .../zh-Hant.lproj/Localizable.strings | 4 + .../zh_CN.lproj/Localizable.strings | 4 + .../ConversationSettingsViewModel.swift | 32 +++++- .../NotificationSettingsViewModel.swift | 66 ++++++++--- .../Settings/PrivacySettingsViewModel.swift | 106 +++++++++++++++--- .../Shared/SessionTableViewController.swift | 9 +- Session/Shared/SessionTableViewModel.swift | 9 +- .../Shared/Types/SessionCell+Accessory.swift | 45 +++++--- .../Views/SessionCell+AccessoryView.swift | 16 ++- Session/Shared/Views/SessionCell.swift | 8 +- Session/Utilities/MockDataGenerator.swift | 9 +- SessionMessagingKit/Configuration.swift | 3 +- .../Migrations/_003_YDBToGRDBMigration.swift | 9 +- .../_005_FixDeletedMessageReadState.swift | 2 +- .../_006_FixHiddenModAdminSupport.swift | 2 +- .../_007_HomeQueryOptimisationIndexes.swift | 2 +- .../Migrations/_008_EmojiReacts.swift | 2 +- .../Migrations/_009_OpenGroupPermission.swift | 2 +- .../_011_AddPendingReadReceipts.swift | 2 +- .../Migrations/_012_AddFTSIfNeeded.swift | 2 +- .../_015_BlockCommunityMessageRequests.swift | 33 ++++++ .../Database/Models/Profile.swift | 29 ++++- .../VisibleMessage+Profile.swift | 22 +++- .../Open Groups/OpenGroupAPI.swift | 12 +- .../Protos/Generated/SNProto.swift | 14 +++ .../Protos/Generated/SessionProtos.pb.swift | 56 ++++++++- .../Generated/WebSocketResources.pb.swift | 7 ++ .../Protos/SessionProtos.proto | 29 ++--- .../MessageReceiver+VisibleMessages.swift | 1 + .../Sending & Receiving/MessageSender.swift | 3 +- .../SessionUtil+Contacts.swift | 3 +- .../Config Handling/SessionUtil+Shared.swift | 24 ++++ .../SessionUtil+UserProfile.swift | 26 +++++ .../Database/Setting+Utilities.swift | 58 ++++++++++ .../SessionThreadViewModel.swift | 6 +- .../Utilities/Preferences.swift | 3 + .../Utilities/ProfileManager.swift | 7 ++ .../Configs/ConfigUserProfileSpec.swift | 15 +++ .../ShareNavController.swift | 4 +- .../Combine/Publisher+Utilities.swift | 17 +++ .../Database/Models/Setting.swift | 48 +++++++- 65 files changed, 766 insertions(+), 141 deletions(-) create mode 100644 SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift create mode 100644 SessionMessagingKit/SessionUtil/Database/Setting+Utilities.swift diff --git a/LibSession-Util b/LibSession-Util index d8f07fa92..e3ccf29db 160000 --- a/LibSession-Util +++ b/LibSession-Util @@ -1 +1 @@ -Subproject commit d8f07fa92c12c5c2409774e03e03395d7847d1c2 +Subproject commit e3ccf29db08aaf0b9bb6bbe72ae5967cd183a78d diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index cac7103f7..e0f3f7efe 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -531,6 +531,8 @@ FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */; }; FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FD2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift */; }; FD1C98E4282E3C5B00B76F9E /* UINavigationBar+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */; }; + FD1D732A2A85AA2000E3F410 /* Setting+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D73292A85AA2000E3F410 /* Setting+Utilities.swift */; }; + FD1D732E2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */; }; FD23CE1B2A651E6D0000B97C /* NetworkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1A2A651E6D0000B97C /* NetworkType.swift */; }; FD23CE1F2A65269C0000B97C /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1E2A65269C0000B97C /* Crypto.swift */; }; FD23CE222A661D000000B97C /* OpenGroupAPI+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */; }; @@ -1689,6 +1691,8 @@ FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PersistableRecord+Utilities.swift"; sourceTree = ""; }; FD1A94FD2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistableRecordUtilitiesSpec.swift; sourceTree = ""; }; FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Utilities.swift"; sourceTree = ""; }; + FD1D73292A85AA2000E3F410 /* Setting+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Setting+Utilities.swift"; sourceTree = ""; }; + FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _015_BlockCommunityMessageRequests.swift; sourceTree = ""; }; FD23CE1A2A651E6D0000B97C /* NetworkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkType.swift; sourceTree = ""; }; FD23CE1E2A65269C0000B97C /* Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = ""; }; FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenGroupAPI+Crypto.swift"; sourceTree = ""; }; @@ -3618,6 +3622,7 @@ FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */, FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */, FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */, + FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */, ); path = Migrations; sourceTree = ""; @@ -3763,6 +3768,7 @@ FD2B4B022949886900AB4848 /* Database */ = { isa = PBXGroup; children = ( + FD1D73292A85AA2000E3F410 /* Setting+Utilities.swift */, FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */, ); path = Database; @@ -5914,12 +5920,14 @@ C352A35B2557824E00338F3E /* AttachmentUploadJob.swift in Sources */, FD2959922A4417A900888A17 /* PreparedSendData.swift in Sources */, FD5C7305284F0FF30029977D /* MessageReceiver+VisibleMessages.swift in Sources */, + FD1D732E2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift in Sources */, FD432437299DEA38008A0213 /* TypeConversion+Utilities.swift in Sources */, FD2B4B042949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift in Sources */, FD09797027FA6FF300936362 /* Profile.swift in Sources */, FD245C56285065EA00B966DD /* SNProto.swift in Sources */, FD09798B27FD1CFE00936362 /* Capability.swift in Sources */, C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */, + FD1D732A2A85AA2000E3F410 /* Setting+Utilities.swift in Sources */, FD17D79C27F40B2E00122BE0 /* SMKLegacy.swift in Sources */, FD09798127FCFEE800936362 /* SessionThread.swift in Sources */, FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */, diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index fa5656fa1..523cdd884 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -208,17 +208,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers }() private lazy var emptyStateLabel: UILabel = { - let text: String = String( - format: { - switch (viewModel.threadData.threadIsNoteToSelf, viewModel.threadData.canWrite) { - case (true, _): return "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF".localized() - case (_, false): return "CONVERSATION_EMPTY_STATE_READ_ONLY".localized() - default: return "CONVERSATION_EMPTY_STATE".localized() - } - }(), - viewModel.threadData.displayName - ) - + let text: String = emptyStateText(for: viewModel.threadData) let result: UILabel = UILabel() result.accessibilityLabel = "Empty state label" result.translatesAutoresizingMaskIntoConstraints = false @@ -698,6 +688,24 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers self.viewModel.onInteractionChange = nil } + private func emptyStateText(for threadData: SessionThreadViewModel) -> String { + return String( + format: { + switch (threadData.threadIsNoteToSelf, threadData.canWrite) { + case (true, _): return "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF".localized() + case (_, false): + return (threadData.profile?.blocksCommunityMessageRequests == true ? + "COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE".localized() : + "CONVERSATION_EMPTY_STATE_READ_ONLY".localized() + ) + + default: return "CONVERSATION_EMPTY_STATE".localized() + } + }(), + threadData.displayName + ) + } + private func handleThreadUpdates(_ updatedThreadData: SessionThreadViewModel, initialLoad: Bool = false) { // Ensure the first load or a load when returning from a child screen runs without animations (if // we don't do this the cells will animate in from a frame of CGRect.zero or have a buggy transition) @@ -738,17 +746,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers ) // Update the empty state - let text: String = String( - format: { - switch (updatedThreadData.threadIsNoteToSelf, updatedThreadData.canWrite) { - case (true, _): return "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF".localized() - case (_, false): return "CONVERSATION_EMPTY_STATE_READ_ONLY".localized() - default: return "CONVERSATION_EMPTY_STATE".localized() - } - }(), - updatedThreadData.displayName - ) - + let text: String = emptyStateText(for: updatedThreadData) emptyStateLabel.attributedText = NSAttributedString(string: text) .adding( attributes: [.font: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize)], @@ -791,8 +789,10 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers updatedThreadData.threadRequiresApproval == true ) self?.messageRequestStackView.isHidden = ( - updatedThreadData.threadIsMessageRequest == false && - updatedThreadData.threadRequiresApproval == false + !updatedThreadData.canWrite || ( + updatedThreadData.threadIsMessageRequest == false && + updatedThreadData.threadRequiresApproval == false + ) ) self?.messageRequestBackgroundView.isHidden = (self?.messageRequestStackView.isHidden == true) self?.messageRequestDescriptionLabelBottomConstraint?.constant = (updatedThreadData.threadRequiresApproval == true ? -4 : -20) diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 40792b3fc..07251bcc5 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -178,6 +178,7 @@ class ThreadSettingsViewModel: SessionTableViewModel [SectionModel] in + .trackingConstantRegion { [weak self] db -> State in + State( + trimOpenGroupMessagesOlderThanSixMonths: db[.trimOpenGroupMessagesOlderThanSixMonths], + shouldAutoPlayConsecutiveAudioMessages: db[.shouldAutoPlayConsecutiveAudioMessages] + ) + } + .removeDuplicates() + .handleEvents(didFail: { SNLog("[ConversationSettingsViewModel] Observation failed with error: \($0)") }) + .publisher(in: Storage.shared) + .withPrevious() + .map { (previous: State?, current: State) -> [SectionModel] in return [ SectionModel( model: .messageTrimming, @@ -55,7 +70,11 @@ class ConversationSettingsViewModel: SessionTableViewModel { +class NotificationSettingsViewModel: SessionTableViewModel { // MARK: - Config public enum Section: SessionTableSection { @@ -31,7 +31,7 @@ class NotificationSettingsViewModel: SessionTableViewModel [SectionModel] in - let notificationSound: Preferences.Sound = db[.defaultNotificationSound] - .defaulting(to: Preferences.Sound.defaultNotificationSound) - let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType] - .defaulting(to: Preferences.NotificationPreviewType.defaultPreviewType) - + .trackingConstantRegion { db -> State in + State( + isUsingFullAPNs: false, // Set later the the data flow + notificationSound: db[.defaultNotificationSound] + .defaulting(to: Preferences.Sound.defaultNotificationSound), + playNotificationSoundInForeground: db[.playNotificationSoundInForeground], + previewType: db[.preferencesNotificationPreviewType] + .defaulting(to: Preferences.NotificationPreviewType.defaultPreviewType) + ) + } + .removeDuplicates() + .handleEvents(didFail: { SNLog("[NotificationSettingsViewModel] Observation failed with error: \($0)") }) + .publisher(in: Storage.shared) + .manualRefreshFrom(forcedRefresh) + .map { dbState -> State in + State( + isUsingFullAPNs: UserDefaults.standard[.isUsingFullAPNs], + notificationSound: dbState.notificationSound, + playNotificationSoundInForeground: dbState.playNotificationSoundInForeground, + previewType: dbState.previewType + ) + } + .withPrevious() + .map { (previous: State?, current: State) -> [SectionModel] in return [ SectionModel( model: .strategy, @@ -68,20 +93,24 @@ class NotificationSettingsViewModel: SessionTableViewModel [SectionModel] in + .trackingConstantRegion { [weak self] db -> State in + State( + isScreenLockEnabled: db[.isScreenLockEnabled], + checkForCommunityMessageRequests: db[.checkForCommunityMessageRequests], + areReadReceiptsEnabled: db[.areReadReceiptsEnabled], + typingIndicatorsEnabled: db[.typingIndicatorsEnabled], + areLinkPreviewsEnabled: db[.areLinkPreviewsEnabled], + areCallsEnabled: db[.areCallsEnabled] + ) + } + .removeDuplicates() + .handleEvents(didFail: { SNLog("[PrivacySettingsViewModel] Observation failed with error: \($0)") }) + .publisher(in: Storage.shared) + .withPrevious() + .map { (previous: State?, current: State) -> [SectionModel] in return [ SectionModel( model: .screenSecurity, @@ -96,7 +122,13 @@ class PrivacySettingsViewModel: SessionTableViewModel 100 } // Prevent too many changes from causing performance issues ) { [weak self] updatedData in self?.viewModel.updateTableData(updatedData) @@ -339,6 +339,7 @@ class SessionTableViewController { Just(nil).eraseToAnyPublisher() } open var rightNavItems: AnyPublisher<[NavItem]?, Never> { Just(nil).eraseToAnyPublisher() } + private let _forcedRefresh: PassthroughSubject = PassthroughSubject() + lazy var forcedRefresh: AnyPublisher = _forcedRefresh + .shareReplay(0) private let _showToast: PassthroughSubject<(String, ThemeValue), Never> = PassthroughSubject() lazy var showToast: AnyPublisher<(String, ThemeValue), Never> = _showToast .shareReplay(0) @@ -62,6 +65,10 @@ class SessionTableViewModel( for viewModel: SessionTableViewModel ) -> AnyPublisher<(Output, StagedChangeset), Failure> where Output == [ArraySection>] { diff --git a/Session/Shared/Types/SessionCell+Accessory.swift b/Session/Shared/Types/SessionCell+Accessory.swift index af9d617eb..4bacade56 100644 --- a/Session/Shared/Types/SessionCell+Accessory.swift +++ b/Session/Shared/Types/SessionCell+Accessory.swift @@ -394,19 +394,30 @@ extension SessionCell.Accessory { extension SessionCell.Accessory { public enum DataSource: Hashable, Equatable { - case boolValue(Bool) + case boolValue(key: String, value: Bool, oldValue: Bool) case dynamicString(() -> String?) - case userDefaults(UserDefaults, key: String) - case settingBool(key: Setting.BoolKey) + + static func boolValue(_ value: Bool, oldValue: Bool) -> DataSource { + return .boolValue(key: "", value: value, oldValue: oldValue) + } + + static func boolValue(key: Setting.BoolKey, value: Bool, oldValue: Bool) -> DataSource { + return .boolValue(key: key.rawValue, value: value, oldValue: oldValue) + } // MARK: - Convenience public var currentBoolValue: Bool { switch self { - case .boolValue(let value): return value + case .boolValue(_, let value, _): return value case .dynamicString: return false - case .userDefaults(let defaults, let key): return defaults.bool(forKey: key) - case .settingBool(let key): return Storage.shared[key] + } + } + + public var oldBoolValue: Bool { + switch self { + case .boolValue(_, _, let oldValue): return oldValue + default: return false } } @@ -421,27 +432,27 @@ extension SessionCell.Accessory { public func hash(into hasher: inout Hasher) { switch self { - case .boolValue(let value): value.hash(into: &hasher) + case .boolValue(let key, let value, let oldValue): + key.hash(into: &hasher) + value.hash(into: &hasher) + oldValue.hash(into: &hasher) + case .dynamicString(let generator): generator().hash(into: &hasher) - case .userDefaults(_, let key): key.hash(into: &hasher) - case .settingBool(let key): key.hash(into: &hasher) } } public static func == (lhs: DataSource, rhs: DataSource) -> Bool { switch (lhs, rhs) { - case (.boolValue(let lhsValue), .boolValue(let rhsValue)): - return (lhsValue == rhsValue) + case (.boolValue(let lhsKey, let lhsValue, let lhsOldValue), .boolValue(let rhsKey, let rhsValue, let rhsOldValue)): + return ( + lhsKey == rhsKey && + lhsValue == rhsValue && + lhsOldValue == rhsOldValue + ) case (.dynamicString(let lhsGenerator), .dynamicString(let rhsGenerator)): return (lhsGenerator() == rhsGenerator()) - case (.userDefaults(_, let lhsKey), .userDefaults(_, let rhsKey)): - return (lhsKey == rhsKey) - - case (.settingBool(let lhsKey), .settingBool(let rhsKey)): - return (lhsKey == rhsKey) - default: return false } } diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index 44c81b9eb..39ca4344a 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -277,7 +277,8 @@ extension SessionCell { public func update( with accessory: Accessory?, tintColor: ThemeValue, - isEnabled: Bool + isEnabled: Bool, + isManualReload: Bool ) { guard let accessory: Accessory = accessory else { return } @@ -356,10 +357,15 @@ extension SessionCell { fixedWidthConstraint.isActive = true toggleSwitchConstraints.forEach { $0.isActive = true } - let newValue: Bool = dataSource.currentBoolValue - - if newValue != toggleSwitch.isOn { - toggleSwitch.setOn(newValue, animated: true) + if !isManualReload { + toggleSwitch.setOn(dataSource.oldBoolValue, animated: false) + + // Dispatch so the cell reload doesn't conflict with the setting change animation + if dataSource.oldBoolValue != dataSource.currentBoolValue { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(10)) { [weak toggleSwitch] in + toggleSwitch?.setOn(dataSource.currentBoolValue, animated: true) + } + } } case .dropDown(let dataSource, let accessibility): diff --git a/Session/Shared/Views/SessionCell.swift b/Session/Shared/Views/SessionCell.swift index 912fb37a9..1b2b8630e 100644 --- a/Session/Shared/Views/SessionCell.swift +++ b/Session/Shared/Views/SessionCell.swift @@ -313,7 +313,7 @@ public class SessionCell: UITableViewCell { botSeparator.isHidden = true } - public func update(with info: Info) { + public func update(with info: Info, isManualReload: Bool = false) { interactionMode = (info.title?.interaction ?? .none) shouldHighlightTitle = (info.title?.interaction != .copy) titleExtraView = info.title?.extraViewGenerator?() @@ -332,7 +332,8 @@ public class SessionCell: UITableViewCell { leftAccessoryView.update( with: info.leftAccessory, tintColor: info.styling.tintColor, - isEnabled: info.isEnabled + isEnabled: info.isEnabled, + isManualReload: isManualReload ) titleStackView.isHidden = (info.title == nil && info.subtitle == nil) titleLabel.isUserInteractionEnabled = (info.title?.interaction == .copy) @@ -356,7 +357,8 @@ public class SessionCell: UITableViewCell { rightAccessoryView.update( with: info.rightAccessory, tintColor: info.styling.tintColor, - isEnabled: info.isEnabled + isEnabled: info.isEnabled, + isManualReload: isManualReload ) contentStackViewLeadingConstraint.isActive = (info.styling.alignment == .leading) diff --git a/Session/Utilities/MockDataGenerator.swift b/Session/Utilities/MockDataGenerator.swift index 1cbf94fbe..d80ea168b 100644 --- a/Session/Utilities/MockDataGenerator.swift +++ b/Session/Utilities/MockDataGenerator.swift @@ -99,7 +99,8 @@ enum MockDataGenerator { .compactMap { _ in stringContent.randomElement(using: &dmThreadRandomGenerator) } .joined(), lastNameUpdate: Date().timeIntervalSince1970, - lastProfilePictureUpdate: Date().timeIntervalSince1970 + lastProfilePictureUpdate: Date().timeIntervalSince1970, + lastBlocksCommunityMessageRequests: 0 ) .saved(db) @@ -180,7 +181,8 @@ enum MockDataGenerator { .compactMap { _ in stringContent.randomElement(using: &cgThreadRandomGenerator) } .joined(), lastNameUpdate: Date().timeIntervalSince1970, - lastProfilePictureUpdate: Date().timeIntervalSince1970 + lastProfilePictureUpdate: Date().timeIntervalSince1970, + lastBlocksCommunityMessageRequests: 0 ) .saved(db) @@ -310,7 +312,8 @@ enum MockDataGenerator { .compactMap { _ in stringContent.randomElement(using: &ogThreadRandomGenerator) } .joined(), lastNameUpdate: Date().timeIntervalSince1970, - lastProfilePictureUpdate: Date().timeIntervalSince1970 + lastProfilePictureUpdate: Date().timeIntervalSince1970, + lastBlocksCommunityMessageRequests: 0 ) .saved(db) diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index 0aa4f5d37..8522a4276 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -37,7 +37,8 @@ public enum SNMessagingKit: MigratableTarget { // Just to make the external API (Features.useSharedUtilForUserConfig(db) ? _014_GenerateInitialUserConfigDumps.self : (nil as Migration.Type?) - ) + ), + _015_BlockCommunityMessageRequests.self ].compactMap { $0 } ] ) diff --git a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift index 8918c1c9b..9f0af5346 100644 --- a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -422,7 +422,8 @@ enum _003_YDBToGRDBMigration: Migration { profilePictureUrl: legacyContact.profilePictureURL, profilePictureFileName: legacyContact.profilePictureFileName, profileEncryptionKey: legacyContact.profileEncryptionKey?.keyData, - lastProfilePictureUpdate: 0 + lastProfilePictureUpdate: 0, + lastBlocksCommunityMessageRequests: 0 ).migrationSafeInsert(db) /// **Note:** The blow "shouldForce" flags are here to allow us to avoid having to run legacy migrations they @@ -645,7 +646,8 @@ enum _003_YDBToGRDBMigration: Migration { id: profileId, name: profileId, lastNameUpdate: 0, - lastProfilePictureUpdate: 0 + lastProfilePictureUpdate: 0, + lastBlocksCommunityMessageRequests: 0 ).migrationSafeSave(db) } @@ -1059,7 +1061,8 @@ enum _003_YDBToGRDBMigration: Migration { id: quotedMessage.authorId, name: quotedMessage.authorId, lastNameUpdate: 0, - lastProfilePictureUpdate: 0 + lastProfilePictureUpdate: 0, + lastBlocksCommunityMessageRequests: 0 ).migrationSafeSave(db) } diff --git a/SessionMessagingKit/Database/Migrations/_005_FixDeletedMessageReadState.swift b/SessionMessagingKit/Database/Migrations/_005_FixDeletedMessageReadState.swift index 65e68507c..235a217d3 100644 --- a/SessionMessagingKit/Database/Migrations/_005_FixDeletedMessageReadState.swift +++ b/SessionMessagingKit/Database/Migrations/_005_FixDeletedMessageReadState.swift @@ -9,7 +9,7 @@ enum _005_FixDeletedMessageReadState: Migration { static let target: TargetMigrations.Identifier = .messagingKit static let identifier: String = "FixDeletedMessageReadState" static let needsConfigSync: Bool = false - static let minExpectedRunDuration: TimeInterval = 0.1 + static let minExpectedRunDuration: TimeInterval = 0.01 static func migrate(_ db: Database) throws { _ = try Interaction diff --git a/SessionMessagingKit/Database/Migrations/_006_FixHiddenModAdminSupport.swift b/SessionMessagingKit/Database/Migrations/_006_FixHiddenModAdminSupport.swift index c1097eb94..b746c6362 100644 --- a/SessionMessagingKit/Database/Migrations/_006_FixHiddenModAdminSupport.swift +++ b/SessionMessagingKit/Database/Migrations/_006_FixHiddenModAdminSupport.swift @@ -10,7 +10,7 @@ enum _006_FixHiddenModAdminSupport: Migration { static let target: TargetMigrations.Identifier = .messagingKit static let identifier: String = "FixHiddenModAdminSupport" static let needsConfigSync: Bool = false - static let minExpectedRunDuration: TimeInterval = 0.1 + static let minExpectedRunDuration: TimeInterval = 0.01 static func migrate(_ db: Database) throws { try db.alter(table: GroupMember.self) { t in diff --git a/SessionMessagingKit/Database/Migrations/_007_HomeQueryOptimisationIndexes.swift b/SessionMessagingKit/Database/Migrations/_007_HomeQueryOptimisationIndexes.swift index b468098f7..5e53bb6ee 100644 --- a/SessionMessagingKit/Database/Migrations/_007_HomeQueryOptimisationIndexes.swift +++ b/SessionMessagingKit/Database/Migrations/_007_HomeQueryOptimisationIndexes.swift @@ -9,7 +9,7 @@ enum _007_HomeQueryOptimisationIndexes: Migration { static let target: TargetMigrations.Identifier = .messagingKit static let identifier: String = "HomeQueryOptimisationIndexes" static let needsConfigSync: Bool = false - static let minExpectedRunDuration: TimeInterval = 0.1 + static let minExpectedRunDuration: TimeInterval = 0.01 static func migrate(_ db: Database) throws { try db.create( diff --git a/SessionMessagingKit/Database/Migrations/_008_EmojiReacts.swift b/SessionMessagingKit/Database/Migrations/_008_EmojiReacts.swift index b06687dca..399dba483 100644 --- a/SessionMessagingKit/Database/Migrations/_008_EmojiReacts.swift +++ b/SessionMessagingKit/Database/Migrations/_008_EmojiReacts.swift @@ -9,7 +9,7 @@ enum _008_EmojiReacts: Migration { static let target: TargetMigrations.Identifier = .messagingKit static let identifier: String = "EmojiReacts" static let needsConfigSync: Bool = false - static let minExpectedRunDuration: TimeInterval = 0.1 + static let minExpectedRunDuration: TimeInterval = 0.01 static func migrate(_ db: Database) throws { try db.create(table: Reaction.self) { t in diff --git a/SessionMessagingKit/Database/Migrations/_009_OpenGroupPermission.swift b/SessionMessagingKit/Database/Migrations/_009_OpenGroupPermission.swift index 4f6036a2d..f4c7e8617 100644 --- a/SessionMessagingKit/Database/Migrations/_009_OpenGroupPermission.swift +++ b/SessionMessagingKit/Database/Migrations/_009_OpenGroupPermission.swift @@ -8,7 +8,7 @@ enum _009_OpenGroupPermission: Migration { static let target: TargetMigrations.Identifier = .messagingKit static let identifier: String = "OpenGroupPermission" static let needsConfigSync: Bool = false - static let minExpectedRunDuration: TimeInterval = 0.1 + static let minExpectedRunDuration: TimeInterval = 0.01 static func migrate(_ db: GRDB.Database) throws { try db.alter(table: OpenGroup.self) { t in diff --git a/SessionMessagingKit/Database/Migrations/_011_AddPendingReadReceipts.swift b/SessionMessagingKit/Database/Migrations/_011_AddPendingReadReceipts.swift index 9c2e228d5..2fb57b2cf 100644 --- a/SessionMessagingKit/Database/Migrations/_011_AddPendingReadReceipts.swift +++ b/SessionMessagingKit/Database/Migrations/_011_AddPendingReadReceipts.swift @@ -10,7 +10,7 @@ enum _011_AddPendingReadReceipts: Migration { static let target: TargetMigrations.Identifier = .messagingKit static let identifier: String = "AddPendingReadReceipts" static let needsConfigSync: Bool = false - static let minExpectedRunDuration: TimeInterval = 0.1 + static let minExpectedRunDuration: TimeInterval = 0.01 static func migrate(_ db: Database) throws { try db.create(table: PendingReadReceipt.self) { t in diff --git a/SessionMessagingKit/Database/Migrations/_012_AddFTSIfNeeded.swift b/SessionMessagingKit/Database/Migrations/_012_AddFTSIfNeeded.swift index d994b6a90..57cb66e7d 100644 --- a/SessionMessagingKit/Database/Migrations/_012_AddFTSIfNeeded.swift +++ b/SessionMessagingKit/Database/Migrations/_012_AddFTSIfNeeded.swift @@ -9,7 +9,7 @@ enum _012_AddFTSIfNeeded: Migration { static let target: TargetMigrations.Identifier = .messagingKit static let identifier: String = "AddFTSIfNeeded" static let needsConfigSync: Bool = false - static let minExpectedRunDuration: TimeInterval = 0.1 + static let minExpectedRunDuration: TimeInterval = 0.01 static func migrate(_ db: Database) throws { // Fix an issue that the fullTextSearchTable was dropped unintentionally and global search won't work. diff --git a/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift b/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift new file mode 100644 index 000000000..f887a6ce3 --- /dev/null +++ b/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift @@ -0,0 +1,33 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +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" + static let needsConfigSync: Bool = false + static let minExpectedRunDuration: TimeInterval = 0.01 + + static func migrate(_ db: Database) throws { + // Add the new 'Profile' properties + try db.alter(table: Profile.self) { t in + t.add(.blocksCommunityMessageRequests, .boolean) + t.add(.lastBlocksCommunityMessageRequests, .integer) + .notNull() + .defaults(to: 0) + } + + // If the user exists and the 'checkForCommunityMessageRequests' hasn't already been set then default it to "false" + if + Identity.userExists(db), + (try Setting.exists(db, id: Setting.BoolKey.checkForCommunityMessageRequests.rawValue)) == false + { + db[.checkForCommunityMessageRequests] = true + } + + Storage.update(progress: 1, for: self, in: target) // In case this is the last migration + } +} diff --git a/SessionMessagingKit/Database/Models/Profile.swift b/SessionMessagingKit/Database/Models/Profile.swift index cc4e4bef6..a9a6bd8af 100644 --- a/SessionMessagingKit/Database/Models/Profile.swift +++ b/SessionMessagingKit/Database/Models/Profile.swift @@ -27,6 +27,9 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco case profilePictureFileName case profileEncryptionKey case lastProfilePictureUpdate + + case blocksCommunityMessageRequests + case lastBlocksCommunityMessageRequests } /// The id for the user that owns the profile (Note: This could be a sessionId, a blindedId or some future variant) @@ -53,6 +56,12 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco /// The timestamp (in seconds since epoch) that the profile picture was last updated public let lastProfilePictureUpdate: TimeInterval + /// A flag indicating whether this profile has reported that it blocks community message requests + public let blocksCommunityMessageRequests: Bool? + + /// The timestamp (in seconds since epoch) that the `blocksCommunityMessageRequests` setting was last updated + public let lastBlocksCommunityMessageRequests: TimeInterval + // MARK: - Initialization public init( @@ -63,7 +72,9 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco profilePictureUrl: String? = nil, profilePictureFileName: String? = nil, profileEncryptionKey: Data? = nil, - lastProfilePictureUpdate: TimeInterval + lastProfilePictureUpdate: TimeInterval, + blocksCommunityMessageRequests: Bool? = nil, + lastBlocksCommunityMessageRequests: TimeInterval ) { self.id = id self.name = name @@ -73,6 +84,8 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco self.profilePictureFileName = profilePictureFileName self.profileEncryptionKey = profileEncryptionKey self.lastProfilePictureUpdate = lastProfilePictureUpdate + self.blocksCommunityMessageRequests = blocksCommunityMessageRequests + self.lastBlocksCommunityMessageRequests = lastBlocksCommunityMessageRequests } // MARK: - Description @@ -114,7 +127,9 @@ public extension Profile { profilePictureUrl: profilePictureUrl, profilePictureFileName: try? container.decode(String.self, forKey: .profilePictureFileName), profileEncryptionKey: profileKey, - lastProfilePictureUpdate: try container.decode(TimeInterval.self, forKey: .lastProfilePictureUpdate) + lastProfilePictureUpdate: try container.decode(TimeInterval.self, forKey: .lastProfilePictureUpdate), + blocksCommunityMessageRequests: try? container.decode(Bool.self, forKey: .blocksCommunityMessageRequests), + lastBlocksCommunityMessageRequests: try container.decode(TimeInterval.self, forKey: .lastBlocksCommunityMessageRequests) ) } @@ -129,6 +144,8 @@ public extension Profile { try container.encodeIfPresent(profilePictureFileName, forKey: .profilePictureFileName) try container.encodeIfPresent(profileEncryptionKey, forKey: .profileEncryptionKey) try container.encode(lastProfilePictureUpdate, forKey: .lastProfilePictureUpdate) + try container.encodeIfPresent(blocksCommunityMessageRequests, forKey: .blocksCommunityMessageRequests) + try container.encode(lastBlocksCommunityMessageRequests, forKey: .lastBlocksCommunityMessageRequests) } } @@ -156,7 +173,9 @@ public extension Profile { profilePictureUrl: profilePictureUrl, profilePictureFileName: nil, profileEncryptionKey: profileKey, - lastProfilePictureUpdate: sentTimestamp + lastProfilePictureUpdate: sentTimestamp, + blocksCommunityMessageRequests: (proto.hasBlocksCommunityMessageRequests ? proto.blocksCommunityMessageRequests : nil), + lastBlocksCommunityMessageRequests: (proto.hasBlocksCommunityMessageRequests ? sentTimestamp : 0) ) } @@ -242,7 +261,9 @@ public extension Profile { profilePictureUrl: nil, profilePictureFileName: nil, profileEncryptionKey: nil, - lastProfilePictureUpdate: 0 + lastProfilePictureUpdate: 0, + blocksCommunityMessageRequests: nil, + lastBlocksCommunityMessageRequests: 0 ) } diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Profile.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Profile.swift index 8f63ed5a9..4ad3649ac 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Profile.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Profile.swift @@ -10,15 +10,22 @@ public extension VisibleMessage { public let displayName: String? public let profileKey: Data? public let profilePictureUrl: String? + public let blocksCommunityMessageRequests: Bool? // MARK: - Initialization - internal init(displayName: String, profileKey: Data? = nil, profilePictureUrl: String? = nil) { + internal init( + displayName: String, + profileKey: Data? = nil, + profilePictureUrl: String? = nil, + blocksCommunityMessageRequests: Bool? = nil + ) { let hasUrlAndKey: Bool = (profileKey != nil && profilePictureUrl != nil) self.displayName = displayName self.profileKey = (hasUrlAndKey ? profileKey : nil) self.profilePictureUrl = (hasUrlAndKey ? profilePictureUrl : nil) + self.blocksCommunityMessageRequests = blocksCommunityMessageRequests } // MARK: - Proto Conversion @@ -32,7 +39,8 @@ public extension VisibleMessage { return VMProfile( displayName: displayName, profileKey: proto.profileKey, - profilePictureUrl: profileProto.profilePicture + profilePictureUrl: profileProto.profilePicture, + blocksCommunityMessageRequests: (proto.hasBlocksCommunityMessageRequests ? proto.blocksCommunityMessageRequests : nil) ) } @@ -45,6 +53,10 @@ public extension VisibleMessage { let profileProto = SNProtoLokiProfile.builder() profileProto.setDisplayName(displayName) + if let blocksCommunityMessageRequests: Bool = self.blocksCommunityMessageRequests { + dataMessageProto.setBlocksCommunityMessageRequests(blocksCommunityMessageRequests) + } + if let profileKey = profileKey, let profilePictureUrl = profilePictureUrl { dataMessageProto.setProfileKey(profileKey) profileProto.setProfilePicture(profilePictureUrl) @@ -112,10 +124,14 @@ public extension VisibleMessage { // MARK: - Conversion extension VisibleMessage.VMProfile { - init(profile: Profile) { + init( + profile: Profile, + blocksCommunityMessageRequests: Bool? + ) { self.displayName = profile.name self.profileKey = profile.profileEncryptionKey self.profilePictureUrl = profile.profilePictureUrl + self.blocksCommunityMessageRequests = blocksCommunityMessageRequests } } diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 4cd74f821..5c5d622c0 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -109,10 +109,12 @@ public enum OpenGroupAPI { // The 'inbox' and 'outbox' only work with blinded keys so don't bother polling them if not blinded !capabilities.contains(.blind) ? [] : [ - // Inbox - (lastInboxMessageId == 0 ? - try preparedInbox(db, on: server, using: dependencies) : - try preparedInboxSince(db, id: lastInboxMessageId, on: server, using: dependencies) + // Inbox (only check the inbox if the user want's community message requests) + (!db[.checkForCommunityMessageRequests] ? nil : + (lastInboxMessageId == 0 ? + try preparedInbox(db, on: server, using: dependencies) : + try preparedInboxSince(db, id: lastInboxMessageId, on: server, using: dependencies) + ) ), // Outbox @@ -120,7 +122,7 @@ public enum OpenGroupAPI { try preparedOutbox(db, on: server, using: dependencies) : try preparedOutboxSince(db, id: lastOutboxMessageId, on: server, using: dependencies) ), - ] + ].compactMap { $0 } ) ) diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index ed766ca8d..72e4ca517 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -2497,6 +2497,9 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr if let _value = syncTarget { builder.setSyncTarget(_value) } + if hasBlocksCommunityMessageRequests { + builder.setBlocksCommunityMessageRequests(blocksCommunityMessageRequests) + } return builder } @@ -2570,6 +2573,10 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr proto.syncTarget = valueParam } + @objc public func setBlocksCommunityMessageRequests(_ valueParam: Bool) { + proto.blocksCommunityMessageRequests = valueParam + } + @objc public func build() throws -> SNProtoDataMessage { return try SNProtoDataMessage.parseProto(proto) } @@ -2646,6 +2653,13 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr return proto.hasSyncTarget } + @objc public var blocksCommunityMessageRequests: Bool { + return proto.blocksCommunityMessageRequests + } + @objc public var hasBlocksCommunityMessageRequests: Bool { + return proto.hasBlocksCommunityMessageRequests + } + private init(proto: SessionProtos_DataMessage, attachments: [SNProtoAttachmentPointer], quote: SNProtoDataMessageQuote?, diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index 6f209cb67..3fb72c9ad 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -600,7 +600,7 @@ struct SessionProtos_DataMessage { set {_uniqueStorage()._attachments = newValue} } - /// optional GroupContext group = 3; // No longer used + /// optional GroupContext group = 3; // No longer used var flags: UInt32 { get {return _storage._flags ?? 0} set {_uniqueStorage()._flags = newValue} @@ -696,6 +696,15 @@ struct SessionProtos_DataMessage { /// Clears the value of `syncTarget`. Subsequent reads from it will return its default value. mutating func clearSyncTarget() {_uniqueStorage()._syncTarget = nil} + var blocksCommunityMessageRequests: Bool { + get {return _storage._blocksCommunityMessageRequests ?? false} + set {_uniqueStorage()._blocksCommunityMessageRequests = newValue} + } + /// Returns true if `blocksCommunityMessageRequests` has been explicitly set. + var hasBlocksCommunityMessageRequests: Bool {return _storage._blocksCommunityMessageRequests != nil} + /// Clears the value of `blocksCommunityMessageRequests`. Subsequent reads from it will return its default value. + mutating func clearBlocksCommunityMessageRequests() {_uniqueStorage()._blocksCommunityMessageRequests = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() enum Flags: SwiftProtobuf.Enum { @@ -1665,6 +1674,43 @@ extension SessionProtos_SharedConfigMessage.Kind: CaseIterable { #endif // swift(>=4.2) +#if swift(>=5.5) && canImport(_Concurrency) +extension SessionProtos_Envelope: @unchecked Sendable {} +extension SessionProtos_Envelope.TypeEnum: @unchecked Sendable {} +extension SessionProtos_TypingMessage: @unchecked Sendable {} +extension SessionProtos_TypingMessage.Action: @unchecked Sendable {} +extension SessionProtos_UnsendRequest: @unchecked Sendable {} +extension SessionProtos_MessageRequestResponse: @unchecked Sendable {} +extension SessionProtos_Content: @unchecked Sendable {} +extension SessionProtos_CallMessage: @unchecked Sendable {} +extension SessionProtos_CallMessage.TypeEnum: @unchecked Sendable {} +extension SessionProtos_KeyPair: @unchecked Sendable {} +extension SessionProtos_DataExtractionNotification: @unchecked Sendable {} +extension SessionProtos_DataExtractionNotification.TypeEnum: @unchecked Sendable {} +extension SessionProtos_LokiProfile: @unchecked Sendable {} +extension SessionProtos_DataMessage: @unchecked Sendable {} +extension SessionProtos_DataMessage.Flags: @unchecked Sendable {} +extension SessionProtos_DataMessage.Quote: @unchecked Sendable {} +extension SessionProtos_DataMessage.Quote.QuotedAttachment: @unchecked Sendable {} +extension SessionProtos_DataMessage.Quote.QuotedAttachment.Flags: @unchecked Sendable {} +extension SessionProtos_DataMessage.Preview: @unchecked Sendable {} +extension SessionProtos_DataMessage.Reaction: @unchecked Sendable {} +extension SessionProtos_DataMessage.Reaction.Action: @unchecked Sendable {} +extension SessionProtos_DataMessage.OpenGroupInvitation: @unchecked Sendable {} +extension SessionProtos_DataMessage.ClosedGroupControlMessage: @unchecked Sendable {} +extension SessionProtos_DataMessage.ClosedGroupControlMessage.TypeEnum: @unchecked Sendable {} +extension SessionProtos_DataMessage.ClosedGroupControlMessage.KeyPairWrapper: @unchecked Sendable {} +extension SessionProtos_ConfigurationMessage: @unchecked Sendable {} +extension SessionProtos_ConfigurationMessage.ClosedGroup: @unchecked Sendable {} +extension SessionProtos_ConfigurationMessage.Contact: @unchecked Sendable {} +extension SessionProtos_ReceiptMessage: @unchecked Sendable {} +extension SessionProtos_ReceiptMessage.TypeEnum: @unchecked Sendable {} +extension SessionProtos_AttachmentPointer: @unchecked Sendable {} +extension SessionProtos_AttachmentPointer.Flags: @unchecked Sendable {} +extension SessionProtos_SharedConfigMessage: @unchecked Sendable {} +extension SessionProtos_SharedConfigMessage.Kind: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "SessionProtos" @@ -2288,6 +2334,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa 102: .same(proto: "openGroupInvitation"), 104: .same(proto: "closedGroupControlMessage"), 105: .same(proto: "syncTarget"), + 106: .same(proto: "blocksCommunityMessageRequests"), ] fileprivate class _StorageClass { @@ -2304,6 +2351,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa var _openGroupInvitation: SessionProtos_DataMessage.OpenGroupInvitation? = nil var _closedGroupControlMessage: SessionProtos_DataMessage.ClosedGroupControlMessage? = nil var _syncTarget: String? = nil + var _blocksCommunityMessageRequests: Bool? = nil static let defaultInstance = _StorageClass() @@ -2323,6 +2371,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa _openGroupInvitation = source._openGroupInvitation _closedGroupControlMessage = source._closedGroupControlMessage _syncTarget = source._syncTarget + _blocksCommunityMessageRequests = source._blocksCommunityMessageRequests } } @@ -2366,6 +2415,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa case 102: try { try decoder.decodeSingularMessageField(value: &_storage._openGroupInvitation) }() case 104: try { try decoder.decodeSingularMessageField(value: &_storage._closedGroupControlMessage) }() case 105: try { try decoder.decodeSingularStringField(value: &_storage._syncTarget) }() + case 106: try { try decoder.decodeSingularBoolField(value: &_storage._blocksCommunityMessageRequests) }() default: break } } @@ -2417,6 +2467,9 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa try { if let v = _storage._syncTarget { try visitor.visitSingularStringField(value: v, fieldNumber: 105) } }() + try { if let v = _storage._blocksCommunityMessageRequests { + try visitor.visitSingularBoolField(value: v, fieldNumber: 106) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -2439,6 +2492,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa if _storage._openGroupInvitation != rhs_storage._openGroupInvitation {return false} if _storage._closedGroupControlMessage != rhs_storage._closedGroupControlMessage {return false} if _storage._syncTarget != rhs_storage._syncTarget {return false} + if _storage._blocksCommunityMessageRequests != rhs_storage._blocksCommunityMessageRequests {return false} return true } if !storagesAreEqual {return false} diff --git a/SessionMessagingKit/Protos/Generated/WebSocketResources.pb.swift b/SessionMessagingKit/Protos/Generated/WebSocketResources.pb.swift index 737e40ce6..2fe165044 100644 --- a/SessionMessagingKit/Protos/Generated/WebSocketResources.pb.swift +++ b/SessionMessagingKit/Protos/Generated/WebSocketResources.pb.swift @@ -218,6 +218,13 @@ extension WebSocketProtos_WebSocketMessage.TypeEnum: CaseIterable { #endif // swift(>=4.2) +#if swift(>=5.5) && canImport(_Concurrency) +extension WebSocketProtos_WebSocketRequestMessage: @unchecked Sendable {} +extension WebSocketProtos_WebSocketResponseMessage: @unchecked Sendable {} +extension WebSocketProtos_WebSocketMessage: @unchecked Sendable {} +extension WebSocketProtos_WebSocketMessage.TypeEnum: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "WebSocketProtos" diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index 429c10b14..55d77b18f 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -192,20 +192,21 @@ message DataMessage { optional uint32 expirationTimer = 8; } - optional string body = 1; - repeated AttachmentPointer attachments = 2; - // optional GroupContext group = 3; // No longer used - optional uint32 flags = 4; - optional uint32 expireTimer = 5; - optional bytes profileKey = 6; - optional uint64 timestamp = 7; - optional Quote quote = 8; - repeated Preview preview = 10; - optional Reaction reaction = 11; - optional LokiProfile profile = 101; - optional OpenGroupInvitation openGroupInvitation = 102; - optional ClosedGroupControlMessage closedGroupControlMessage = 104; - optional string syncTarget = 105; + optional string body = 1; + repeated AttachmentPointer attachments = 2; + // optional GroupContext group = 3; // No longer used + optional uint32 flags = 4; + optional uint32 expireTimer = 5; + optional bytes profileKey = 6; + optional uint64 timestamp = 7; + optional Quote quote = 8; + repeated Preview preview = 10; + optional Reaction reaction = 11; + optional LokiProfile profile = 101; + optional OpenGroupInvitation openGroupInvitation = 102; + optional ClosedGroupControlMessage closedGroupControlMessage = 104; + optional string syncTarget = 105; + optional bool blocksCommunityMessageRequests = 106; } message ConfigurationMessage { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index a1a959306..4b2573c5e 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -31,6 +31,7 @@ extension MessageReceiver { db, publicKey: sender, name: profile.displayName, + blocksCommunityMessageRequests: profile.blocksCommunityMessageRequests, avatarUpdate: { guard let profilePictureUrl: String = profile.profilePictureUrl, diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 9b968bdab..747841f4e 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -436,7 +436,8 @@ public final class MessageSender { // Attach the user's profile message.profile = VisibleMessage.VMProfile( - profile: Profile.fetchOrCreateCurrentUser() + profile: Profile.fetchOrCreateCurrentUser(db), + blocksCommunityMessageRequests: !db[.checkForCommunityMessageRequests] ) if (message.profile?.displayName ?? "").isEmpty { diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift index 019b19829..b6a8b86f0 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -573,7 +573,8 @@ private extension SessionUtil { count: ProfileManager.avatarAES256KeyByteLength ) ), - lastProfilePictureUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000) + lastProfilePictureUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000), + lastBlocksCommunityMessageRequests: 0 ) result[contactId] = ContactData( diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift index 909ea9ce7..c05bc17c3 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift @@ -213,6 +213,30 @@ internal extension SessionUtil { return updated } + static func updatingSetting(_ db: Database, _ updated: Setting?) throws { + // Don't current support any nullable settings + guard let updatedSetting: Setting = updated else { return } + + let userPublicKey: String = getUserHexEncodedPublicKey(db) + + // Currently the only synced setting is 'checkForCommunityMessageRequests' + switch updatedSetting.id { + case Setting.BoolKey.checkForCommunityMessageRequests.rawValue: + try SessionUtil.performAndPushChange( + db, + for: .userProfile, + publicKey: userPublicKey + ) { conf in + try SessionUtil.updateSettings( + checkForCommunityMessageRequests: updatedSetting.unsafeValue(as: Bool.self), + in: conf + ) + } + + default: break + } + } + static func kickFromConversationUIIfNeeded(removedThreadIds: [String]) { guard !removedThreadIds.isEmpty else { return } diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift index ed522930e..44ed7b2b2 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -12,6 +12,10 @@ internal extension SessionUtil { Profile.Columns.profileEncryptionKey ] + static let syncedSettings: [String] = [ + Setting.BoolKey.checkForCommunityMessageRequests.rawValue + ] + // MARK: - Incoming Changes static func handleUserProfileUpdate( @@ -115,6 +119,17 @@ internal extension SessionUtil { } } + // Update settings if needed + let updatedAllowBlindedMessageRequests: Int32 = user_profile_get_blinded_msgreqs(conf) + let updatedAllowBlindedMessageRequestsBoolValue: Bool = (updatedAllowBlindedMessageRequests >= 1) + + if + updatedAllowBlindedMessageRequests >= 0 && + updatedAllowBlindedMessageRequestsBoolValue != db[.checkForCommunityMessageRequests] + { + db[.checkForCommunityMessageRequests] = updatedAllowBlindedMessageRequestsBoolValue + } + // Create a contact for the current user if needed (also force-approve the current user // in case the account got into a weird state or restored directly from a migration) let userContact: Contact = Contact.fetchOrCreate(db, id: userPublicKey) @@ -159,4 +174,15 @@ internal extension SessionUtil { user_profile_set_nts_priority(conf, priority) } + + static func updateSettings( + checkForCommunityMessageRequests: Bool? = nil, + in conf: UnsafeMutablePointer? + ) throws { + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + if let blindedMessageRequests: Bool = checkForCommunityMessageRequests { + user_profile_set_blinded_msgreqs(conf, (blindedMessageRequests ? 1 : 0)) + } + } } diff --git a/SessionMessagingKit/SessionUtil/Database/Setting+Utilities.swift b/SessionMessagingKit/SessionUtil/Database/Setting+Utilities.swift new file mode 100644 index 000000000..2e178c5b1 --- /dev/null +++ b/SessionMessagingKit/SessionUtil/Database/Setting+Utilities.swift @@ -0,0 +1,58 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtilitiesKit + +public extension Database { + func setAndUpdateConfig(_ key: Setting.BoolKey, to newValue: Bool) throws { + try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue)) + } + + func setAndUpdateConfig(_ key: Setting.DoubleKey, to newValue: Double?) throws { + try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue)) + } + + func setAndUpdateConfig(_ key: Setting.IntKey, to newValue: Int?) throws { + try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue)) + } + + func setAndUpdateConfig(_ key: Setting.StringKey, to newValue: String?) throws { + try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue)) + } + + func setAndUpdateConfig(_ key: Setting.EnumKey, to newValue: T?) throws { + try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue)) + } + + func setAndUpdateConfig(_ key: Setting.EnumKey, to newValue: T?) throws { + try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue)) + } + + /// Value will be stored as a timestamp in seconds since 1970 + func setAndUpdateConfig(_ key: Setting.DateKey, to newValue: Date?) throws { + try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue)) + } + + private func updateConfigIfNeeded( + _ db: Database, + key: String, + updatedSetting: Setting? + ) throws { + // Before we do anything custom make sure the setting should trigger a change + guard SessionUtil.syncedSettings.contains(key) else { return } + + defer { + // If we changed a column that requires a config update then we may as well automatically + // enqueue a new config sync job once the transaction completes (but only enqueue it once + // per transaction - doing it more than once is pointless) + let userPublicKey: String = getUserHexEncodedPublicKey(db) + + db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in + ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey) + } + } + + try SessionUtil.updatingSetting(db, updatedSetting) + } +} diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 0ee0f5f5a..c9d0c43eb 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -104,7 +104,11 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public var canWrite: Bool { switch threadVariant { - case .contact: return true + case .contact: + guard threadIsMessageRequest == true else { return true } + + return (profile?.blocksCommunityMessageRequests != true) + case .legacyGroup, .group: return ( currentUserIsClosedGroupMember == true && diff --git a/SessionMessagingKit/Utilities/Preferences.swift b/SessionMessagingKit/Utilities/Preferences.swift index 34a00860e..4713e8ce1 100644 --- a/SessionMessagingKit/Utilities/Preferences.swift +++ b/SessionMessagingKit/Utilities/Preferences.swift @@ -65,6 +65,9 @@ public extension Setting.BoolKey { /// Controls whether concurrent audio messages should automatically be played after the one the user starts /// playing finishes static let shouldAutoPlayConsecutiveAudioMessages: Setting.BoolKey = "shouldAutoPlayConsecutiveAudioMessages" + + /// Controls whether the device will poll for community message requests (SOGS `/inbox` endpoint) + static let checkForCommunityMessageRequests: Setting.BoolKey = "checkForCommunityMessageRequests" } public extension Setting.StringKey { diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 7266b30f6..7597c683a 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -498,6 +498,7 @@ public struct ProfileManager { _ db: Database, publicKey: String, name: String?, + blocksCommunityMessageRequests: Bool? = nil, avatarUpdate: AvatarUpdate, sentTimestamp: TimeInterval, calledFromConfigHandling: Bool = false, @@ -516,6 +517,12 @@ public struct ProfileManager { } } + // Blocks community message requets flag + if let blocksCommunityMessageRequests: Bool = blocksCommunityMessageRequests, sentTimestamp > profile.lastBlocksCommunityMessageRequests { + profileChanges.append(Profile.Columns.blocksCommunityMessageRequests.set(to: blocksCommunityMessageRequests)) + profileChanges.append(Profile.Columns.lastBlocksCommunityMessageRequests.set(to: sentTimestamp)) + } + // Profile picture & profile key var avatarNeedsDownload: Bool = false var targetAvatarUrl: String? = nil diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift index ce0ba7a6e..d7f08b3a1 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift @@ -285,6 +285,17 @@ class ConfigUserProfileSpec { ) user_profile_set_pic(conf2, p2) + user_profile_set_nts_expiry(conf2, 86200) + expect(user_profile_get_nts_expiry(conf2)).to(equal(86400)) + + expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(-1)) + user_profile_set_blinded_msgreqs(conf2, 0) + expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(0)) + user_profile_set_blinded_msgreqs(conf2, -1) + expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(-1)) + user_profile_set_blinded_msgreqs(conf2, 1) + expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(1)) + // Both have changes, so push need a push expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_push(conf2)).to(beTrue()) @@ -364,6 +375,10 @@ class ConfigUserProfileSpec { .to(equal("7177657274007975696f31323334353637383930313233343536373839303132")) expect(user_profile_get_nts_priority(conf)).to(equal(9)) expect(user_profile_get_nts_priority(conf2)).to(equal(9)) + expect(user_profile_get_nts_expiry(conf)).to(equal(86400)) + expect(user_profile_get_nts_expiry(conf2)).to(equal(86400)) + expect(user_profile_get_blinded_msgreqs(conf)).to(equal(1)) + expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(1)) let fakeHash4: String = "fakehash4" var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() diff --git a/SessionShareExtension/ShareNavController.swift b/SessionShareExtension/ShareNavController.swift index 457d5e277..c2d6c1ad6 100644 --- a/SessionShareExtension/ShareNavController.swift +++ b/SessionShareExtension/ShareNavController.swift @@ -211,11 +211,11 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { } func shareViewWasCompleted() { - extensionContext!.completeRequest(returningItems: [], completionHandler: nil) + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) } func shareViewWasCancelled() { - extensionContext!.completeRequest(returningItems: [], completionHandler: nil) + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) } func shareViewFailed(error: Error) { diff --git a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift index a0985631e..c4a2dc0cb 100644 --- a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift +++ b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift @@ -65,6 +65,23 @@ public extension Publisher { return self.receive(on: scheduler, options: options) .eraseToAnyPublisher() } + + func manualRefreshFrom(_ refreshTrigger: some Publisher) -> AnyPublisher { + return Publishers + .CombineLatest(refreshTrigger.prepend(()).setFailureType(to: Failure.self), self) + .map { _, value in value } + .eraseToAnyPublisher() + } + + func withPrevious() -> AnyPublisher<(previous: Output?, current: Output), Failure> { + scan(Optional<(Output?, Output)>.none) { ($0?.1, $1) } + .compactMap { $0 } + .eraseToAnyPublisher() + } + + func withPrevious(_ initialPreviousValue: Output) -> AnyPublisher<(previous: Output, current: Output), Failure> { + scan((initialPreviousValue, initialPreviousValue)) { ($0.1, $1) }.eraseToAnyPublisher() + } } // MARK: - Convenience diff --git a/SessionUtilitiesKit/Database/Models/Setting.swift b/SessionUtilitiesKit/Database/Models/Setting.swift index 1f634dac5..163fd01b8 100644 --- a/SessionUtilitiesKit/Database/Models/Setting.swift +++ b/SessionUtilitiesKit/Database/Models/Setting.swift @@ -15,6 +15,7 @@ public struct Setting: Codable, Identifiable, FetchableRecord, PersistableRecord } public var id: String { key } + public var rawValue: Data { value } let key: String let value: Data @@ -53,7 +54,7 @@ extension Setting { self.value = Data(bytes: &targetValue, count: MemoryLayout.size(ofValue: targetValue)) } - fileprivate func value(as type: Bool.Type) -> Bool? { + public func unsafeValue(as type: Bool.Type) -> Bool? { // Note: The 'assumingMemoryBound' is essentially going to try to convert // the memory into the provided type so can result in invalid data being // returned if the type is incorrect. But it does seem safer than the 'load' @@ -189,7 +190,7 @@ public extension Database { subscript(key: Setting.BoolKey) -> Bool { get { // Default to false if it doesn't exist - (self[key.rawValue]?.value(as: Bool.self) ?? false) + (self[key.rawValue]?.unsafeValue(as: Bool.self) ?? false) } set { self[key.rawValue] = Setting(key: key.rawValue, value: newValue) } } @@ -245,4 +246,47 @@ public extension Database { ) } } + + func setting(key: Setting.BoolKey, to newValue: Bool) -> Setting? { + let result: Setting? = Setting(key: key.rawValue, value: newValue) + self[key.rawValue] = result + return result + } + + func setting(key: Setting.DoubleKey, to newValue: Double?) -> Setting? { + let result: Setting? = Setting(key: key.rawValue, value: newValue) + self[key.rawValue] = result + return result + } + + func setting(key: Setting.IntKey, to newValue: Int?) -> Setting? { + let result: Setting? = Setting(key: key.rawValue, value: newValue) + self[key.rawValue] = result + return result + } + + func setting(key: Setting.StringKey, to newValue: String?) -> Setting? { + let result: Setting? = Setting(key: key.rawValue, value: newValue) + self[key.rawValue] = result + return result + } + + func setting(key: Setting.EnumKey, to newValue: T?) -> Setting? { + let result: Setting? = Setting(key: key.rawValue, value: newValue?.rawValue) + self[key.rawValue] = result + return result + } + + func setting(key: Setting.EnumKey, to newValue: T?) -> Setting? { + let result: Setting? = Setting(key: key.rawValue, value: newValue?.rawValue) + self[key.rawValue] = result + return result + } + + /// Value will be stored as a timestamp in seconds since 1970 + func setting(key: Setting.DateKey, to newValue: Date?) -> Setting? { + let result: Setting? = Setting(key: key.rawValue, value: newValue.map { $0.timeIntervalSince1970 }) + self[key.rawValue] = result + return result + } } From 26c6df78abfee3163613bd4815768abf0b8d5002 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 11 Aug 2023 18:04:15 +1000 Subject: [PATCH 15/17] Fixed test compilation issues --- .../Settings/ThreadSettingsViewModelSpec.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index 22fe69cda..c271f6a3e 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -60,14 +60,16 @@ class ThreadSettingsViewModelSpec: QuickSpec { id: "05\(TestConstants.publicKey)", name: "TestMe", lastNameUpdate: 0, - lastProfilePictureUpdate: 0 + lastProfilePictureUpdate: 0, + lastBlocksCommunityMessageRequests: 0 ).insert(db) try Profile( id: "TestId", name: "TestUser", lastNameUpdate: 0, - lastProfilePictureUpdate: 0 + lastProfilePictureUpdate: 0, + lastBlocksCommunityMessageRequests: 0 ).insert(db) } viewModel = ThreadSettingsViewModel( From ef5aa927a0ccfb8ae02b77a2bd75d9269c74134c Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 11 Aug 2023 18:48:14 +1000 Subject: [PATCH 16/17] Added logic to use the setting if it's already been sent in a config Added the ability to define requirements for migrations (in case some data or state needs to be loaded for a migration to be able to be performed correctly) --- Session.xcodeproj/project.pbxproj | 4 +++ .../_015_BlockCommunityMessageRequests.swift | 13 +++++++- .../Config Handling/SessionUtil+Shared.swift | 16 ++++++++++ .../SessionUtil+UserProfile.swift | 10 ++++++ SessionUtilitiesKit/Database/Storage.swift | 31 +++++++++++++++++++ .../Database/Types/Migration.swift | 4 +++ .../Database/Types/MigrationRequirement.swift | 12 +++++++ SignalUtilitiesKit/Utilities/AppSetup.swift | 16 ++++++++++ 8 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 SessionUtilitiesKit/Database/Types/MigrationRequirement.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 17a813494..a83905cf0 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -517,6 +517,7 @@ FD1C98E4282E3C5B00B76F9E /* UINavigationBar+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */; }; FD1D732A2A85AA2000E3F410 /* Setting+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D73292A85AA2000E3F410 /* Setting+Utilities.swift */; }; FD1D732E2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */; }; + FD1F9C9F2A862BE60050F671 /* MigrationRequirement.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1F9C9E2A862BE60050F671 /* MigrationRequirement.swift */; }; FD23CE1B2A651E6D0000B97C /* NetworkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1A2A651E6D0000B97C /* NetworkType.swift */; }; FD23CE1F2A65269C0000B97C /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1E2A65269C0000B97C /* Crypto.swift */; }; FD23CE222A661D000000B97C /* OpenGroupAPI+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */; }; @@ -1675,6 +1676,7 @@ FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Utilities.swift"; sourceTree = ""; }; FD1D73292A85AA2000E3F410 /* Setting+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Setting+Utilities.swift"; sourceTree = ""; }; FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _015_BlockCommunityMessageRequests.swift; sourceTree = ""; }; + FD1F9C9E2A862BE60050F671 /* MigrationRequirement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationRequirement.swift; sourceTree = ""; }; FD23CE1A2A651E6D0000B97C /* NetworkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkType.swift; sourceTree = ""; }; FD23CE1E2A65269C0000B97C /* Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = ""; }; FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenGroupAPI+Crypto.swift"; sourceTree = ""; }; @@ -3671,6 +3673,7 @@ children = ( FD17D7BE27F51F8200122BE0 /* ColumnExpressible.swift */, FD17D7B727F51ECA00122BE0 /* Migration.swift */, + FD1F9C9E2A862BE60050F671 /* MigrationRequirement.swift */, FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */, FD17D7C027F5200100122BE0 /* TypedTableDefinition.swift */, FD37EA1028AB34B3003AE748 /* TypedTableAlteration.swift */, @@ -5678,6 +5681,7 @@ FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */, C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */, FD09797D27FBDB2000936362 /* Notification+Utilities.swift in Sources */, + FD1F9C9F2A862BE60050F671 /* MigrationRequirement.swift in Sources */, FDC6D7602862B3F600B04575 /* Dependencies.swift in Sources */, C3C2AC2E2553CBEB00C340D1 /* String+Trimming.swift in Sources */, FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */, diff --git a/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift b/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift index f887a6ce3..b512101b2 100644 --- a/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift +++ b/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift @@ -10,6 +10,7 @@ enum _015_BlockCommunityMessageRequests: Migration { static let identifier: String = "BlockCommunityMessageRequests" static let needsConfigSync: Bool = false static let minExpectedRunDuration: TimeInterval = 0.01 + static var requirements: [MigrationRequirement] = [.sessionUtilStateLoaded] static func migrate(_ db: Database) throws { // Add the new 'Profile' properties @@ -25,7 +26,17 @@ enum _015_BlockCommunityMessageRequests: Migration { Identity.userExists(db), (try Setting.exists(db, id: Setting.BoolKey.checkForCommunityMessageRequests.rawValue)) == false { - db[.checkForCommunityMessageRequests] = true + let rawBlindedMessageRequestValue: Int32 = try SessionUtil + .config(for: .userProfile, publicKey: getUserHexEncodedPublicKey(db)) + .wrappedValue + .map { conf -> Int32 in try SessionUtil.rawBlindedMessageRequestValue(in: conf) } + .defaulting(to: -1) + + // Use the value in the config if we happen to have one, otherwise use the default + db[.checkForCommunityMessageRequests] = (rawBlindedMessageRequestValue < 0 ? + true : + (rawBlindedMessageRequestValue > 0) + ) } Storage.update(progress: 1, for: self, in: target) // In case this is the last migration diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift index ce2500f1c..4a9c8d286 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift @@ -210,6 +210,22 @@ internal extension SessionUtil { return updated } + static func hasSetting(_ db: Database, forKey key: String) throws -> Bool { + let userPublicKey: String = getUserHexEncodedPublicKey(db) + + // Currently the only synced setting is 'checkForCommunityMessageRequests' + switch key { + case Setting.BoolKey.checkForCommunityMessageRequests.rawValue: + return try SessionUtil + .config(for: .userProfile, publicKey: userPublicKey) + .wrappedValue + .map { conf -> Bool in (try SessionUtil.rawBlindedMessageRequestValue(in: conf) >= 0) } + .defaulting(to: false) + + default: return false + } + } + static func updatingSetting(_ db: Database, _ updated: Setting?) throws { // Don't current support any nullable settings guard let updatedSetting: Setting = updated else { return } diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift index 44ed7b2b2..4416eee82 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -186,3 +186,13 @@ internal extension SessionUtil { } } } + +// MARK: - Direct Values + +extension SessionUtil { + static func rawBlindedMessageRequestValue(in conf: UnsafeMutablePointer?) throws -> Int32 { + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + return user_profile_get_blinded_msgreqs(conf) + } +} diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 464bf8eb5..2278e4670 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -47,8 +47,10 @@ open class Storage { fileprivate var dbWriter: DatabaseWriter? internal var testDbWriter: DatabaseWriter? { dbWriter } + private var unprocessedMigrationRequirements: Atomic<[MigrationRequirement]> = Atomic(MigrationRequirement.allCases) private var migrator: DatabaseMigrator? private var migrationProgressUpdater: Atomic<((String, CGFloat) -> ())>? + private var migrationRequirementProcesser: Atomic<(Database?, MigrationRequirement) -> ()>? // MARK: - Initialization @@ -77,6 +79,7 @@ open class Storage { migrationTargets: (customMigrationTargets ?? []), async: false, onProgressUpdate: nil, + onMigrationRequirement: { _, _ in }, onComplete: { _, _ in } ) return @@ -148,6 +151,7 @@ open class Storage { migrationTargets: [MigratableTarget.Type], async: Bool = true, onProgressUpdate: ((CGFloat, TimeInterval) -> ())?, + onMigrationRequirement: @escaping (Database?, MigrationRequirement) -> (), onComplete: @escaping (Swift.Result, Bool) -> () ) { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { @@ -232,13 +236,24 @@ open class Storage { onProgressUpdate?(totalProgress, totalMinExpectedDuration) } }) + self.migrationRequirementProcesser = Atomic(onMigrationRequirement) // Store the logic to run when the migration completes let migrationCompleted: (Swift.Result) -> () = { [weak self] result in + // Process any unprocessed requirements which need to be processed before completion + // then clear out the state + self?.unprocessedMigrationRequirements.wrappedValue + .filter { $0.shouldProcessAtCompletionIfNotRequired } + .forEach { self?.migrationRequirementProcesser?.wrappedValue(nil, $0) } self?.migrationsCompleted.mutate { $0 = true } self?.migrationProgressUpdater = nil + self?.migrationRequirementProcesser = nil SUKLegacy.clearLegacyDatabaseInstance() + // Reset in case there is a requirement on a migration which runs when returning from + // the background + self?.unprocessedMigrationRequirements.mutate { $0 = MigrationRequirement.allCases } + // Don't log anything in the case of a 'success' or if the database is suspended (the // latter will happen if the user happens to return to the background too quickly on // launch so is unnecessarily alarming, it also gets caught and logged separately by @@ -288,6 +303,22 @@ open class Storage { } } + public func willStartMigration(_ db: Database, _ migration: Migration.Type) { + let unprocessedRequirements: Set = migration.requirements.asSet() + .intersection(unprocessedMigrationRequirements.wrappedValue.asSet()) + + // No need to do anything if there are no unprocessed requirements + guard !unprocessedRequirements.isEmpty else { return } + + // Process all of the requirements for this migration + unprocessedRequirements.forEach { migrationRequirementProcesser?.wrappedValue(db, $0) } + + // Remove any processed requirements from the list (don't want to process them multiple times) + unprocessedMigrationRequirements.mutate { + $0 = Array($0.asSet().subtracting(migration.requirements.asSet())) + } + } + public static func update( progress: CGFloat, for migration: Migration.Type, diff --git a/SessionUtilitiesKit/Database/Types/Migration.swift b/SessionUtilitiesKit/Database/Types/Migration.swift index 6e4c909e5..aa8a815de 100644 --- a/SessionUtilitiesKit/Database/Types/Migration.swift +++ b/SessionUtilitiesKit/Database/Types/Migration.swift @@ -8,17 +8,21 @@ public protocol Migration { static var identifier: String { get } static var needsConfigSync: Bool { get } static var minExpectedRunDuration: TimeInterval { get } + static var requirements: [MigrationRequirement] { get } static func migrate(_ db: Database) throws } public extension Migration { + static var requirements: [MigrationRequirement] { [] } + static func loggedMigrate( _ storage: Storage?, targetIdentifier: TargetMigrations.Identifier ) -> ((_ db: Database) throws -> ()) { return { (db: Database) in SNLogNotTests("[Migration Info] Starting \(targetIdentifier.key(with: self))") + storage?.willStartMigration(db, self) storage?.internalCurrentlyRunningMigration.mutate { $0 = (targetIdentifier, self) } defer { storage?.internalCurrentlyRunningMigration.mutate { $0 = nil } } diff --git a/SessionUtilitiesKit/Database/Types/MigrationRequirement.swift b/SessionUtilitiesKit/Database/Types/MigrationRequirement.swift new file mode 100644 index 000000000..9e586b809 --- /dev/null +++ b/SessionUtilitiesKit/Database/Types/MigrationRequirement.swift @@ -0,0 +1,12 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +import Foundation + +public enum MigrationRequirement: CaseIterable { + case sessionUtilStateLoaded + + var shouldProcessAtCompletionIfNotRequired: Bool { + switch self { + case .sessionUtilStateLoaded: return true + } + } +} diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index 4ac031c56..75b708cba 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -82,6 +82,20 @@ public enum AppSetup { SNUIKit.self ], onProgressUpdate: migrationProgressChanged, + onMigrationRequirement: { db, requirement in + switch requirement { + case .sessionUtilStateLoaded: + guard Identity.userExists(db) else { return } + + // After the migrations have run but before the migration completion we load the + // SessionUtil state + SessionUtil.loadState( + db, + userPublicKey: getUserHexEncodedPublicKey(db), + ed25519SecretKey: Identity.fetchUserEd25519KeyPair(db)?.secretKey + ) + } + }, onComplete: { result, needsConfigSync in // After the migrations have run but before the migration completion we load the // SessionUtil state and update the 'needsConfigSync' flag based on whether the @@ -93,6 +107,8 @@ public enum AppSetup { ) } + // The 'needsConfigSync' flag should be based on whether either a migration or the + // configs need to be sync'ed migrationsCompletion(result, (needsConfigSync || SessionUtil.needsSync)) // The 'if' is only there to prevent the "variable never read" warning from showing From 382b466ded1a862d63edde44dcff76d8a1e4f6ba Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 11 Aug 2023 18:58:04 +1000 Subject: [PATCH 17/17] Fixed a bug where conversations without messages could display invalid dates --- Session/Utilities/Date+Utilities.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Session/Utilities/Date+Utilities.swift b/Session/Utilities/Date+Utilities.swift index ed7aab4f4..c6e440f3f 100644 --- a/Session/Utilities/Date+Utilities.swift +++ b/Session/Utilities/Date+Utilities.swift @@ -5,6 +5,9 @@ import SessionUtilitiesKit public extension Date { var formattedForDisplay: String { + // If we don't have a date then + guard self.timeIntervalSince1970 > 0 else { return "" } + let dateNow: Date = Date() guard Calendar.current.isDate(self, equalTo: dateNow, toGranularity: .year) else {