Further work on SessionMessagingKit migrations

Added migrations for contacts and started working through thread migration (have contact and closed group threads migrating)
Deprecated usage of ECKeyPair in the migrations (want to be able to remove Curve25519Kit in the future)
This commit is contained in:
Morgan Pretty 2022-04-06 15:43:26 +10:00
parent 0f4df804ed
commit cf66edb723
138 changed files with 4255 additions and 2397 deletions

View file

@ -230,7 +230,6 @@
B897621C25D201F7004F83B2 /* ScrollToBottomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */; }; B897621C25D201F7004F83B2 /* ScrollToBottomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */; };
B8AE75A425A6C6A6001A84D2 /* Data+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */; }; B8AE75A425A6C6A6001A84D2 /* Data+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */; };
B8AF4BB426A5204600583500 /* SendSeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8AF4BB326A5204600583500 /* SendSeedModal.swift */; }; B8AF4BB426A5204600583500 /* SendSeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8AF4BB326A5204600583500 /* SendSeedModal.swift */; };
B8B32033258B235D0020074B /* Storage+Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B32032258B235D0020074B /* Storage+Contacts.swift */; };
B8B3204E258C15C80020074B /* ContactsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B32044258C117C0020074B /* ContactsMigration.swift */; }; B8B3204E258C15C80020074B /* ContactsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B32044258C117C0020074B /* ContactsMigration.swift */; };
B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B320B6258C30D70020074B /* HTMLMetadata.swift */; }; B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B320B6258C30D70020074B /* HTMLMetadata.swift */; };
B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A4238F627000BA5194 /* HomeVC.swift */; }; B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A4238F627000BA5194 /* HomeVC.swift */; };
@ -739,6 +738,21 @@
FD09796B27F6C67500936362 /* Failable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796A27F6C67500936362 /* Failable.swift */; }; FD09796B27F6C67500936362 /* Failable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796A27F6C67500936362 /* Failable.swift */; };
FD09796E27FA6D0000936362 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796D27FA6D0000936362 /* Contact.swift */; }; FD09796E27FA6D0000936362 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796D27FA6D0000936362 /* Contact.swift */; };
FD09797027FA6FF300936362 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796F27FA6FF300936362 /* Profile.swift */; }; FD09797027FA6FF300936362 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796F27FA6FF300936362 /* Profile.swift */; };
FD09797227FAA2F500936362 /* Optional+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797127FAA2F500936362 /* Optional+Utilities.swift */; };
FD09797527FAB64300936362 /* ProfileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797327FAB3E200936362 /* ProfileManager.swift */; };
FD09797727FAB7A600936362 /* Data+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797627FAB7A600936362 /* Data+Image.swift */; };
FD09797927FAB7E800936362 /* ImageFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797827FAB7E800936362 /* ImageFormat.swift */; };
FD09797B27FBB25900936362 /* Updatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797A27FBB25900936362 /* Updatable.swift */; };
FD09797D27FBDB2000936362 /* Notification+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797C27FBDB2000936362 /* Notification+Utilities.swift */; };
FD09797F27FCFBFF00936362 /* OWSAES256Key+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797E27FCFBFF00936362 /* OWSAES256Key+Utilities.swift */; };
FD09798127FCFEE800936362 /* SessionThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09798027FCFEE800936362 /* SessionThread.swift */; };
FD09798327FD1A1500936362 /* ClosedGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09798227FD1A1500936362 /* ClosedGroup.swift */; };
FD09798527FD1A6500936362 /* ClosedGroupKeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09798427FD1A6500936362 /* ClosedGroupKeyPair.swift */; };
FD09798727FD1B7800936362 /* GroupMember.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09798627FD1B7800936362 /* GroupMember.swift */; };
FD09798927FD1C5A00936362 /* OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09798827FD1C5A00936362 /* OpenGroup.swift */; };
FD09798B27FD1CFE00936362 /* Capability.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09798A27FD1CFE00936362 /* Capability.swift */; };
FD09798D27FD1D8900936362 /* DisappearingMessageConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09798C27FD1D8900936362 /* DisappearingMessageConfiguration.swift */; };
FD09799127FD499200936362 /* BoxKeyPair+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09799027FD499200936362 /* BoxKeyPair+Utilities.swift */; };
FD17D79927F40AB800122BE0 /* _002_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79827F40AB800122BE0 /* _002_YDBToGRDBMigration.swift */; }; FD17D79927F40AB800122BE0 /* _002_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79827F40AB800122BE0 /* _002_YDBToGRDBMigration.swift */; };
FD17D79C27F40B2E00122BE0 /* SMKLegacyModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79B27F40B2E00122BE0 /* SMKLegacyModels.swift */; }; FD17D79C27F40B2E00122BE0 /* SMKLegacyModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79B27F40B2E00122BE0 /* SMKLegacyModels.swift */; };
FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */; }; FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */; };
@ -1227,7 +1241,6 @@
B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollToBottomButton.swift; sourceTree = "<group>"; }; B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollToBottomButton.swift; sourceTree = "<group>"; };
B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Trimming.swift"; sourceTree = "<group>"; }; B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Trimming.swift"; sourceTree = "<group>"; };
B8AF4BB326A5204600583500 /* SendSeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendSeedModal.swift; sourceTree = "<group>"; }; B8AF4BB326A5204600583500 /* SendSeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendSeedModal.swift; sourceTree = "<group>"; };
B8B32032258B235D0020074B /* Storage+Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Contacts.swift"; sourceTree = "<group>"; };
B8B32044258C117C0020074B /* ContactsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsMigration.swift; sourceTree = "<group>"; }; B8B32044258C117C0020074B /* ContactsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsMigration.swift; sourceTree = "<group>"; };
B8B320B6258C30D70020074B /* HTMLMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadata.swift; sourceTree = "<group>"; }; B8B320B6258C30D70020074B /* HTMLMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadata.swift; sourceTree = "<group>"; };
B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
@ -1772,6 +1785,21 @@
FD09796A27F6C67500936362 /* Failable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Failable.swift; sourceTree = "<group>"; }; FD09796A27F6C67500936362 /* Failable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Failable.swift; sourceTree = "<group>"; };
FD09796D27FA6D0000936362 /* Contact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = "<group>"; }; FD09796D27FA6D0000936362 /* Contact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = "<group>"; };
FD09796F27FA6FF300936362 /* Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = "<group>"; }; FD09796F27FA6FF300936362 /* Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = "<group>"; };
FD09797127FAA2F500936362 /* Optional+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Utilities.swift"; sourceTree = "<group>"; };
FD09797327FAB3E200936362 /* ProfileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileManager.swift; sourceTree = "<group>"; };
FD09797627FAB7A600936362 /* Data+Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Image.swift"; sourceTree = "<group>"; };
FD09797827FAB7E800936362 /* ImageFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFormat.swift; sourceTree = "<group>"; };
FD09797A27FBB25900936362 /* Updatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updatable.swift; sourceTree = "<group>"; };
FD09797C27FBDB2000936362 /* Notification+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Utilities.swift"; sourceTree = "<group>"; };
FD09797E27FCFBFF00936362 /* OWSAES256Key+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OWSAES256Key+Utilities.swift"; sourceTree = "<group>"; };
FD09798027FCFEE800936362 /* SessionThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThread.swift; sourceTree = "<group>"; };
FD09798227FD1A1500936362 /* ClosedGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosedGroup.swift; sourceTree = "<group>"; };
FD09798427FD1A6500936362 /* ClosedGroupKeyPair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosedGroupKeyPair.swift; sourceTree = "<group>"; };
FD09798627FD1B7800936362 /* GroupMember.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMember.swift; sourceTree = "<group>"; };
FD09798827FD1C5A00936362 /* OpenGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroup.swift; sourceTree = "<group>"; };
FD09798A27FD1CFE00936362 /* Capability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Capability.swift; sourceTree = "<group>"; };
FD09798C27FD1D8900936362 /* DisappearingMessageConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisappearingMessageConfiguration.swift; sourceTree = "<group>"; };
FD09799027FD499200936362 /* BoxKeyPair+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BoxKeyPair+Utilities.swift"; sourceTree = "<group>"; };
FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = "<group>"; }; FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = "<group>"; };
FD17D79827F40AB800122BE0 /* _002_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_YDBToGRDBMigration.swift; sourceTree = "<group>"; }; FD17D79827F40AB800122BE0 /* _002_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_YDBToGRDBMigration.swift; sourceTree = "<group>"; };
FD17D79B27F40B2E00122BE0 /* SMKLegacyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKLegacyModels.swift; sourceTree = "<group>"; }; FD17D79B27F40B2E00122BE0 /* SMKLegacyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKLegacyModels.swift; sourceTree = "<group>"; };
@ -2314,13 +2342,16 @@
children = ( children = (
C33FDB54255A580D00E217F9 /* DataSource.h */, C33FDB54255A580D00E217F9 /* DataSource.h */,
C33FDBB6255A581600E217F9 /* DataSource.m */, C33FDBB6255A581600E217F9 /* DataSource.m */,
FD09797827FAB7E800936362 /* ImageFormat.swift */,
C33FDAFC255A580600E217F9 /* MIMETypeUtil.h */, C33FDAFC255A580600E217F9 /* MIMETypeUtil.h */,
C33FDB41255A580C00E217F9 /* MIMETypeUtil.m */, C33FDB41255A580C00E217F9 /* MIMETypeUtil.m */,
C33FDB29255A580A00E217F9 /* NSData+Image.h */, C33FDB29255A580A00E217F9 /* NSData+Image.h */,
C33FDAEF255A580500E217F9 /* NSData+Image.m */, C33FDAEF255A580500E217F9 /* NSData+Image.m */,
FD09797627FAB7A600936362 /* Data+Image.swift */,
C33FDB22255A580900E217F9 /* OWSMediaUtils.swift */, C33FDB22255A580900E217F9 /* OWSMediaUtils.swift */,
C33FDB1C255A580900E217F9 /* UIImage+OWS.h */, C33FDB1C255A580900E217F9 /* UIImage+OWS.h */,
C33FDB81255A581100E217F9 /* UIImage+OWS.m */, C33FDB81255A581100E217F9 /* UIImage+OWS.m */,
FD09797A27FBB25900936362 /* Updatable.swift */,
); );
path = Media; path = Media;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2666,7 +2697,6 @@
C33FDAB1255A580000E217F9 /* OWSStorage.m */, C33FDAB1255A580000E217F9 /* OWSStorage.m */,
C33FDAB9255A580100E217F9 /* OWSStorage+Subclass.h */, C33FDAB9255A580100E217F9 /* OWSStorage+Subclass.h */,
B8D8F1372566120F0092EF10 /* Storage+ClosedGroups.swift */, B8D8F1372566120F0092EF10 /* Storage+ClosedGroups.swift */,
B8B32032258B235D0020074B /* Storage+Contacts.swift */,
B8D8F17625661AFA0092EF10 /* Storage+Jobs.swift */, B8D8F17625661AFA0092EF10 /* Storage+Jobs.swift */,
B8D8F19225661BF80092EF10 /* Storage+Messaging.swift */, B8D8F19225661BF80092EF10 /* Storage+Messaging.swift */,
B8D8F18825661BA50092EF10 /* Storage+OpenGroups.swift */, B8D8F18825661BA50092EF10 /* Storage+OpenGroups.swift */,
@ -3148,6 +3178,7 @@
children = ( children = (
C33FDB01255A580700E217F9 /* AppReadiness.h */, C33FDB01255A580700E217F9 /* AppReadiness.h */,
C33FDB75255A581000E217F9 /* AppReadiness.m */, C33FDB75255A581000E217F9 /* AppReadiness.m */,
FD09799027FD499200936362 /* BoxKeyPair+Utilities.swift */,
C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */, C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */,
C37F53E8255BA9BB002AEA92 /* Environment.h */, C37F53E8255BA9BB002AEA92 /* Environment.h */,
C37F5402255BA9ED002AEA92 /* Environment.m */, C37F5402255BA9ED002AEA92 /* Environment.m */,
@ -3157,6 +3188,7 @@
C3A71D0A2558989C0043A11F /* MessageWrapper.swift */, C3A71D0A2558989C0043A11F /* MessageWrapper.swift */,
C3A71D4E25589FF30043A11F /* NSData+messagePadding.h */, C3A71D4E25589FF30043A11F /* NSData+messagePadding.h */,
C3A71D4825589FF20043A11F /* NSData+messagePadding.m */, C3A71D4825589FF20043A11F /* NSData+messagePadding.m */,
FD09797E27FCFBFF00936362 /* OWSAES256Key+Utilities.swift */,
C38EF2F5255B6DBC007E1867 /* OWSAudioPlayer.h */, C38EF2F5255B6DBC007E1867 /* OWSAudioPlayer.h */,
C38EF2F7255B6DBC007E1867 /* OWSAudioPlayer.m */, C38EF2F7255B6DBC007E1867 /* OWSAudioPlayer.m */,
C38EF281255B6D84007E1867 /* OWSAudioSession.swift */, C38EF281255B6D84007E1867 /* OWSAudioSession.swift */,
@ -3175,6 +3207,7 @@
7B1581E1271E743B00848B49 /* OWSSounds.swift */, 7B1581E1271E743B00848B49 /* OWSSounds.swift */,
C38EF2FB255B6DBD007E1867 /* OWSWindowManager.h */, C38EF2FB255B6DBD007E1867 /* OWSWindowManager.h */,
C38EF306255B6DBE007E1867 /* OWSWindowManager.m */, C38EF306255B6DBE007E1867 /* OWSWindowManager.m */,
FD09797327FAB3E200936362 /* ProfileManager.swift */,
C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */, C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */,
C3BBE0B42554F0E10050F1E3 /* ProofOfWork.swift */, C3BBE0B42554F0E10050F1E3 /* ProofOfWork.swift */,
C33FDB91255A581200E217F9 /* ProtoUtils.h */, C33FDB91255A581200E217F9 /* ProtoUtils.h */,
@ -3558,6 +3591,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FD09796A27F6C67500936362 /* Failable.swift */, FD09796A27F6C67500936362 /* Failable.swift */,
FD09797127FAA2F500936362 /* Optional+Utilities.swift */,
FD09797C27FBDB2000936362 /* Notification+Utilities.swift */,
C3E7134E251C867C009649BB /* Sodium+Conversion.swift */, C3E7134E251C867C009649BB /* Sodium+Conversion.swift */,
); );
path = Utilities; path = Utilities;
@ -3568,6 +3603,13 @@
children = ( children = (
FD09796D27FA6D0000936362 /* Contact.swift */, FD09796D27FA6D0000936362 /* Contact.swift */,
FD09796F27FA6FF300936362 /* Profile.swift */, FD09796F27FA6FF300936362 /* Profile.swift */,
FD09798027FCFEE800936362 /* SessionThread.swift */,
FD09798C27FD1D8900936362 /* DisappearingMessageConfiguration.swift */,
FD09798227FD1A1500936362 /* ClosedGroup.swift */,
FD09798427FD1A6500936362 /* ClosedGroupKeyPair.swift */,
FD09798827FD1C5A00936362 /* OpenGroup.swift */,
FD09798627FD1B7800936362 /* GroupMember.swift */,
FD09798A27FD1CFE00936362 /* Capability.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@ -4720,11 +4762,13 @@
FD17D7C127F5200100122BE0 /* TypedTableDefinition.swift in Sources */, FD17D7C127F5200100122BE0 /* TypedTableDefinition.swift in Sources */,
FD17D7EA27F6A1C600122BE0 /* SUKLegacyModels.swift in Sources */, FD17D7EA27F6A1C600122BE0 /* SUKLegacyModels.swift in Sources */,
C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */, C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */,
FD09797B27FBB25900936362 /* Updatable.swift in Sources */,
C32C5A2D256DB849003C73A2 /* LKGroupUtilities.m in Sources */, C32C5A2D256DB849003C73A2 /* LKGroupUtilities.m in Sources */,
C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */, C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */,
B8856E09256F1676001CE70E /* UIDevice+featureSupport.swift in Sources */, B8856E09256F1676001CE70E /* UIDevice+featureSupport.swift in Sources */,
B8856DEF256F161F001CE70E /* NSString+SSK.m in Sources */, B8856DEF256F161F001CE70E /* NSString+SSK.m in Sources */,
FD09796727F6B0B600936362 /* Sodium+Conversion.swift in Sources */, FD09796727F6B0B600936362 /* Sodium+Conversion.swift in Sources */,
FD09797927FAB7E800936362 /* ImageFormat.swift in Sources */,
C32C5DC9256DD935003C73A2 /* ProxiedContentDownloader.swift in Sources */, C32C5DC9256DD935003C73A2 /* ProxiedContentDownloader.swift in Sources */,
C3D9E4C02567767F0040E4F3 /* DataSource.m in Sources */, C3D9E4C02567767F0040E4F3 /* DataSource.m in Sources */,
C3D9E43125676D3D0040E4F3 /* Configuration.swift in Sources */, C3D9E43125676D3D0040E4F3 /* Configuration.swift in Sources */,
@ -4740,6 +4784,7 @@
C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */, C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */,
C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */, C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */,
C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */, C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */,
FD09797D27FBDB2000936362 /* Notification+Utilities.swift in Sources */,
C3C2AC2E2553CBEB00C340D1 /* String+Trimming.swift in Sources */, C3C2AC2E2553CBEB00C340D1 /* String+Trimming.swift in Sources */,
C32C5B48256DC211003C73A2 /* NSNotificationCenter+OWS.m in Sources */, C32C5B48256DC211003C73A2 /* NSNotificationCenter+OWS.m in Sources */,
FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */, FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */,
@ -4777,10 +4822,12 @@
FD17D7BF27F51F8200122BE0 /* ColumnExpressible.swift in Sources */, FD17D7BF27F51F8200122BE0 /* ColumnExpressible.swift in Sources */,
FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */, FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */,
B87EF18126377A1D00124B3C /* Features.swift in Sources */, B87EF18126377A1D00124B3C /* Features.swift in Sources */,
FD09797727FAB7A600936362 /* Data+Image.swift in Sources */,
C300A60D2554B31900555489 /* Logging.swift in Sources */, C300A60D2554B31900555489 /* Logging.swift in Sources */,
B8FF8EA625C11FEF004D1F22 /* IPv4.swift in Sources */, B8FF8EA625C11FEF004D1F22 /* IPv4.swift in Sources */,
C3D9E35525675EE10040E4F3 /* MIMETypeUtil.m in Sources */, C3D9E35525675EE10040E4F3 /* MIMETypeUtil.m in Sources */,
FD705A8E278CE29800F16121 /* String+Localization.swift in Sources */, FD705A8E278CE29800F16121 /* String+Localization.swift in Sources */,
FD09797227FAA2F500936362 /* Optional+Utilities.swift in Sources */,
C3A7219A2558C1660043A11F /* AnyPromise+Conversion.swift in Sources */, C3A7219A2558C1660043A11F /* AnyPromise+Conversion.swift in Sources */,
C300A6322554B6D100555489 /* NSDate+Timestamp.mm in Sources */, C300A6322554B6D100555489 /* NSDate+Timestamp.mm in Sources */,
FD17D7E727F6A16700122BE0 /* _002_YDBToGRDBMigration.swift in Sources */, FD17D7E727F6A16700122BE0 /* _002_YDBToGRDBMigration.swift in Sources */,
@ -4816,6 +4863,7 @@
C300A5FC2554B0A000555489 /* MessageReceiver.swift in Sources */, C300A5FC2554B0A000555489 /* MessageReceiver.swift in Sources */,
7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */, 7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */,
C32C5A76256DBBCF003C73A2 /* SignalAttachment.swift in Sources */, C32C5A76256DBBCF003C73A2 /* SignalAttachment.swift in Sources */,
FD09798927FD1C5A00936362 /* OpenGroup.swift in Sources */,
C32C5CA4256DD1DC003C73A2 /* TSAccountManager.m in Sources */, C32C5CA4256DD1DC003C73A2 /* TSAccountManager.m in Sources */,
C352A3892557876500338F3E /* JobQueue.swift in Sources */, C352A3892557876500338F3E /* JobQueue.swift in Sources */,
C3BBE0B52554F0E10050F1E3 /* ProofOfWork.swift in Sources */, C3BBE0B52554F0E10050F1E3 /* ProofOfWork.swift in Sources */,
@ -4824,14 +4872,19 @@
B8856D1A256F114D001CE70E /* ProximityMonitoringManager.swift in Sources */, B8856D1A256F114D001CE70E /* ProximityMonitoringManager.swift in Sources */,
C3D9E52725677DF20040E4F3 /* OWSThumbnailService.swift in Sources */, C3D9E52725677DF20040E4F3 /* OWSThumbnailService.swift in Sources */,
C32C5E75256DE020003C73A2 /* YapDatabaseTransaction+OWS.m in Sources */, C32C5E75256DE020003C73A2 /* YapDatabaseTransaction+OWS.m in Sources */,
FD09797527FAB64300936362 /* ProfileManager.swift in Sources */,
C3BBE0802554CDD70050F1E3 /* Storage.swift in Sources */, C3BBE0802554CDD70050F1E3 /* Storage.swift in Sources */,
C3DB66AC260ACA42001EFC55 /* OpenGroupManagerV2.swift in Sources */, C3DB66AC260ACA42001EFC55 /* OpenGroupManagerV2.swift in Sources */,
B8F5F61B25EDE4BF003BF8D4 /* DataExtractionNotificationInfoMessage.swift in Sources */, B8F5F61B25EDE4BF003BF8D4 /* DataExtractionNotificationInfoMessage.swift in Sources */,
C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */, C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */,
FD09797F27FCFBFF00936362 /* OWSAES256Key+Utilities.swift in Sources */,
FD09798327FD1A1500936362 /* ClosedGroup.swift in Sources */,
B8856D34256F1192001CE70E /* Environment.m in Sources */, B8856D34256F1192001CE70E /* Environment.m in Sources */,
B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */, B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */,
C32C5AB1256DBE8F003C73A2 /* TSIncomingMessage.m in Sources */, C32C5AB1256DBE8F003C73A2 /* TSIncomingMessage.m in Sources */,
C3A3A107256E1A5C004D228D /* OWSDisappearingMessagesFinder.m in Sources */, C3A3A107256E1A5C004D228D /* OWSDisappearingMessagesFinder.m in Sources */,
FD09798727FD1B7800936362 /* GroupMember.swift in Sources */,
FD09799127FD499200936362 /* BoxKeyPair+Utilities.swift in Sources */,
C32C59C3256DB41F003C73A2 /* TSGroupModel.m in Sources */, C32C59C3256DB41F003C73A2 /* TSGroupModel.m in Sources */,
B8856ECE256F1E58001CE70E /* OWSPreferences.m in Sources */, B8856ECE256F1E58001CE70E /* OWSPreferences.m in Sources */,
FD17D79927F40AB800122BE0 /* _002_YDBToGRDBMigration.swift in Sources */, FD17D79927F40AB800122BE0 /* _002_YDBToGRDBMigration.swift in Sources */,
@ -4861,10 +4914,10 @@
C3DA9C0725AE7396008F7C7E /* ConfigurationMessage.swift in Sources */, C3DA9C0725AE7396008F7C7E /* ConfigurationMessage.swift in Sources */,
B8856CEE256F1054001CE70E /* OWSAudioPlayer.m in Sources */, B8856CEE256F1054001CE70E /* OWSAudioPlayer.m in Sources */,
FD5D200F27AA2B6000FEA984 /* MessageRequestResponse.swift in Sources */, FD5D200F27AA2B6000FEA984 /* MessageRequestResponse.swift in Sources */,
FD09798D27FD1D8900936362 /* DisappearingMessageConfiguration.swift in Sources */,
C32C5EDC256DF501003C73A2 /* YapDatabaseConnection+OWS.m in Sources */, C32C5EDC256DF501003C73A2 /* YapDatabaseConnection+OWS.m in Sources */,
C3BBE0762554CDA60050F1E3 /* Configuration.swift in Sources */, C3BBE0762554CDA60050F1E3 /* Configuration.swift in Sources */,
C35D76DB26606304009AA5FB /* ThreadUpdateBatcher.swift in Sources */, C35D76DB26606304009AA5FB /* ThreadUpdateBatcher.swift in Sources */,
B8B32033258B235D0020074B /* Storage+Contacts.swift in Sources */,
B8856D69256F141F001CE70E /* OWSWindowManager.m in Sources */, B8856D69256F141F001CE70E /* OWSWindowManager.m in Sources */,
C3D9E3BE25676AD70040E4F3 /* TSAttachmentPointer.m in Sources */, C3D9E3BE25676AD70040E4F3 /* TSAttachmentPointer.m in Sources */,
C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */, C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */,
@ -4908,6 +4961,7 @@
C3A3A18A256E2092004D228D /* SignalRecipient.m in Sources */, C3A3A18A256E2092004D228D /* SignalRecipient.m in Sources */,
C3C2A74425539EB700C340D1 /* Message.swift in Sources */, C3C2A74425539EB700C340D1 /* Message.swift in Sources */,
C32C5F11256DF79A003C73A2 /* SSKIncrementingIdFinder.swift in Sources */, C32C5F11256DF79A003C73A2 /* SSKIncrementingIdFinder.swift in Sources */,
FD09798527FD1A6500936362 /* ClosedGroupKeyPair.swift in Sources */,
C32C5DBF256DD743003C73A2 /* ClosedGroupPoller.swift in Sources */, C32C5DBF256DD743003C73A2 /* ClosedGroupPoller.swift in Sources */,
C32C5EEE256DF54E003C73A2 /* TSDatabaseView.m in Sources */, C32C5EEE256DF54E003C73A2 /* TSDatabaseView.m in Sources */,
C352A35B2557824E00338F3E /* AttachmentUploadJob.swift in Sources */, C352A35B2557824E00338F3E /* AttachmentUploadJob.swift in Sources */,
@ -4916,11 +4970,13 @@
C32C5AAE256DBE8F003C73A2 /* TSInteraction.m in Sources */, C32C5AAE256DBE8F003C73A2 /* TSInteraction.m in Sources */,
C32C5D23256DD4C0003C73A2 /* Mention.swift in Sources */, C32C5D23256DD4C0003C73A2 /* Mention.swift in Sources */,
C32C5FD6256E0346003C73A2 /* Notification+Thread.swift in Sources */, C32C5FD6256E0346003C73A2 /* Notification+Thread.swift in Sources */,
FD09798B27FD1CFE00936362 /* Capability.swift in Sources */,
C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */, C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */,
C32C5C88256DD0D2003C73A2 /* Storage+Messaging.swift in Sources */, C32C5C88256DD0D2003C73A2 /* Storage+Messaging.swift in Sources */,
FD17D79C27F40B2E00122BE0 /* SMKLegacyModels.swift in Sources */, FD17D79C27F40B2E00122BE0 /* SMKLegacyModels.swift in Sources */,
C32C59C7256DB41F003C73A2 /* TSThread.m in Sources */, C32C59C7256DB41F003C73A2 /* TSThread.m in Sources */,
C300A5B22554AF9800555489 /* VisibleMessage+Profile.swift in Sources */, C300A5B22554AF9800555489 /* VisibleMessage+Profile.swift in Sources */,
FD09798127FCFEE800936362 /* SessionThread.swift in Sources */,
C32C5A75256DBBCF003C73A2 /* TSAttachmentPointer+Conversion.swift in Sources */, C32C5A75256DBBCF003C73A2 /* TSAttachmentPointer+Conversion.swift in Sources */,
C32C5AF8256DC051003C73A2 /* OWSDisappearingMessagesConfiguration.m in Sources */, C32C5AF8256DC051003C73A2 /* OWSDisappearingMessagesConfiguration.m in Sources */,
C32C5EBA256DE130003C73A2 /* OWSQuotedReplyModel.m in Sources */, C32C5EBA256DE130003C73A2 /* OWSQuotedReplyModel.m in Sources */,

View file

@ -1,4 +1,8 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import PromiseKit import PromiseKit
import SessionMessagingKit
@objc(SNEditClosedGroupVC) @objc(SNEditClosedGroupVC)
final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegate { final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegate {
@ -73,7 +77,7 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega
backButton.tintColor = Colors.text backButton.tintColor = Colors.text
navigationItem.backBarButtonItem = backButton navigationItem.backBarButtonItem = backButton
func getDisplayName(for publicKey: String) -> String { func getDisplayName(for publicKey: String) -> String {
return Storage.shared.getContact(with: publicKey)?.displayName(for: .regular) ?? publicKey return Profile.displayName(for: publicKey)
} }
setUpViewHierarchy() setUpViewHierarchy()
// Always show zombies at the bottom // Always show zombies at the bottom
@ -107,7 +111,7 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega
membersLabel.font = .systemFont(ofSize: Values.mediumFontSize) membersLabel.font = .systemFont(ofSize: Values.mediumFontSize)
membersLabel.text = "Members" membersLabel.text = "Members"
// Add members button // Add members button
let hasContactsToAdd = !Set(ContactUtilities.getAllContacts()).subtracting(self.membersAndZombies).isEmpty let hasContactsToAdd = !Set(Contact.fetchAllIds()).subtracting(self.membersAndZombies).isEmpty
if (!hasContactsToAdd) { if (!hasContactsToAdd) {
addMembersButton.isUserInteractionEnabled = false addMembersButton.isUserInteractionEnabled = false
let disabledColor = Colors.text.withAlphaComponent(Values.mediumOpacity) let disabledColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
@ -246,10 +250,10 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega
var members = self.membersAndZombies var members = self.membersAndZombies
members.append(contentsOf: selectedUsers) members.append(contentsOf: selectedUsers)
func getDisplayName(for publicKey: String) -> String { func getDisplayName(for publicKey: String) -> String {
return Storage.shared.getContact(with: publicKey)?.displayName(for: .regular) ?? publicKey return Profile.displayName(for: publicKey)
} }
self.membersAndZombies = members.sorted { getDisplayName(for: $0) < getDisplayName(for: $1) } self.membersAndZombies = members.sorted { getDisplayName(for: $0) < getDisplayName(for: $1) }
let hasContactsToAdd = !Set(ContactUtilities.getAllContacts()).subtracting(self.membersAndZombies).isEmpty let hasContactsToAdd = !Set(Contact.fetchAllIds()).subtracting(self.membersAndZombies).isEmpty
self.addMembersButton.isUserInteractionEnabled = hasContactsToAdd self.addMembersButton.isUserInteractionEnabled = hasContactsToAdd
let color = hasContactsToAdd ? Colors.accent : Colors.text.withAlphaComponent(Values.mediumOpacity) let color = hasContactsToAdd ? Colors.accent : Colors.text.withAlphaComponent(Values.mediumOpacity)
self.addMembersButton.layer.borderColor = color.cgColor self.addMembersButton.layer.borderColor = color.cgColor

View file

@ -1,4 +1,8 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import PromiseKit import PromiseKit
import SessionMessagingKit
private protocol TableViewTouchDelegate { private protocol TableViewTouchDelegate {
@ -15,7 +19,7 @@ private final class TableView : UITableView {
} }
final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegate, TableViewTouchDelegate, UITextFieldDelegate, UIScrollViewDelegate { final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegate, TableViewTouchDelegate, UITextFieldDelegate, UIScrollViewDelegate {
private let contacts = ContactUtilities.getAllContacts() private let contacts = Contact.fetchAllIds()
private var selectedContacts: Set<String> = [] private var selectedContacts: Set<String> = []
// MARK: Components // MARK: Components
@ -174,7 +178,10 @@ final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegat
promise = MessageSender.createClosedGroup(name: name, members: selectedContacts, transaction: transaction) promise = MessageSender.createClosedGroup(name: name, members: selectedContacts, transaction: transaction)
} }
let _ = promise.done(on: DispatchQueue.main) { thread in let _ = promise.done(on: DispatchQueue.main) { thread in
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() GRDBStorage.shared.write { db in
MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete()
}
self?.presentingViewController?.dismiss(animated: true, completion: nil) self?.presentingViewController?.dismiss(animated: true, completion: nil)
SignalApp.shared().presentConversation(for: thread, action: .compose, animated: false) SignalApp.shared().presentConversation(for: thread, action: .compose, animated: false)
} }

View file

@ -1,8 +1,11 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit import UIKit
import CoreServices import CoreServices
import Photos import Photos
import PhotosUI import PhotosUI
import PromiseKit import PromiseKit
import GRDB
import SessionUtilitiesKit import SessionUtilitiesKit
import SignalUtilitiesKit import SignalUtilitiesKit
@ -14,7 +17,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
// Don't take the user to settings for message requests // Don't take the user to settings for message requests
guard guard
let contactThread: TSContactThread = thread as? TSContactThread, let contactThread: TSContactThread = thread as? TSContactThread,
let contact: Contact = Storage.shared.getContact(with: contactThread.contactSessionID()), let contact: Contact = GRDBStorage.shared.read({ db in try Contact.fetchOne(db, id: contactThread.contactSessionID()) }),
contact.isApproved, contact.isApproved,
contact.didApproveMe contact.didApproveMe
else { else {
@ -44,23 +47,31 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
@objc func unblock() { @objc func unblock() {
guard let thread = thread as? TSContactThread else { return } guard let thread = thread as? TSContactThread else { return }
let publicKey = thread.contactSessionID() let publicKey = thread.contactSessionID()
UIView.animate(withDuration: 0.25, animations: {
self.blockedBanner.alpha = 0
}, completion: { _ in
if let contact: Contact = Storage.shared.getContact(with: publicKey) {
Storage.shared.write(
with: { transaction in
guard let transaction = transaction as? YapDatabaseReadWriteTransaction else { return }
contact.isBlocked = false UIView.animate(
Storage.shared.setContact(contact, using: transaction) withDuration: 0.25,
animations: {
self.blockedBanner.alpha = 0
}, },
completion: { completion: { _ in
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() GRDBStorage.shared.writeAsync(
updates: { db in
try Contact
.fetchOne(db, id: publicKey)?
.with(isBlocked: false)
.update(db)
},
completion: { db, result in
switch result {
case .success:
MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete()
default: break
}
} }
) )
} }
}) )
} }
func showBlockedModalIfNeeded() -> Bool { func showBlockedModalIfNeeded() -> Bool {
@ -374,7 +385,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
// Update the input state if this is a contact thread // Update the input state if this is a contact thread
if let contactThread: TSContactThread = thread as? TSContactThread { if let contactThread: TSContactThread = thread as? TSContactThread {
let contact: Contact? = Storage.shared.getContact(with: contactThread.contactSessionID()) let contact: Contact? = GRDBStorage.shared.read { db in try Contact.fetchOne(db, id: contactThread.contactSessionID()) }
// If the contact doesn't exist yet then it's a message request without the first message sent // If the contact doesn't exist yet then it's a message request without the first message sent
// so only allow text-based messages // so only allow text-based messages
@ -525,9 +536,11 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
} else { } else {
switch viewItem.messageCellType { switch viewItem.messageCellType {
case .audio: case .audio:
if viewItem.interaction is TSIncomingMessage, if
viewItem.interaction is TSIncomingMessage,
let thread = self.thread as? TSContactThread, let thread = self.thread as? TSContactThread,
Storage.shared.getContact(with: thread.contactSessionID())?.isTrusted != true { let contact: Contact? = GRDBStorage.shared.read({ db in try Contact.fetchOne(db, id: thread.contactSessionID()) }),
contact?.isTrusted != true {
confirmDownload() confirmDownload()
} else { } else {
playOrPauseAudio(for: viewItem) playOrPauseAudio(for: viewItem)
@ -535,9 +548,11 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
case .mediaMessage: case .mediaMessage:
guard let index = viewItems.firstIndex(where: { $0 === viewItem }), guard let index = viewItems.firstIndex(where: { $0 === viewItem }),
let cell = messagesTableView.cellForRow(at: IndexPath(row: index, section: 0)) as? VisibleMessageCell else { return } let cell = messagesTableView.cellForRow(at: IndexPath(row: index, section: 0)) as? VisibleMessageCell else { return }
if viewItem.interaction is TSIncomingMessage, if
viewItem.interaction is TSIncomingMessage,
let thread = self.thread as? TSContactThread, let thread = self.thread as? TSContactThread,
Storage.shared.getContact(with: thread.contactSessionID())?.isTrusted != true { let contact: Contact? = GRDBStorage.shared.read({ db in try Contact.fetchOne(db, id: thread.contactSessionID()) }),
contact?.isTrusted != true {
confirmDownload() confirmDownload()
} else { } else {
guard let albumView = cell.albumView else { return } guard let albumView = cell.albumView else { return }
@ -559,9 +574,11 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
gallery.presentDetailView(fromViewController: self, mediaAttachment: stream) gallery.presentDetailView(fromViewController: self, mediaAttachment: stream)
} }
case .genericAttachment: case .genericAttachment:
if viewItem.interaction is TSIncomingMessage, if
viewItem.interaction is TSIncomingMessage,
let thread = self.thread as? TSContactThread, let thread = self.thread as? TSContactThread,
Storage.shared.getContact(with: thread.contactSessionID())?.isTrusted != true { let contact: Contact? = GRDBStorage.shared.read({ db in try Contact.fetchOne(db, id: thread.contactSessionID()) }),
contact?.isTrusted != true {
confirmDownload() confirmDownload()
} }
else if ( else if (
@ -1108,9 +1125,13 @@ extension ConversationVC {
// (it'll be updated with correct profile info if they accept the message request so this // (it'll be updated with correct profile info if they accept the message request so this
// shouldn't cause weird behaviours) // shouldn't cause weird behaviours)
let sessionId: String = contactThread.contactSessionID() let sessionId: String = contactThread.contactSessionID()
let contact: Contact = (Storage.shared.getContact(with: sessionId) ?? Contact(sessionID: sessionId))
guard !contact.isApproved else { return Promise.value(()) } guard
let contact: Contact = GRDBStorage.shared.read({ db in Contact.fetchOrCreate(db, id: sessionId) }),
!contact.isApproved
else {
return Promise.value(())
}
return Promise.value(()) return Promise.value(())
.then { [weak self] _ -> Promise<Void> in .then { [weak self] _ -> Promise<Void> in
@ -1151,17 +1172,20 @@ extension ConversationVC {
} }
.map { _ in .map { _ in
// Default 'didApproveMe' to true for the person approving the message request // Default 'didApproveMe' to true for the person approving the message request
Storage.write { transaction in GRDBStorage.shared.writeAsync(
contact.isApproved = true updates: { db in
contact.didApproveMe = (contact.didApproveMe || !isNewThread) try contact
Storage.shared.setContact(contact, using: transaction) .with(
} isApproved: true,
didApproveMe: .update(contact.didApproveMe || !isNewThread)
)
.save(db)
},
completion: { db, _ in
// Send a sync message with the details of the contact // Send a sync message with the details of the contact
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete()
// Hide the 'messageRequestView' since the request has been approved and force a config // Hide the 'messageRequestView' since the request has been approved
// sync to propagate the contact approval state (both must run on the main thread)
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
let messageRequestViewWasVisible: Bool = (self?.messageRequestView.isHidden == false) let messageRequestViewWasVisible: Bool = (self?.messageRequestView.isHidden == false)
@ -1170,8 +1194,8 @@ extension ConversationVC {
self?.scrollButtonMessageRequestsBottomConstraint?.isActive = false self?.scrollButtonMessageRequestsBottomConstraint?.isActive = false
self?.scrollButtonBottomConstraint?.isActive = true self?.scrollButtonBottomConstraint?.isActive = true
// Update the table content inset and offset to account for the dissapearance of // Update the table content inset and offset to account for
// the messageRequestsView // the dissapearance of the messageRequestsView
if messageRequestViewWasVisible { if messageRequestViewWasVisible {
let messageRequestsOffset: CGFloat = ((self?.messageRequestView.bounds.height ?? 0) + 16) let messageRequestsOffset: CGFloat = ((self?.messageRequestView.bounds.height ?? 0) + 16)
let oldContentInset: UIEdgeInsets = (self?.messagesTableView.contentInset ?? UIEdgeInsets.zero) let oldContentInset: UIEdgeInsets = (self?.messagesTableView.contentInset ?? UIEdgeInsets.zero)
@ -1186,15 +1210,21 @@ extension ConversationVC {
// Update UI // Update UI
self?.updateNavBarButtons() self?.updateNavBarButtons()
if let viewControllers: [UIViewController] = self?.navigationController?.viewControllers,
// Remove the 'MessageRequestsViewController' from the nav hierarchy if present
if
let viewControllers: [UIViewController] = self?.navigationController?.viewControllers,
let messageRequestsIndex = viewControllers.firstIndex(where: { $0 is MessageRequestsViewController }), let messageRequestsIndex = viewControllers.firstIndex(where: { $0 is MessageRequestsViewController }),
messageRequestsIndex > 0 { messageRequestsIndex > 0
{
var newViewControllers = viewControllers var newViewControllers = viewControllers
newViewControllers.remove(at: messageRequestsIndex) newViewControllers.remove(at: messageRequestsIndex)
self?.navigationController?.setViewControllers(newViewControllers, animated: false) self?.navigationController?.setViewControllers(newViewControllers, animated: false)
} }
} }
} }
)
}
} }
@objc func acceptMessageRequest() { @objc func acceptMessageRequest() {
@ -1220,32 +1250,37 @@ extension ConversationVC {
let alertVC: UIAlertController = UIAlertController(title: NSLocalizedString("MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON", comment: ""), message: nil, preferredStyle: .actionSheet) let alertVC: UIAlertController = UIAlertController(title: NSLocalizedString("MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON", comment: ""), message: nil, preferredStyle: .actionSheet)
alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_DELETE_TITLE", comment: ""), style: .destructive) { _ in alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_DELETE_TITLE", comment: ""), style: .destructive) { _ in
// Delete the request // Delete the request
Storage.write( GRDBStorage.shared.writeAsync(
with: { [weak self] transaction in updates: { [weak self] db in
Storage.shared.cancelPendingMessageSendJobs(for: uniqueId, using: transaction)
// Update the contact // Update the contact
if let contactThread: TSContactThread = self?.thread as? TSContactThread { if let contactThread: TSContactThread = self?.thread as? TSContactThread {
let sessionId: String = contactThread.contactSessionID() let sessionId: String = contactThread.contactSessionID()
if let contact: Contact = Storage.shared.getContact(with: sessionId) {
// Stop observing the `BlockListDidChange` notification (we are about to pop the screen // Stop observing the `BlockListDidChange` notification (we are about to pop the screen
// so showing the banner just looks buggy) // so showing the banner just looks buggy)
if let strongSelf = self { if let strongSelf = self {
NotificationCenter.default.removeObserver(strongSelf, name: .contactBlockedStateChanged, object: nil) NotificationCenter.default.removeObserver(strongSelf, name: .contactBlockedStateChanged, object: nil)
} }
contact.isApproved = false try? Contact
contact.isBlocked = true .fetchOne(db, id: sessionId)?
.with(
isApproved: false,
isBlocked: true,
// Note: We set this to true so the current user will be able to send a // Note: We set this to true so the current user will be able to send a
// message to the person who originally sent them the message request in // message to the person who originally sent them the message request in
// the future if they unblock them // the future if they unblock them
contact.didApproveMe = true didApproveMe: true
)
Storage.shared.setContact(contact, using: transaction) .update(db)
}
} }
},
completion: { db, _ in
Storage.write(
with: { [weak self] transaction in
// TODO: This should be above the contact updating
Storage.shared.cancelPendingMessageSendJobs(for: uniqueId, using: transaction)
// Delete all thread content // Delete all thread content
self?.thread.removeAllThreadInteractions(with: transaction) self?.thread.removeAllThreadInteractions(with: transaction)
@ -1253,13 +1288,16 @@ extension ConversationVC {
}, },
completion: { [weak self] in completion: { [weak self] in
// Force a config sync and pop to the previous screen // Force a config sync and pop to the previous screen
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() // TODO: This might cause an "incorrect thread" crash
MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete()
DispatchQueue.main.async { DispatchQueue.main.async {
self?.navigationController?.popViewController(animated: true) self?.navigationController?.popViewController(animated: true)
} }
} }
) )
}
)
}) })
alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel, handler: nil)) alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel, handler: nil))
self.present(alertVC, animated: true, completion: nil) self.present(alertVC, animated: true, completion: nil)

View file

@ -77,7 +77,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
} }
// Legacy account // Legacy account
return Mnemonic.encode(hexEncodedString: Identity.fetchUserKeyPair()!.hexEncodedPrivateKey) return Mnemonic.encode(hexEncodedString: Identity.fetchUserPrivateKey()!.toHexString())
}() }()
lazy var viewModel = ConversationViewModel(thread: thread, focusMessageIdOnOpen: nil, delegate: self) lazy var viewModel = ConversationViewModel(thread: thread, focusMessageIdOnOpen: nil, delegate: self)
@ -150,10 +150,9 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
lazy var blockedBanner: InfoBanner = { lazy var blockedBanner: InfoBanner = {
let name: String let name: String
if let thread = thread as? TSContactThread { if let thread = thread as? TSContactThread {
let publicKey = thread.contactSessionID() name = Profile.displayName(for: thread.contactSessionID(), thread: thread)
let context = Contact.context(for: thread) }
name = Storage.shared.getContact(with: publicKey)?.displayName(for: context) ?? publicKey else {
} else {
name = "Thread" name = "Thread"
} }
let message = "\(name) is blocked. Unblock them?" let message = "\(name) is blocked. Unblock them?"
@ -378,7 +377,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
// Update the input state if this is a contact thread // Update the input state if this is a contact thread
if let contactThread: TSContactThread = thread as? TSContactThread { if let contactThread: TSContactThread = thread as? TSContactThread {
let contact: Contact? = Storage.shared.getContact(with: contactThread.contactSessionID()) let contact: Contact? = GRDBStorage.shared.read { db in try Contact.fetchOne(db, id: contactThread.contactSessionID()) }
// If the contact doesn't exist yet then it's a message request without the first message sent // If the contact doesn't exist yet then it's a message request without the first message sent
// so only allow text-based messages // so only allow text-based messages
@ -473,7 +472,11 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
else { else {
if let contactThread: TSContactThread = thread as? TSContactThread { if let contactThread: TSContactThread = thread as? TSContactThread {
// Don't show the settings button for message requests // Don't show the settings button for message requests
if let contact: Contact = Storage.shared.getContact(with: contactThread.contactSessionID()), contact.isApproved, contact.didApproveMe { if
let contact: Contact = GRDBStorage.shared.read({ db in try Contact.fetchOne(db, id: contactThread.contactSessionID()) }),
contact.isApproved,
contact.didApproveMe
{
let size = Values.verySmallProfilePictureSize let size = Values.verySmallProfilePictureSize
let profilePictureView = ProfilePictureView() let profilePictureView = ProfilePictureView()
profilePictureView.size = size profilePictureView.size = size
@ -657,7 +660,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
// Update the input state if this is a contact thread // Update the input state if this is a contact thread
if let contactThread: TSContactThread = thread as? TSContactThread { if let contactThread: TSContactThread = thread as? TSContactThread {
let contact: Contact? = Storage.shared.getContact(with: contactThread.contactSessionID()) let contact: Contact? = GRDBStorage.shared.read { db in try Contact.fetchOne(db, id: contactThread.contactSessionID()) }
// If the contact doesn't exist yet then it's a message request without the first message sent // If the contact doesn't exist yet then it's a message request without the first message sent
// so only allow text-based messages // so only allow text-based messages
@ -718,18 +721,17 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
} }
} }
// MARK: General // MARK: - General
@objc func addOrRemoveBlockedBanner() { @objc func addOrRemoveBlockedBanner() {
func detach() { DispatchQueue.main.async {
blockedBanner.removeFromSuperview() guard let thread = self.thread as? TSContactThread, thread.isBlocked() else {
self.blockedBanner.removeFromSuperview()
return
} }
guard let thread = thread as? TSContactThread else { return detach() }
if thread.isBlocked() { self.view.addSubview(self.blockedBanner)
view.addSubview(blockedBanner) self.blockedBanner.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: self.view)
blockedBanner.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view)
}
else {
detach()
} }
} }

View file

@ -18,6 +18,7 @@
#import <SessionMessagingKit/TSThread.h> #import <SessionMessagingKit/TSThread.h>
#import <SessionMessagingKit/TSGroupThread.h> #import <SessionMessagingKit/TSGroupThread.h>
#import <SessionMessagingKit/TSGroupModel.h> #import <SessionMessagingKit/TSGroupModel.h>
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
#import <YapDatabase/YapDatabase.h> #import <YapDatabase/YapDatabase.h>
#import <YapDatabase/YapDatabaseAutoView.h> #import <YapDatabase/YapDatabaseAutoView.h>
#import <YapDatabase/YapDatabaseViewChange.h> #import <YapDatabase/YapDatabaseViewChange.h>
@ -258,11 +259,6 @@ NS_ASSUME_NONNULL_BEGIN
return SSKEnvironment.shared.tsAccountManager; return SSKEnvironment.shared.tsAccountManager;
} }
- (OWSProfileManager *)profileManager
{
return [OWSProfileManager sharedManager];
}
#pragma mark - #pragma mark -
- (void)addNotificationListeners - (void)addNotificationListeners
@ -281,7 +277,7 @@ NS_ASSUME_NONNULL_BEGIN
object:nil]; object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(localProfileDidChange:) selector:@selector(localProfileDidChange:)
name:kNSNotificationName_LocalProfileDidChange name:NSNotification.localProfileDidChange
object:nil]; object:nil];
} }
@ -1271,8 +1267,7 @@ NS_ASSUME_NONNULL_BEGIN
} }
if (shouldShowSenderName) { if (shouldShowSenderName) {
SNContactContext context = [SNContact contextForThread:self.thread]; senderName = [[NSAttributedString alloc] initWithString:[SMKProfile displayNameWithId:incomingSenderId thread:self.thread]];
senderName = [[NSAttributedString alloc] initWithString:[[LKStorage.shared getContactWithSessionID:incomingSenderId] displayNameFor:context] ?: incomingSenderId];
} }
// Show the sender profile picture for incoming group messages unless the // Show the sender profile picture for incoming group messages unless the

View file

@ -1,3 +1,8 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SessionUIKit
import SessionMessagingKit
final class QuoteView : UIView { final class QuoteView : UIView {
private let mode: Mode private let mode: Mode
@ -180,8 +185,7 @@ final class QuoteView : UIView {
if let groupThread = thread as? TSGroupThread { if let groupThread = thread as? TSGroupThread {
let authorLabel = UILabel() let authorLabel = UILabel()
authorLabel.lineBreakMode = .byTruncatingTail authorLabel.lineBreakMode = .byTruncatingTail
let context: Contact.Context = groupThread.isOpenGroup ? .openGroup : .regular authorLabel.text = Profile.displayName(for: authorID, thread: groupThread)
authorLabel.text = Storage.shared.getContact(with: authorID)?.displayName(for: context) ?? authorID
authorLabel.textColor = textColor authorLabel.textColor = textColor
authorLabel.font = .boldSystemFont(ofSize: Values.smallFontSize) authorLabel.font = .boldSystemFont(ofSize: Values.smallFontSize)
let authorLabelSize = authorLabel.systemLayoutSizeFitting(availableSpace) let authorLabelSize = authorLabel.systemLayoutSizeFitting(availableSpace)

View file

@ -1,5 +1,10 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
final class InfoMessageCell : MessageCell { import UIKit
import SessionUIKit
import SessionMessagingKit
final class InfoMessageCell: MessageCell {
private lazy var iconImageViewWidthConstraint = iconImageView.set(.width, to: InfoMessageCell.iconSize) private lazy var iconImageViewWidthConstraint = iconImageView.set(.width, to: InfoMessageCell.iconSize)
private lazy var iconImageViewHeightConstraint = iconImageView.set(.height, to: InfoMessageCell.iconSize) private lazy var iconImageViewHeightConstraint = iconImageView.set(.height, to: InfoMessageCell.iconSize)
@ -48,7 +53,7 @@ final class InfoMessageCell : MessageCell {
let icon: UIImage? let icon: UIImage?
switch message.messageType { switch message.messageType {
case .disappearingMessagesUpdate: case .disappearingMessagesUpdate:
var configuration: OWSDisappearingMessagesConfiguration? var configuration: SessionMessagingKit.Legacy.DisappearingMessagesConfiguration?
Storage.read { transaction in Storage.read { transaction in
configuration = message.thread(with: transaction).disappearingMessagesConfiguration(with: transaction) configuration = message.thread(with: transaction).disappearingMessagesConfiguration(with: transaction)
} }

View file

@ -1,3 +1,4 @@
import SessionUtilitiesKit
final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
private var unloadContent: (() -> Void)? private var unloadContent: (() -> Void)?
@ -351,11 +352,14 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
stackView.pin(to: snContentView, withInset: inset) stackView.pin(to: snContentView, withInset: inset)
} }
case .mediaMessage: case .mediaMessage:
if viewItem.interaction is TSIncomingMessage, if
viewItem.interaction is TSIncomingMessage,
let thread = thread as? TSContactThread, let thread = thread as? TSContactThread,
Storage.shared.getContact(with: thread.contactSessionID())?.isTrusted != true { let contact: Contact? = GRDBStorage.shared.read({ db in try Contact.fetchOne(db, id: thread.contactSessionID()) }),
contact?.isTrusted != true {
showMediaPlaceholder() showMediaPlaceholder()
} else { }
else {
guard let cache = delegate?.getMediaCache() else { preconditionFailure() } guard let cache = delegate?.getMediaCache() else { preconditionFailure() }
// Stack view // Stack view
let stackView = UIStackView(arrangedSubviews: []) let stackView = UIStackView(arrangedSubviews: [])
@ -385,11 +389,14 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
stackView.pin(to: snContentView) stackView.pin(to: snContentView)
} }
case .audio: case .audio:
if viewItem.interaction is TSIncomingMessage, if
viewItem.interaction is TSIncomingMessage,
let thread = thread as? TSContactThread, let thread = thread as? TSContactThread,
Storage.shared.getContact(with: thread.contactSessionID())?.isTrusted != true { let contact: Contact? = GRDBStorage.shared.read({ db in try Contact.fetchOne(db, id: thread.contactSessionID()) }),
contact?.isTrusted != true {
showMediaPlaceholder() showMediaPlaceholder()
} else { }
else {
let voiceMessageView = VoiceMessageView(viewItem: viewItem) let voiceMessageView = VoiceMessageView(viewItem: viewItem)
snContentView.addSubview(voiceMessageView) snContentView.addSubview(voiceMessageView)
voiceMessageView.pin(to: snContentView) voiceMessageView.pin(to: snContentView)
@ -397,11 +404,14 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
viewItem.lastAudioMessageView = voiceMessageView viewItem.lastAudioMessageView = voiceMessageView
} }
case .genericAttachment: case .genericAttachment:
if viewItem.interaction is TSIncomingMessage, if
viewItem.interaction is TSIncomingMessage,
let thread = thread as? TSContactThread, let thread = thread as? TSContactThread,
Storage.shared.getContact(with: thread.contactSessionID())?.isTrusted != true { let contact: Contact? = GRDBStorage.shared.read({ db in try Contact.fetchOne(db, id: thread.contactSessionID()) }),
contact?.isTrusted != true {
showMediaPlaceholder() showMediaPlaceholder()
} else { }
else {
let inset: CGFloat = 12 let inset: CGFloat = 12
let maxWidth = VisibleMessageCell.getMaxWidth(for: viewItem) - 2 * inset let maxWidth = VisibleMessageCell.getMaxWidth(for: viewItem) - 2 * inset
// Stack view // Stack view

View file

@ -105,18 +105,13 @@ CGFloat kIconViewLength = 24;
return SSKEnvironment.shared.tsAccountManager; return SSKEnvironment.shared.tsAccountManager;
} }
- (OWSProfileManager *)profileManager
{
return [OWSProfileManager sharedManager];
}
#pragma mark #pragma mark
- (void)observeNotifications - (void)observeNotifications
{ {
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(otherUsersProfileDidChange:) selector:@selector(otherUsersProfileDidChange:)
name:kNSNotificationName_OtherUsersProfileDidChange name:NSNotification.otherUsersProfileDidChange
object:nil]; object:nil];
} }
@ -130,7 +125,7 @@ CGFloat kIconViewLength = 24;
NSString *threadName = self.thread.name; NSString *threadName = self.thread.name;
if ([self.thread isKindOfClass:TSContactThread.class]) { if ([self.thread isKindOfClass:TSContactThread.class]) {
TSContactThread *thread = (TSContactThread *)self.thread; TSContactThread *thread = (TSContactThread *)self.thread;
return [[LKStorage.shared getContactWithSessionID:thread.contactSessionID] displayNameFor:SNContactContextRegular] ?: @"Anonymous"; return [SMKProfile displayNameWithId:thread.contactSessionID customFallback: @"Anonymous"];
} else if (threadName.length == 0 && [self isGroupThread]) { } else if (threadName.length == 0 && [self isGroupThread]) {
threadName = [MessageStrings newGroupDefaultTitle]; threadName = [MessageStrings newGroupDefaultTitle];
} }
@ -237,11 +232,10 @@ CGFloat kIconViewLength = 24;
self.disappearingMessagesDurations = [OWSDisappearingMessagesConfiguration validDurationsSeconds]; self.disappearingMessagesDurations = [OWSDisappearingMessagesConfiguration validDurationsSeconds];
self.disappearingMessagesConfiguration = self.disappearingMessagesConfiguration =
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:self.thread.uniqueId]; [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueId:self.thread.uniqueId];
if (!self.disappearingMessagesConfiguration) { if (!self.disappearingMessagesConfiguration) {
self.disappearingMessagesConfiguration = self.disappearingMessagesConfiguration = [OWSDisappearingMessagesConfiguration defaultWith: self.thread.uniqueId];
[[OWSDisappearingMessagesConfiguration alloc] initDefaultWithThreadId:self.thread.uniqueId];
} }
[self updateTableContents]; [self updateTableContents];
@ -361,7 +355,7 @@ CGFloat kIconViewLength = 24;
displayName = @"the group"; displayName = @"the group";
} else { } else {
TSContactThread *thread = (TSContactThread *)self.thread; TSContactThread *thread = (TSContactThread *)self.thread;
displayName = [[LKStorage.shared getContactWithSessionID:thread.contactSessionID] displayNameFor:SNContactContextRegular] ?: @"anonymous"; displayName = [SMKProfile displayNameWithId:thread.contactSessionID customFallback:@"anonymous"];
} }
subtitleLabel.text = [NSString stringWithFormat:NSLocalizedString(@"When enabled, messages between you and %@ will disappear after they have been seen.", ""), displayName]; subtitleLabel.text = [NSString stringWithFormat:NSLocalizedString(@"When enabled, messages between you and %@ will disappear after they have been seen.", ""), displayName];
subtitleLabel.textColor = LKColors.text; subtitleLabel.textColor = LKColors.text;
@ -762,7 +756,7 @@ CGFloat kIconViewLength = 24;
[infoMessage saveWithTransaction:transaction]; [infoMessage saveWithTransaction:transaction];
SNExpirationTimerUpdate *expirationTimerUpdate = [SNExpirationTimerUpdate new]; SNExpirationTimerUpdate *expirationTimerUpdate = [SNExpirationTimerUpdate new];
BOOL isEnabled = self.disappearingMessagesConfiguration.enabled; BOOL isEnabled = self.disappearingMessagesConfiguration.isEnabled;
expirationTimerUpdate.duration = isEnabled ? self.disappearingMessagesConfiguration.durationSeconds : 0; expirationTimerUpdate.duration = isEnabled ? self.disappearingMessagesConfiguration.durationSeconds : 0;
[SNMessageSender send:expirationTimerUpdate inThread:self.thread usingTransaction:transaction]; [SNMessageSender send:expirationTimerUpdate inThread:self.thread usingTransaction:transaction];
}]; }];
@ -908,7 +902,7 @@ CGFloat kIconViewLength = 24;
- (void)toggleDisappearingMessages:(BOOL)flag - (void)toggleDisappearingMessages:(BOOL)flag
{ {
self.disappearingMessagesConfiguration.enabled = flag; self.disappearingMessagesConfiguration.isEnabled = flag;
[self updateTableContents]; [self updateTableContents];
} }
@ -1027,16 +1021,11 @@ CGFloat kIconViewLength = 24;
{ {
if (![self.thread isKindOfClass:TSContactThread.class]) { return; } if (![self.thread isKindOfClass:TSContactThread.class]) { return; }
NSString *sessionID = ((TSContactThread *)self.thread).contactSessionID; NSString *sessionID = ((TSContactThread *)self.thread).contactSessionID;
SNContact *contact = [LKStorage.shared getContactWithSessionID:sessionID]; SMKProfile *profile = [SMKProfile fetchOrCreateWithId:sessionID];
if (contact == nil) {
contact = [[SNContact alloc] initWithSessionID:sessionID];
}
NSString *text = [self.displayNameTextField.text stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]; NSString *text = [self.displayNameTextField.text stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
contact.nickname = text.length > 0 ? text : nil; profile.nickname = text.length > 0 ? text : nil;
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [SMKProfile saveProfile: profile];
[LKStorage.shared setContact:contact usingTransaction:transaction]; self.displayNameLabel.text = text.length > 0 ? text : profile.name;
}];
self.displayNameLabel.text = text.length > 0 ? text : contact.name;
[self hideEditNameUI]; [self hideEditNameUI];
} }
@ -1069,7 +1058,7 @@ CGFloat kIconViewLength = 24;
{ {
OWSAssertIsOnMainThread(); OWSAssertIsOnMainThread();
NSString *recipientId = notification.userInfo[kNSNotificationKey_ProfileRecipientId]; NSString *recipientId = notification.userInfo[NSNotification.profileRecipientIdKey];
OWSAssertDebug(recipientId.length > 0); OWSAssertDebug(recipientId.length > 0);
if (recipientId.length > 0 && [self.thread isKindOfClass:[TSContactThread class]] && if (recipientId.length > 0 && [self.thread isKindOfClass:[TSContactThread class]] &&

View file

@ -1,3 +1,9 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import GRDB
import SessionUIKit
import SessionUtilitiesKit
import SessionMessagingKit import SessionMessagingKit
final class BlockedModal: Modal { final class BlockedModal: Modal {
@ -19,7 +25,7 @@ final class BlockedModal: Modal {
override func populateContentView() { override func populateContentView() {
// Name // Name
let name = Storage.shared.getContact(with: publicKey)?.displayName(for: .regular) ?? publicKey let name = Profile.displayName(for: publicKey)
// Title // Title
let titleLabel = UILabel() let titleLabel = UILabel()
titleLabel.textColor = Colors.text titleLabel.textColor = Colors.text
@ -66,17 +72,15 @@ final class BlockedModal: Modal {
@objc private func unblock() { @objc private func unblock() {
let publicKey: String = self.publicKey let publicKey: String = self.publicKey
Storage.shared.write( GRDBStorage.shared.writeAsync(
with: { transaction in updates: { db in
guard let transaction = transaction as? YapDatabaseReadWriteTransaction, let contact: Contact = Storage.shared.getContact(with: publicKey, using: transaction) else { try? Contact
return .fetchOne(db, id: publicKey)?
} .with(isBlocked: true)
.update(db)
contact.isBlocked = false
Storage.shared.setContact(contact, using: transaction as Any)
}, },
completion: { completion: { db, _ in
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete()
} }
) )

View file

@ -52,7 +52,7 @@ final class ConversationTitleView : UIView {
let notificationCenter = NotificationCenter.default let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(update), name: Notification.Name.groupThreadUpdated, object: nil) notificationCenter.addObserver(self, selector: #selector(update), name: Notification.Name.groupThreadUpdated, object: nil)
notificationCenter.addObserver(self, selector: #selector(update), name: Notification.Name.muteSettingUpdated, object: nil) notificationCenter.addObserver(self, selector: #selector(update), name: Notification.Name.muteSettingUpdated, object: nil)
notificationCenter.addObserver(self, selector: #selector(update), name: Notification.Name.contactUpdated, object: nil) notificationCenter.addObserver(self, selector: #selector(update), name: Notification.Name.profileUpdated, object: nil)
update() update()
} }
@ -62,11 +62,18 @@ final class ConversationTitleView : UIView {
// MARK: Updating // MARK: Updating
@objc private func update() { @objc private func update() {
titleLabel.text = getTitle() DispatchQueue.main.async {
let subtitle = getSubtitle() self.titleLabel.text = self.getTitle()
subtitleLabel.attributedText = subtitle
let titleFontSize = (subtitle != nil) ? Values.mediumFontSize : Values.veryLargeFontSize let subtitle: NSAttributedString? = self.getSubtitle()
titleLabel.font = .boldSystemFont(ofSize: titleFontSize) self.subtitleLabel.attributedText = subtitle
self.titleLabel.font = .boldSystemFont(
ofSize: (subtitle != nil ?
Values.mediumFontSize :
Values.veryLargeFontSize
)
)
}
} }
// MARK: General // MARK: General
@ -79,13 +86,9 @@ final class ConversationTitleView : UIView {
} }
else { else {
let sessionID = (thread as! TSContactThread).contactSessionID() let sessionID = (thread as! TSContactThread).contactSessionID()
var result = sessionID
Storage.read { transaction in
let displayName: String = ((Storage.shared.getContact(with: sessionID)?.displayName(for: .regular)) ?? sessionID)
let middleTruncatedHexKey: String = "\(sessionID.prefix(4))...\(sessionID.suffix(4))" let middleTruncatedHexKey: String = "\(sessionID.prefix(4))...\(sessionID.suffix(4))"
result = (displayName == sessionID ? middleTruncatedHexKey : displayName)
} return Profile.displayName(for: sessionID, customFallback: middleTruncatedHexKey)
return result
} }
} }

View file

@ -1,3 +1,10 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import GRDB
import SessionUIKit
import SessionUtilitiesKit
import SessionMessagingKit
final class DownloadAttachmentModal : Modal { final class DownloadAttachmentModal : Modal {
private let viewItem: ConversationViewItem private let viewItem: ConversationViewItem
@ -19,7 +26,7 @@ final class DownloadAttachmentModal : Modal {
override func populateContentView() { override func populateContentView() {
guard let publicKey = (viewItem.interaction as? TSIncomingMessage)?.authorId else { return } guard let publicKey = (viewItem.interaction as? TSIncomingMessage)?.authorId else { return }
// Name // Name
let name = Storage.shared.getContact(with: publicKey)?.displayName(for: .regular) ?? publicKey let name = Profile.displayName(for: publicKey)
// Title // Title
let titleLabel = UILabel() let titleLabel = UILabel()
titleLabel.textColor = Colors.text titleLabel.textColor = Colors.text
@ -65,15 +72,23 @@ final class DownloadAttachmentModal : Modal {
// MARK: Interaction // MARK: Interaction
@objc private func trust() { @objc private func trust() {
guard let message = viewItem.interaction as? TSIncomingMessage else { return } guard let message = viewItem.interaction as? TSIncomingMessage else { return }
let publicKey = message.authorId
let contact = Storage.shared.getContact(with: publicKey) ?? Contact(sessionID: publicKey) GRDBStorage.shared.writeAsync(
contact.isTrusted = true updates: { db in
try? Contact
.fetchOrCreate(db, id: message.authorId)
.with(isTrusted: true)
.save(db)
},
completion: { _, _ in
Storage.write(with: { transaction in Storage.write(with: { transaction in
Storage.shared.setContact(contact, using: transaction)
MessageInvalidator.invalidate(message, with: transaction) MessageInvalidator.invalidate(message, with: transaction)
}, completion: { }, completion: {
Storage.shared.resumeAttachmentDownloadJobsIfNeeded(for: message.uniqueThreadId) Storage.shared.resumeAttachmentDownloadJobsIfNeeded(for: message.uniqueThreadId)
}) })
}
)
presentingViewController?.dismiss(animated: true, completion: nil) presentingViewController?.dismiss(animated: true, completion: nil)
} }
} }

View file

@ -72,7 +72,9 @@ final class JoinOpenGroupModal : Modal {
Storage.shared.write { [presentingViewController = self.presentingViewController!] transaction in Storage.shared.write { [presentingViewController = self.presentingViewController!] transaction in
OpenGroupManagerV2.shared.add(room: room, server: server, publicKey: publicKey, using: transaction) OpenGroupManagerV2.shared.add(room: room, server: server, publicKey: publicKey, using: transaction)
.done(on: DispatchQueue.main) { _ in .done(on: DispatchQueue.main) { _ in
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...) GRDBStorage.shared.write { db in
MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
}
} }
.catch(on: DispatchQueue.main) { error in .catch(on: DispatchQueue.main) { error in
let alert = UIAlertController(title: "Couldn't Join", message: error.localizedDescription, preferredStyle: .alert) let alert = UIAlertController(title: "Couldn't Join", message: error.localizedDescription, preferredStyle: .alert)

View file

@ -1,5 +1,9 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
final class UserDetailsSheet : Sheet { import UIKit
import SessionMessagingKit
final class UserDetailsSheet: Sheet {
private let sessionID: String private let sessionID: String
init(for sessionID: String) { init(for sessionID: String) {
@ -26,7 +30,7 @@ final class UserDetailsSheet : Sheet {
profilePictureView.update() profilePictureView.update()
// Display name label // Display name label
let displayNameLabel = UILabel() let displayNameLabel = UILabel()
let displayName = Storage.shared.getContact(with: sessionID)?.displayName(for: .regular) ?? sessionID let displayName = Profile.displayName(for: sessionID)
displayNameLabel.text = displayName displayNameLabel.text = displayName
displayNameLabel.font = .boldSystemFont(ofSize: Values.largeFontSize) displayNameLabel.font = .boldSystemFont(ofSize: Values.largeFontSize)
displayNameLabel.textColor = Colors.text displayNameLabel.textColor = Colors.text

View file

@ -1,3 +1,8 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import Curve25519Kit
import SessionUtilitiesKit
final class NewDMVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate { final class NewDMVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate {
private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)

View file

@ -1,6 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit import UIKit
import GRDB
import SessionMessagingKit import SessionMessagingKit
import SessionUtilitiesKit import SessionUtilitiesKit
@ -155,8 +156,8 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
// Notifications // Notifications
let notificationCenter = NotificationCenter.default let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(handleYapDatabaseModifiedNotification(_:)), name: .YapDatabaseModified, object: OWSPrimaryStorage.shared().dbNotificationObject) notificationCenter.addObserver(self, selector: #selector(handleYapDatabaseModifiedNotification(_:)), name: .YapDatabaseModified, object: OWSPrimaryStorage.shared().dbNotificationObject)
notificationCenter.addObserver(self, selector: #selector(handleProfileDidChangeNotification(_:)), name: NSNotification.Name(rawValue: kNSNotificationName_OtherUsersProfileDidChange), object: nil) notificationCenter.addObserver(self, selector: #selector(handleProfileDidChangeNotification(_:)), name: Notification.Name.otherUsersProfileDidChange, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleLocalProfileDidChangeNotification(_:)), name: Notification.Name(kNSNotificationName_LocalProfileDidChange), object: nil) notificationCenter.addObserver(self, selector: #selector(handleLocalProfileDidChangeNotification(_:)), name: Notification.Name.localProfileDidChange, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleSeedViewedNotification(_:)), name: .seedViewed, object: nil) notificationCenter.addObserver(self, selector: #selector(handleSeedViewedNotification(_:)), name: .seedViewed, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleBlockedContactsUpdatedNotification(_:)), name: .blockedContactsUpdated, object: nil) notificationCenter.addObserver(self, selector: #selector(handleBlockedContactsUpdatedNotification(_:)), name: .blockedContactsUpdated, object: nil)
notificationCenter.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: .OWSApplicationDidBecomeActive, object: nil) notificationCenter.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: .OWSApplicationDidBecomeActive, object: nil)
@ -167,7 +168,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
self.threads.update(with: transaction) // Perform the initial update self.threads.update(with: transaction) // Perform the initial update
} }
// Start polling if needed (i.e. if the user just created or restored their Session ID) // Start polling if needed (i.e. if the user just created or restored their Session ID)
if Identity.fetchUserKeyPair() != nil { if Identity.userExists() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.startPollerIfNeeded() appDelegate.startPollerIfNeeded()
appDelegate.startClosedGroupPoller() appDelegate.startClosedGroupPoller()
@ -399,7 +400,9 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
} }
@objc private func handleLocalProfileDidChangeNotification(_ notification: Notification) { @objc private func handleLocalProfileDidChangeNotification(_ notification: Notification) {
updateNavBarButtons() DispatchQueue.main.async {
self.updateNavBarButtons()
}
} }
@objc private func handleSeedViewedNotification(_ notification: Notification) { @objc private func handleSeedViewedNotification(_ notification: Notification) {
@ -531,41 +534,49 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
let publicKey = thread.contactSessionID() let publicKey = thread.contactSessionID()
let block = UITableViewRowAction(style: .normal, title: NSLocalizedString("BLOCK_LIST_BLOCK_BUTTON", comment: "")) { _, _ in let block = UITableViewRowAction(style: .normal, title: NSLocalizedString("BLOCK_LIST_BLOCK_BUTTON", comment: "")) { _, _ in
Storage.shared.write( GRDBStorage.shared.writeAsync(
with: { transaction in updates: { db in
guard let transaction = transaction as? YapDatabaseReadWriteTransaction, let contact: Contact = Storage.shared.getContact(with: publicKey, using: transaction) else { try Contact
return .fetchOrCreate(db, id: publicKey)
} .with(isBlocked: true)
.save(db)
contact.isBlocked = true
Storage.shared.setContact(contact, using: transaction as Any)
}, },
completion: { completion: { db, result in
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() switch result {
case .success:
MessageSender.syncConfiguration(db, forceSyncNow: true)
.retainUntilComplete()
DispatchQueue.main.async { DispatchQueue.main.async {
tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade) tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade)
} }
default: break
}
} }
) )
} }
block.backgroundColor = Colors.unimportant block.backgroundColor = Colors.unimportant
let unblock = UITableViewRowAction(style: .normal, title: NSLocalizedString("BLOCK_LIST_UNBLOCK_BUTTON", comment: "")) { _, _ in let unblock = UITableViewRowAction(style: .normal, title: NSLocalizedString("BLOCK_LIST_UNBLOCK_BUTTON", comment: "")) { _, _ in
Storage.shared.write( GRDBStorage.shared.writeAsync(
with: { transaction in updates: { db in
guard let transaction = transaction as? YapDatabaseReadWriteTransaction, let contact: Contact = Storage.shared.getContact(with: publicKey, using: transaction) else { try Contact
return .fetchOrCreate(db, id: publicKey)
} .with(isBlocked: false)
.save(db)
contact.isBlocked = false
Storage.shared.setContact(contact, using: transaction as Any)
}, },
completion: { completion: { db, result in
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() switch result {
case .success:
MessageSender.syncConfiguration(db, forceSyncNow: true)
.retainUntilComplete()
DispatchQueue.main.async { DispatchQueue.main.async {
tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade) tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade)
} }
default: break
}
} }
) )
} }

View file

@ -1,6 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit import UIKit
import GRDB
import SessionUIKit import SessionUIKit
import SessionMessagingKit import SessionMessagingKit
@ -107,7 +108,7 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
self, self,
selector: #selector(handleProfileDidChangeNotification(_:)), selector: #selector(handleProfileDidChangeNotification(_:)),
name: NSNotification.Name(rawValue: kNSNotificationName_OtherUsersProfileDidChange), name: Notification.Name.otherUsersProfileDidChange,
object: nil object: nil
) )
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
@ -302,32 +303,6 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat
// MARK: - Interaction // MARK: - Interaction
private func updateContactAndThread(thread: TSThread, with transaction: YapDatabaseReadWriteTransaction, onComplete: ((Bool) -> ())? = nil) {
guard let contactThread: TSContactThread = thread as? TSContactThread else {
onComplete?(false)
return
}
var needsSync: Bool = false
// Update the contact
let sessionId: String = contactThread.contactSessionID()
if let contact: Contact = Storage.shared.getContact(with: sessionId), (contact.isApproved || !contact.isBlocked) {
contact.isApproved = false
contact.isBlocked = true
Storage.shared.setContact(contact, using: transaction)
needsSync = true
}
// Delete all thread content
thread.removeAllThreadInteractions(with: transaction)
thread.remove(with: transaction)
onComplete?(needsSync)
}
@objc private func clearAllTapped() { @objc private func clearAllTapped() {
let threadCount: Int = Int(messageRequestCount) let threadCount: Int = Int(messageRequestCount)
let threads: [TSThread] = (0..<threadCount).compactMap { self.thread(at: $0) } let threads: [TSThread] = (0..<threadCount).compactMap { self.thread(at: $0) }
@ -336,38 +311,49 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat
let alertVC: UIAlertController = UIAlertController(title: NSLocalizedString("MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE", comment: ""), message: nil, preferredStyle: .actionSheet) let alertVC: UIAlertController = UIAlertController(title: NSLocalizedString("MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_TITLE", comment: ""), message: nil, preferredStyle: .actionSheet)
alertVC.addAction(UIAlertAction(title: NSLocalizedString("MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON", comment: ""), style: .destructive) { _ in alertVC.addAction(UIAlertAction(title: NSLocalizedString("MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON", comment: ""), style: .destructive) { _ in
// Clear the requests // Clear the requests
GRDBStorage.shared.writeAsync(
updates: { db in
threads.forEach { thread in
guard
let sessionId: String = (thread as? TSContactThread)?.contactSessionID(),
let contact: Contact = try? Contact.fetchOrCreate(db, id: sessionId),
!contact.isBlocked
else { return }
try? contact
.with(
isApproved: false,
isBlocked: true
)
.save(db)
needsSync = true
}
},
completion: { db, _ in
Storage.write( Storage.write(
with: { [weak self] transaction in with: { transaction in
threads.forEach { thread in threads.forEach { thread in
if let uniqueId: String = thread.uniqueId { if let uniqueId: String = thread.uniqueId {
Storage.shared.cancelPendingMessageSendJobs(for: uniqueId, using: transaction) Storage.shared.cancelPendingMessageSendJobs(for: uniqueId, using: transaction)
} }
self?.updateContactAndThread(thread: thread, with: transaction) { threadNeedsSync in // Delete all thread content
if threadNeedsSync { thread.removeAllThreadInteractions(with: transaction)
needsSync = true thread.remove(with: transaction)
}
}
// Block the contact
if
let sessionId: String = (thread as? TSContactThread)?.contactSessionID(),
!thread.isBlocked(),
let contact: Contact = Storage.shared.getContact(with: sessionId, using: transaction)
{
contact.isBlocked = true
Storage.shared.setContact(contact, using: transaction)
needsSync = true
}
} }
}, },
completion: { completion: {
// Force a config sync // Force a config sync
if needsSync { if needsSync {
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() // TODO: This might crash due to a "wrong thread" error
MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete()
} }
} }
) )
}
)
}) })
alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel, handler: nil)) alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel, handler: nil))
self.present(alertVC, animated: true, completion: nil) self.present(alertVC, animated: true, completion: nil)
@ -378,24 +364,36 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat
let alertVC: UIAlertController = UIAlertController(title: NSLocalizedString("MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON", comment: ""), message: nil, preferredStyle: .actionSheet) let alertVC: UIAlertController = UIAlertController(title: NSLocalizedString("MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON", comment: ""), message: nil, preferredStyle: .actionSheet)
alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_DELETE_TITLE", comment: ""), style: .destructive) { _ in alertVC.addAction(UIAlertAction(title: NSLocalizedString("TXT_DELETE_TITLE", comment: ""), style: .destructive) { _ in
Storage.write( GRDBStorage.shared.writeAsync(
with: { [weak self] transaction in updates: { db in
Storage.shared.cancelPendingMessageSendJobs(for: uniqueId, using: transaction) guard
self?.updateContactAndThread(thread: thread, with: transaction)
// Block the contact
if
let sessionId: String = (thread as? TSContactThread)?.contactSessionID(), let sessionId: String = (thread as? TSContactThread)?.contactSessionID(),
!thread.isBlocked(), let contact: Contact = try? Contact.fetchOrCreate(db, id: sessionId),
let contact: Contact = Storage.shared.getContact(with: sessionId, using: transaction) !contact.isBlocked
{ else { return }
contact.isBlocked = true
Storage.shared.setContact(contact, using: transaction) try? contact
} .with(
isApproved: false,
isBlocked: true
)
.save(db)
},
completion: { db, _ in
Storage.write(
with: { transaction in
Storage.shared.cancelPendingMessageSendJobs(for: uniqueId, using: transaction)
// Delete all thread content
thread.removeAllThreadInteractions(with: transaction)
thread.remove(with: transaction)
}, },
completion: { completion: {
// Force a config sync // Force a config sync
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() // TODO: This might crash due to a "wrong thread" error
MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete()
}
)
} }
) )
}) })

View file

@ -680,9 +680,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
private func senderName(message: TSMessage) -> String { private func senderName(message: TSMessage) -> String {
switch message { switch message {
case let incomingMessage as TSIncomingMessage: case let incomingMessage as TSIncomingMessage:
let publicKey = incomingMessage.authorId return Profile.displayName(for: incomingMessage.authorId, thread: incomingMessage.thread)
let context = Contact.context(for: incomingMessage.thread)
return Storage.shared.getContact(with: publicKey)?.displayName(for: context) ?? publicKey
case is TSOutgoingMessage: case is TSOutgoingMessage:
return NSLocalizedString("MEDIA_GALLERY_SENDER_NAME_YOU", comment: "Short sender label for media sent by you") return NSLocalizedString("MEDIA_GALLERY_SENDER_NAME_YOU", comment: "Short sender label for media sent by you")
default: default:

View file

@ -56,11 +56,6 @@ static NSTimeInterval launchStartedAt;
#pragma mark - Dependencies #pragma mark - Dependencies
- (OWSProfileManager *)profileManager
{
return [OWSProfileManager sharedManager];
}
- (OWSReadReceiptManager *)readReceiptManager - (OWSReadReceiptManager *)readReceiptManager
{ {
return [OWSReadReceiptManager sharedManager]; return [OWSReadReceiptManager sharedManager];
@ -365,14 +360,10 @@ static NSTimeInterval launchStartedAt;
NSDate *now = [NSDate new]; NSDate *now = [NSDate new];
NSDate *lastProfilePictureUpload = (NSDate *)[userDefaults objectForKey:@"lastProfilePictureUpload"]; NSDate *lastProfilePictureUpload = (NSDate *)[userDefaults objectForKey:@"lastProfilePictureUpload"];
if (lastProfilePictureUpload != nil && [now timeIntervalSinceDate:lastProfilePictureUpload] > 14 * 24 * 60 * 60) { if (lastProfilePictureUpload != nil && [now timeIntervalSinceDate:lastProfilePictureUpload] > 14 * 24 * 60 * 60) {
OWSProfileManager *profileManager = OWSProfileManager.sharedManager; // The user defaults flag is updated in ProfileManager
NSString *name = [[LKStorage.shared getUser] name]; NSString *name = [SMKProfile fetchCurrentUserName];
UIImage *profilePicture = [profileManager profileAvatarForRecipientId:userPublicKey]; UIImage *profilePicture = [SMKProfileManager profileAvatarWithRecipientId:userPublicKey];
[profileManager updateLocalProfileName:name avatarImage:profilePicture success:^{ [SMKProfileManager updateLocalWithProfileName:name avatarImage:profilePicture requiresSync:YES];
// Do nothing; the user defaults flag is updated in LokiFileServerAPI
} failure:^(NSError *error) {
// Do nothing
} requiresSync:YES];
} }
if (CurrentAppContext().isMainApp) { if (CurrentAppContext().isMainApp) {

View file

@ -9,24 +9,26 @@ extension AppDelegate {
@objc(syncConfigurationIfNeeded) @objc(syncConfigurationIfNeeded)
func syncConfigurationIfNeeded() { func syncConfigurationIfNeeded() {
guard Storage.shared.getUser()?.name != nil else { return } let lastSync: Date = (UserDefaults.standard[.lastConfigurationSync] ?? .distantPast)
let userDefaults = UserDefaults.standard
let lastSync = userDefaults[.lastConfigurationSync] ?? .distantPast
guard Date().timeIntervalSince(lastSync) > 7 * 24 * 60 * 60 else { return } // Sync every 2 days
MessageSender.syncConfiguration(forceSyncNow: false) guard Date().timeIntervalSince(lastSync) > (7 * 24 * 60 * 60) else { return } // Sync every 2 days
GRDBStorage.shared.write { db in
MessageSender.syncConfiguration(db, forceSyncNow: false)
.done { .done {
// Only update the 'lastConfigurationSync' timestamp if we have done the first sync (Don't want // Only update the 'lastConfigurationSync' timestamp if we have done the
// a new device config sync to override config syncs from other devices) // first sync (Don't want a new device config sync to override config
if userDefaults[.hasSyncedInitialConfiguration] { // syncs from other devices)
userDefaults[.lastConfigurationSync] = Date() if UserDefaults.standard[.hasSyncedInitialConfiguration] {
UserDefaults.standard[.lastConfigurationSync] = Date()
} }
} }
.retainUntilComplete() .retainUntilComplete()
} }
}
@objc func startClosedGroupPoller() { @objc func startClosedGroupPoller() {
guard Identity.fetchUserKeyPair() != nil else { return } guard Identity.userExists() else { return }
ClosedGroupPoller.shared.start() ClosedGroupPoller.shared.start()
} }

View file

@ -197,8 +197,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
return return
} }
let context = Contact.context(for: thread) let senderName = Profile.displayName(for: incomingMessage.authorId, thread: incomingMessage.thread)
let senderName = Storage.shared.getContact(with: incomingMessage.authorId, using: transaction)?.displayName(for: context) ?? incomingMessage.authorId
let notificationTitle: String? let notificationTitle: String?
var notificationBody: String? var notificationBody: String?

View file

@ -131,10 +131,12 @@ final class DisplayNameVC : BaseVC {
guard !displayName.isEmpty else { guard !displayName.isEmpty else {
return showError(title: NSLocalizedString("vc_display_name_display_name_missing_error", comment: "")) return showError(title: NSLocalizedString("vc_display_name_display_name_missing_error", comment: ""))
} }
guard !OWSProfileManager.shared().isProfileNameTooLong(displayName) else { guard !ProfileManager.isToLong(profileName: displayName) else {
return showError(title: NSLocalizedString("vc_display_name_display_name_too_long_error", comment: "")) return showError(title: NSLocalizedString("vc_display_name_display_name_too_long_error", comment: ""))
} }
OWSProfileManager.shared().updateLocalProfileName(displayName, avatarImage: nil, success: { }, failure: { _ in }, requiresSync: false) // Try to save the user name but ignore the result
// Try to save the user name but ignore the result
ProfileManager.updateLocal(profileName: displayName, avatarImage: nil, requiredSync: false)
let pnModeVC = PNModeVC() let pnModeVC = PNModeVC()
navigationController!.pushViewController(pnModeVC, animated: true) navigationController!.pushViewController(pnModeVC, animated: true)
} }

View file

@ -2,7 +2,10 @@
import Foundation import Foundation
import Sodium import Sodium
import GRDB
import Curve25519Kit
import SessionUtilitiesKit import SessionUtilitiesKit
import SessionMessagingKit
enum Onboarding { enum Onboarding {
@ -14,11 +17,13 @@ enum Onboarding {
Identity.store(seed: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) Identity.store(seed: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair)
let x25519PublicKey = x25519KeyPair.hexEncodedPublicKey let x25519PublicKey = x25519KeyPair.hexEncodedPublicKey
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = x25519PublicKey TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = x25519PublicKey
Storage.writeSync { transaction in GRDBStorage.shared.write { db in
let user = Contact(sessionID: x25519PublicKey) try Contact(id: x25519PublicKey)
user.isApproved = true .with(
user.didApproveMe = true isApproved: true,
Storage.shared.setContact(user, using: transaction) didApproveMe: true
)
.save(db)
} }
switch self { switch self {

View file

@ -1,4 +1,8 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import Sodium import Sodium
import Curve25519Kit
final class RegisterVC : BaseVC { final class RegisterVC : BaseVC {
private var seed: Data! { didSet { updateKeyPair() } } private var seed: Data! { didSet { updateKeyPair() } }

View file

@ -10,7 +10,7 @@ final class SeedVC: BaseVC {
} }
// Legacy account // Legacy account
return Mnemonic.encode(hexEncodedString: Identity.fetchUserKeyPair()!.hexEncodedPrivateKey) return Mnemonic.encode(hexEncodedString: Identity.fetchUserPrivateKey()!.toHexString())
}() }()
private lazy var redactedMnemonic: String = { private lazy var redactedMnemonic: String = {

View file

@ -1,3 +1,4 @@
import SessionUtilitiesKit
final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate { final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate {
private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
@ -143,8 +144,11 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
Storage.shared.write { transaction in Storage.shared.write { transaction in
OpenGroupManagerV2.shared.add(room: room, server: server, publicKey: publicKey, using: transaction) OpenGroupManagerV2.shared.add(room: room, server: server, publicKey: publicKey, using: transaction)
.done(on: DispatchQueue.main) { [weak self] _ in .done(on: DispatchQueue.main) { [weak self] _ in
GRDBStorage.shared.write { db in
MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
}
self?.presentingViewController?.dismiss(animated: true, completion: nil) self?.presentingViewController?.dismiss(animated: true, completion: nil)
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
} }
.catch(on: DispatchQueue.main) { [weak self] error in .catch(on: DispatchQueue.main) { [weak self] error in
self?.dismiss(animated: true, completion: nil) // Dismiss the loader self?.dismiss(animated: true, completion: nil) // Dismiss the loader

View file

@ -124,12 +124,16 @@ final class NukeDataModal : Modal {
@objc private func clearDeviceOnly() { @objc private func clearDeviceOnly() {
ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false) { [weak self] _ in ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false) { [weak self] _ in
MessageSender.syncConfiguration(forceSyncNow: true).ensure(on: DispatchQueue.main) { GRDBStorage.shared.write { db in
MessageSender.syncConfiguration(db, forceSyncNow: true)
.ensure(on: DispatchQueue.main) {
self?.dismiss(animated: true, completion: nil) // Dismiss the loader self?.dismiss(animated: true, completion: nil) // Dismiss the loader
UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later
General.Cache.cachedEncodedPublicKey.mutate { $0 = nil } // Remove the cached key so it gets re-cached on next access General.Cache.cachedEncodedPublicKey.mutate { $0 = nil } // Remove the cached key so it gets re-cached on next access
NotificationCenter.default.post(name: .dataNukeRequested, object: nil) NotificationCenter.default.post(name: .dataNukeRequested, object: nil)
}.retainUntilComplete() }
.retainUntilComplete()
}
} }
} }

View file

@ -1,3 +1,8 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import Curve25519Kit
import SessionUtilitiesKit
final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate { final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate {
private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)

View file

@ -12,7 +12,7 @@ final class SeedModal: Modal {
} }
// Legacy account // Legacy account
return Mnemonic.encode(hexEncodedString: Identity.fetchUserKeyPair()!.hexEncodedPrivateKey) return Mnemonic.encode(hexEncodedString: Identity.fetchUserPrivateKey()!.toHexString())
}() }()
// MARK: Lifecycle // MARK: Lifecycle

View file

@ -147,7 +147,7 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
profilePictureView.publicKey = getUserHexEncodedPublicKey() profilePictureView.publicKey = getUserHexEncodedPublicKey()
profilePictureView.update() profilePictureView.update()
// Display name label // Display name label
displayNameLabel.text = Storage.shared.getUser()?.name displayNameLabel.text = Profile.fetchOrCreateCurrentUser().name
// Display name container // Display name container
let displayNameContainer = UIView() let displayNameContainer = UIView()
displayNameContainer.accessibilityLabel = "Edit display name text field" displayNameContainer.accessibilityLabel = "Edit display name text field"
@ -346,7 +346,7 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
} }
func avatarDidChange(_ image: UIImage) { func avatarDidChange(_ image: UIImage) {
let maxSize = Int(kOWSProfileManager_MaxAvatarDiameter) let maxSize = Int(ProfileManager.maxAvatarDiameter)
profilePictureToBeUploaded = image.resizedImage(toFillPixelSize: CGSize(width: maxSize, height: maxSize)) profilePictureToBeUploaded = image.resizedImage(toFillPixelSize: CGSize(width: maxSize, height: maxSize))
updateProfile(isUpdatingDisplayName: false, isUpdatingProfilePicture: true) updateProfile(isUpdatingDisplayName: false, isUpdatingProfilePicture: true)
} }
@ -358,17 +358,24 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
private func updateProfile(isUpdatingDisplayName: Bool, isUpdatingProfilePicture: Bool) { private func updateProfile(isUpdatingDisplayName: Bool, isUpdatingProfilePicture: Bool) {
let userDefaults = UserDefaults.standard let userDefaults = UserDefaults.standard
let name = displayNameToBeUploaded ?? Storage.shared.getUser()?.name let name: String? = (displayNameToBeUploaded ?? Profile.fetchOrCreateCurrentUser().name)
let profilePicture = profilePictureToBeUploaded ?? OWSProfileManager.shared().profileAvatar(forRecipientId: getUserHexEncodedPublicKey()) let profilePicture: UIImage? = (profilePictureToBeUploaded ?? ProfileManager.profileAvatar(for: getUserHexEncodedPublicKey()))
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self, displayNameToBeUploaded, profilePictureToBeUploaded] modalActivityIndicator in ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self, displayNameToBeUploaded, profilePictureToBeUploaded] modalActivityIndicator in
OWSProfileManager.shared().updateLocalProfileName(name, avatarImage: profilePicture, success: { ProfileManager.updateLocal(
profileName: (name ?? ""),
avatarImage: profilePicture,
requiredSync: true,
success: {
if displayNameToBeUploaded != nil { if displayNameToBeUploaded != nil {
userDefaults[.lastDisplayNameUpdate] = Date() userDefaults[.lastDisplayNameUpdate] = Date()
} }
if profilePictureToBeUploaded != nil { if profilePictureToBeUploaded != nil {
userDefaults[.lastProfilePictureUpdate] = Date() userDefaults[.lastProfilePictureUpdate] = Date()
} }
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() GRDBStorage.shared.write { db in
MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete()
}
DispatchQueue.main.async { DispatchQueue.main.async {
modalActivityIndicator.dismiss { modalActivityIndicator.dismiss {
guard let self = self else { return } guard let self = self else { return }
@ -378,13 +385,11 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
self.displayNameToBeUploaded = nil self.displayNameToBeUploaded = nil
} }
} }
}, failure: { error in },
failure: { error in
DispatchQueue.main.async { DispatchQueue.main.async {
modalActivityIndicator.dismiss { modalActivityIndicator.dismiss {
var isMaxFileSizeExceeded = false let isMaxFileSizeExceeded = (error == .avatarUploadMaxFileSizeExceeded)
if let error = error as? FileServerAPIV2.Error {
isMaxFileSizeExceeded = (error == .maxFileSizeExceeded)
}
let title = isMaxFileSizeExceeded ? "Maximum File Size Exceeded" : "Couldn't Update Profile" let title = isMaxFileSizeExceeded ? "Maximum File Size Exceeded" : "Couldn't Update Profile"
let message = isMaxFileSizeExceeded ? "Please select a smaller photo and try again" : "Please check your internet connection and try again" let message = isMaxFileSizeExceeded ? "Please select a smaller photo and try again" : "Please check your internet connection and try again"
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
@ -392,7 +397,8 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
self?.present(alert, animated: true, completion: nil) self?.present(alert, animated: true, completion: nil)
} }
} }
}, requiresSync: true) }
)
} }
} }
@ -461,7 +467,7 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
guard !displayName.isEmpty else { guard !displayName.isEmpty else {
return showError(title: NSLocalizedString("vc_settings_display_name_missing_error", comment: "")) return showError(title: NSLocalizedString("vc_settings_display_name_missing_error", comment: ""))
} }
guard !OWSProfileManager.shared().isProfileNameTooLong(displayName) else { guard !ProfileManager.isToLong(profileName: displayName) else {
return showError(title: NSLocalizedString("vc_settings_display_name_too_long_error", comment: "")) return showError(title: NSLocalizedString("vc_settings_display_name_too_long_error", comment: ""))
} }
isEditingDisplayName = false isEditingDisplayName = false

View file

@ -239,10 +239,9 @@ final class ConversationCell : UITableViewCell {
// Contact // Contact
if threadViewModel.isGroupThread, let thread = threadViewModel.threadRecord as? TSGroupThread { if threadViewModel.isGroupThread, let thread = threadViewModel.threadRecord as? TSGroupThread {
displayNameLabel.attributedText = getHighlightedSnippet(snippet: getDisplayName(), searchText: normalizedSearchText, fontSize: Values.mediumFontSize) displayNameLabel.attributedText = getHighlightedSnippet(snippet: getDisplayName(), searchText: normalizedSearchText, fontSize: Values.mediumFontSize)
let context: Contact.Context = thread.isOpenGroup ? .openGroup : .regular
var rawSnippet: String = "" var rawSnippet: String = ""
thread.groupModel.groupMemberIds.forEach{ id in thread.groupModel.groupMemberIds.forEach { id in
if let displayName = Storage.shared.getContact(with: id)?.displayName(for: context) { if let displayName = Profile.displayNameNoFallback(for: id, thread: thread) {
if !rawSnippet.isEmpty { if !rawSnippet.isEmpty {
rawSnippet += ", \(displayName)" rawSnippet += ", \(displayName)"
} }
@ -348,7 +347,7 @@ final class ConversationCell : UITableViewCell {
private func getMessageAuthorName(message: TSMessage) -> String? { private func getMessageAuthorName(message: TSMessage) -> String? {
guard threadViewModel.isGroupThread else { return nil } guard threadViewModel.isGroupThread else { return nil }
if let incomingMessage = message as? TSIncomingMessage { if let incomingMessage = message as? TSIncomingMessage {
return Storage.shared.getContact(with: incomingMessage.authorId)?.displayName(for: .regular) ?? "Anonymous" return Profile.displayName(for: incomingMessage.authorId, customFallback: "Anonymous")
} }
return nil return nil
} }
@ -356,14 +355,14 @@ final class ConversationCell : UITableViewCell {
private func getDisplayNameForSearch(_ sessionID: String) -> String { private func getDisplayNameForSearch(_ sessionID: String) -> String {
if threadViewModel.threadRecord.isNoteToSelf() { if threadViewModel.threadRecord.isNoteToSelf() {
return NSLocalizedString("NOTE_TO_SELF", comment: "") return NSLocalizedString("NOTE_TO_SELF", comment: "")
} else {
var result = sessionID
if let contact = Storage.shared.getContact(with: sessionID), let name = contact.name {
result = name
if let nickname = contact.nickname { result += "(\(nickname))"}
}
return result
} }
return [
Profile.displayName(for: sessionID),
Profile.fetchOrCreate(id: sessionID).nickname.map { "(\($0)" }
]
.compactMap { $0 }
.joined(separator: " ")
} }
private func getDisplayName() -> String { private func getDisplayName() -> String {
@ -381,9 +380,9 @@ final class ConversationCell : UITableViewCell {
} }
else { else {
let hexEncodedPublicKey: String = threadViewModel.contactSessionID! let hexEncodedPublicKey: String = threadViewModel.contactSessionID!
let displayName: String = (Storage.shared.getContact(with: hexEncodedPublicKey)?.displayName(for: .regular) ?? hexEncodedPublicKey)
let middleTruncatedHexKey: String = "\(hexEncodedPublicKey.prefix(4))...\(hexEncodedPublicKey.suffix(4))" let middleTruncatedHexKey: String = "\(hexEncodedPublicKey.prefix(4))...\(hexEncodedPublicKey.suffix(4))"
return (displayName == hexEncodedPublicKey ? middleTruncatedHexKey : displayName)
return Profile.displayName(for: hexEncodedPublicKey, customFallback: middleTruncatedHexKey)
} }
} }
} }

View file

@ -82,7 +82,7 @@ final class UserCell : UITableViewCell {
func update() { func update() {
profilePictureView.publicKey = publicKey profilePictureView.publicKey = publicKey
profilePictureView.update() profilePictureView.update()
displayNameLabel.text = Storage.shared.getContact(with: publicKey)?.displayName(for: .regular) ?? publicKey displayNameLabel.text = Profile.displayName(for: publicKey)
switch accessory { switch accessory {
case .none: accessoryImageView.isHidden = true case .none: accessoryImageView.isHidden = true

View file

@ -1,3 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SessionMessagingKit
@objc(SNUserSelectionVC) @objc(SNUserSelectionVC)
final class UserSelectionVC : BaseVC, UITableViewDataSource, UITableViewDelegate { final class UserSelectionVC : BaseVC, UITableViewDataSource, UITableViewDelegate {
@ -7,7 +11,7 @@ final class UserSelectionVC : BaseVC, UITableViewDataSource, UITableViewDelegate
private var selectedUsers: Set<String> = [] private var selectedUsers: Set<String> = []
private lazy var users: [String] = { private lazy var users: [String] = {
var result = ContactUtilities.getAllContacts() var result = Contact.fetchAllIds()
result.removeAll { usersToExclude.contains($0) } result.removeAll { usersToExclude.contains($0) }
return result return result
}() }()

View file

@ -15,10 +15,6 @@ public class AccountManager: NSObject {
// MARK: - Dependencies // MARK: - Dependencies
var profileManager: OWSProfileManager {
return OWSProfileManager.shared()
}
private var preferences: OWSPreferences { private var preferences: OWSPreferences {
return Environment.shared.preferences return Environment.shared.preferences
} }

View file

@ -17,9 +17,7 @@ public final class MentionUtilities : NSObject {
while let match = outerMatch { while let match = outerMatch {
let publicKey = String((string as NSString).substring(with: match.range).dropFirst()) // Drop the @ let publicKey = String((string as NSString).substring(with: match.range).dropFirst()) // Drop the @
let matchEnd: Int let matchEnd: Int
let context: Contact.Context = (openGroupV2 != nil) ? .openGroup : .regular if let displayName = Profile.displayNameNoFallback(for: publicKey, context: (openGroupV2 != nil ? .openGroup : .regular)) {
let displayName = Storage.shared.getContact(with: publicKey)?.displayName(for: context)
if let displayName = displayName {
string = (string as NSString).replacingCharacters(in: match.range, with: "@\(displayName)") string = (string as NSString).replacingCharacters(in: match.range, with: "@\(displayName)")
mentions.append((range: NSRange(location: match.range.location, length: displayName.utf16.count + 1), publicKey: publicKey)) // + 1 to include the @ mentions.append((range: NSRange(location: match.range.location, length: displayName.utf16.count + 1), publicKey: publicKey)) // + 1 to include the @
matchEnd = match.range.location + displayName.utf16.count matchEnd = match.range.location + displayName.utf16.count

View file

@ -2,6 +2,7 @@
import Foundation import Foundation
import Sodium import Sodium
import Curve25519Kit
import SessionMessagingKit import SessionMessagingKit
enum MockDataGenerator { enum MockDataGenerator {
@ -146,7 +147,7 @@ enum MockDataGenerator {
!isMessageRequest && !isMessageRequest &&
(((0..<10).randomElement(using: &dmThreadRandomGenerator) ?? 0) < 8) // 80% approved the current user (((0..<10).randomElement(using: &dmThreadRandomGenerator) ?? 0) < 8) // 80% approved the current user
) )
Storage.shared.setContact(contact, using: transaction) transaction.setObject(contact, forKey: contact.sessionID, inCollection: Legacy.contactCollection)
// Generate the message history (Note: Unapproved message requests will only include incoming messages) // Generate the message history (Note: Unapproved message requests will only include incoming messages)
logProgress("DM Thread \(threadIndex)", "Generate \(numMessages) Messages") logProgress("DM Thread \(threadIndex)", "Generate \(numMessages) Messages")
@ -209,7 +210,7 @@ enum MockDataGenerator {
contact.name = (0..<contactNameLength) contact.name = (0..<contactNameLength)
.compactMap { _ in stringContent.randomElement(using: &cgThreadRandomGenerator) } .compactMap { _ in stringContent.randomElement(using: &cgThreadRandomGenerator) }
.joined() .joined()
Storage.shared.setContact(contact, using: transaction) transaction.setObject(contact, forKey: contact.sessionID, inCollection: Legacy.contactCollection)
members.append(randomSessionId) members.append(randomSessionId)
} }
@ -229,8 +230,12 @@ enum MockDataGenerator {
// Add the group to the user's set of public keys to poll for and store the key pair // Add the group to the user's set of public keys to poll for and store the key pair
let encryptionKeyPair = Curve25519.generateKeyPair() let encryptionKeyPair = Curve25519.generateKeyPair()
let keyPair: Box.KeyPair = Box.KeyPair(
publicKey: encryptionKeyPair.publicKey.bytes,
secretKey: encryptionKeyPair.privateKey.bytes
)
Storage.shared.addClosedGroupPublicKey(randomGroupPublicKey, using: transaction) Storage.shared.addClosedGroupPublicKey(randomGroupPublicKey, using: transaction)
Storage.shared.addClosedGroupEncryptionKeyPair(encryptionKeyPair, for: randomGroupPublicKey, using: transaction) Storage.shared.addClosedGroupEncryptionKeyPair(keyPair, for: randomGroupPublicKey, using: transaction)
// Generate the message history (Note: Unapproved message requests will only include incoming messages) // Generate the message history (Note: Unapproved message requests will only include incoming messages)
logProgress("Closed Group Thread \(threadIndex)", "Generate \(numMessages) Messages") logProgress("Closed Group Thread \(threadIndex)", "Generate \(numMessages) Messages")
@ -291,7 +296,7 @@ enum MockDataGenerator {
contact.name = (0..<contactNameLength) contact.name = (0..<contactNameLength)
.compactMap { _ in stringContent.randomElement(using: &ogThreadRandomGenerator) } .compactMap { _ in stringContent.randomElement(using: &ogThreadRandomGenerator) }
.joined() .joined()
Storage.shared.setContact(contact, using: transaction) transaction.setObject(contact, forKey: contact.sessionID, inCollection: Legacy.contactCollection)
members.append(randomSessionId) members.append(randomSessionId)
} }

View file

@ -1,17 +1,29 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import Mantle
import YapDatabase
import SignalCoreKit
public enum Legacy { public enum Legacy {
// MARK: - Collections and Keys // MARK: - Collections and Keys
internal static let contactThreadPrefix = "c" internal static let contactThreadPrefix = "c"
internal static let groupThreadPrefix = "g"
internal static let closedGroupIdPrefix = "__textsecure_group__!"
internal static let closedGroupKeyPairPrefix = "SNClosedGroupEncryptionKeyPairCollection-"
public static let contactCollection = "LokiContactCollection"
internal static let threadCollection = "TSThread" internal static let threadCollection = "TSThread"
internal static let contactCollection = "LokiContactCollection" internal static let disappearingMessagesCollection = "OWSDisappearingMessagesConfiguration"
internal static let closedGroupPublicKeyCollection = "SNClosedGroupPublicKeyCollection"
internal static let closedGroupFormationTimestampCollection = "SNClosedGroupFormationTimestampCollection"
internal static let closedGroupZombieMembersCollection = "SNClosedGroupZombieMembersCollection"
// MARK: - Types // MARK: - Types
public typealias Contact = _LegacyContact public typealias Contact = _LegacyContact
public typealias DisappearingMessagesConfiguration = _LegacyDisappearingMessagesConfiguration
@objc(SNProfile) @objc(SNProfile)
public class Profile: NSObject, NSCoding { public class Profile: NSObject, NSCoding {

View file

@ -39,5 +39,56 @@ enum _001_InitialSetupMigration: Migration {
t.column(.profilePictureFileName, .text) t.column(.profilePictureFileName, .text)
t.column(.profileEncryptionKey, .blob) t.column(.profileEncryptionKey, .blob)
} }
try db.create(table: SessionThread.self) { t in
t.column(.id, .text)
.notNull()
.primaryKey()
t.column(.variant, .integer).notNull()
t.column(.creationDateTimestamp, .double).notNull()
t.column(.shouldBeVisible, .boolean).notNull()
t.column(.isPinned, .boolean).notNull()
t.column(.messageDraft, .text)
t.column(.notificationMode, .integer).notNull()
t.column(.mutedUntilTimestamp, .double)
}
try db.create(table: DisappearingMessagesConfiguration.self) { t in
t.column(.id, .text)
.notNull()
.primaryKey()
.references(SessionThread.self)
t.column(.isEnabled, .boolean)
.defaults(to: false)
.notNull()
t.column(.durationSeconds, .double)
.defaults(to: 0)
.notNull()
}
try db.create(table: ClosedGroup.self) { t in
t.column(.publicKey, .text)
.notNull()
.primaryKey()
t.column(.name, .text).notNull()
t.column(.formationTimestamp, .double).notNull()
}
try db.create(table: ClosedGroupKeyPair.self) { t in
t.column(.publicKey, .text)
.notNull()
.indexed()
.references(ClosedGroup.self)
t.column(.secretKey, .blob).notNull()
t.column(.receivedTimestamp, .double).notNull()
}
try db.create(table: GroupMember.self) { t in
t.column(.groupId, .text)
.notNull()
.indexed()
t.column(.profileId, .text).notNull()
t.column(.role, .integer).notNull()
}
} }
} }

View file

@ -2,6 +2,7 @@
import Foundation import Foundation
import GRDB import GRDB
import Curve25519Kit
import SessionUtilitiesKit import SessionUtilitiesKit
enum _002_YDBToGRDBMigration: Migration { enum _002_YDBToGRDBMigration: Migration {
@ -9,10 +10,19 @@ enum _002_YDBToGRDBMigration: Migration {
// TODO: Autorelease pool???. // TODO: Autorelease pool???.
static func migrate(_ db: Database) throws { static func migrate(_ db: Database) throws {
// MARK: - Contacts // MARK: - Contacts & Threads
var shouldFailMigration: Bool = false
var contacts: Set<Legacy.Contact> = [] var contacts: Set<Legacy.Contact> = []
var contactThreadIds: Set<String> = [] var contactThreadIds: Set<String> = []
var threads: Set<TSThread> = []
var disappearingMessagesConfiguration: [String: Legacy.DisappearingMessagesConfiguration] = [:]
var closedGroupKeys: [String: (timestamp: TimeInterval, keys: SessionUtilitiesKit.Legacy.KeyPair)] = [:]
var closedGroupName: [String: String] = [:]
var closedGroupFormation: [String: UInt64] = [:]
var closedGroupModel: [String: TSGroupModel] = [:]
var closedGroupZombieMemberIds: [String: Set<String>] = [:]
var openGroupInfo: [String: OpenGroupV2] = [:]
Storage.read { transaction in Storage.read { transaction in
// Process the Contacts // Process the Contacts
@ -21,24 +31,105 @@ enum _002_YDBToGRDBMigration: Migration {
contacts.insert(contact) contacts.insert(contact)
} }
// Process the contact threads (only want to create "real" contacts in the new structure) let userClosedGroupPublicKeys: [String] = transaction.allKeys(inCollection: Legacy.closedGroupPublicKeyCollection)
transaction.enumerateKeys(inCollection: Legacy.threadCollection) { key, _ in
guard key.starts(with: Legacy.contactThreadPrefix) else { return } // Process the threads
transaction.enumerateKeysAndObjects(inCollection: Legacy.threadCollection) { key, object, _ in
guard let thread: TSThread = object as? TSThread else { return }
guard let threadId: String = thread.uniqueId else { return }
threads.insert(thread)
// Want to exclude threads which aren't visible (ie. threads which we started
// but the user never ended up sending a message)
if key.starts(with: Legacy.contactThreadPrefix) && thread.shouldBeVisible {
contactThreadIds.insert(key) contactThreadIds.insert(key)
} }
// Get the disappearing messages config
disappearingMessagesConfiguration[threadId] = transaction
.object(forKey: threadId, inCollection: Legacy.disappearingMessagesCollection)
.asType(Legacy.DisappearingMessagesConfiguration.self)
.defaulting(to: Legacy.DisappearingMessagesConfiguration.defaultWith(threadId))
// Process group-specific info
guard let groupThread: TSGroupThread = thread as? TSGroupThread else { return }
if groupThread.isClosedGroup {
// The old threadId for closed groups was in the below format, we don't
// really need the unnecessary complexity so process the key and extract
// the publicKey from it
// `g{base64String(Data(__textsecure_group__!{publicKey}))}
let base64GroupId: String = String(threadId.suffix(from: threadId.index(after: threadId.startIndex)))
guard
let groupIdData: Data = Data(base64Encoded: base64GroupId),
let groupId: String = String(data: groupIdData, encoding: .utf8),
let publicKey: String = groupId.split(separator: "!").last.map({ String($0) }),
let formationTimestamp: UInt64 = transaction.object(forKey: publicKey, inCollection: Legacy.closedGroupFormationTimestampCollection) as? UInt64
else {
SNLog("Unable to decode Closed Group during migration")
shouldFailMigration = true
return
}
guard userClosedGroupPublicKeys.contains(publicKey) else {
SNLog("Found unexpected invalid closed group public key during migration")
shouldFailMigration = true
return
}
let keyCollection: String = "\(Legacy.closedGroupKeyPairPrefix)\(publicKey)"
closedGroupName[threadId] = groupThread.name(with: transaction)
closedGroupModel[threadId] = groupThread.groupModel
closedGroupFormation[threadId] = formationTimestamp
closedGroupZombieMemberIds[threadId] = transaction.object(
forKey: publicKey,
inCollection: Legacy.closedGroupZombieMembersCollection
) as? Set<String>
transaction.enumerateKeysAndObjects(inCollection: keyCollection) { key, object, _ in
guard let timestamp: TimeInterval = TimeInterval(key), let keyPair: SessionUtilitiesKit.Legacy.KeyPair = object as? SessionUtilitiesKit.Legacy.KeyPair else {
return
}
closedGroupKeys[threadId] = (timestamp, keyPair)
}
}
else if groupThread.isOpenGroup {
}
}
} }
// We can't properly throw within the 'enumerateKeysAndObjects' block so have to throw here
guard !shouldFailMigration else { throw GRDBStorageError.migrationFailed }
// Insert the data into GRDB // Insert the data into GRDB
// MARK: - Insert Contacts
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) let currentUserPublicKey: String = getUserHexEncodedPublicKey(db)
try contacts.forEach { contact in try contacts.forEach { contact in
let isCurrentUser: Bool = (contact.sessionID == currentUserPublicKey) let isCurrentUser: Bool = (contact.sessionID == currentUserPublicKey)
let contactThreadId: String = TSContactThread.threadID(fromContactSessionID: contact.sessionID) let contactThreadId: String = TSContactThread.threadID(fromContactSessionID: contact.sessionID)
// Determine if this contact is a "real" contact // Create the "Profile" for the legacy contact
try Profile(
id: contact.sessionID,
name: (contact.name ?? contact.sessionID),
nickname: contact.nickname,
profilePictureUrl: contact.profilePictureURL,
profilePictureFileName: contact.profilePictureFileName,
profileEncryptionKey: contact.profileEncryptionKey
).insert(db)
// Determine if this contact is a "real" contact (don't want to create contacts for
// every user in the new structure but still want profiles for every user)
if if
// TODO: Thread.shouldBeVisible???
isCurrentUser || isCurrentUser ||
contactThreadIds.contains(contactThreadId) || contactThreadIds.contains(contactThreadId) ||
contact.isApproved || contact.isApproved ||
@ -56,16 +147,110 @@ enum _002_YDBToGRDBMigration: Migration {
hasBeenBlocked: (!isCurrentUser && (contact.hasBeenBlocked || contact.isBlocked)) hasBeenBlocked: (!isCurrentUser && (contact.hasBeenBlocked || contact.isBlocked))
).insert(db) ).insert(db)
} }
}
// Create the "Profile" for the legacy contact // MARK: - Insert Threads
try Profile(
id: contact.sessionID, try threads.forEach { thread in
name: (contact.name ?? contact.sessionID), guard let legacyThreadId: String = thread.uniqueId else { return }
nickname: contact.nickname,
profilePictureUrl: contact.profilePictureURL, let id: String
profilePictureFileName: contact.profilePictureFileName, let variant: SessionThread.Variant
profileEncryptionKey: contact.profileEncryptionKey let notificationMode: SessionThread.NotificationMode
switch thread {
case let groupThread as TSGroupThread:
if groupThread.isOpenGroup {
id = legacyThreadId//openGroup.id
variant = .openGroup
}
else {
guard let publicKey: Data = closedGroupKeys[legacyThreadId]?.keys.publicKey else {
throw GRDBStorageError.migrationFailed
}
id = publicKey.toHexString()
variant = .closedGroup
}
notificationMode = (thread.isMuted ? .none :
(groupThread.isOnlyNotifyingForMentions ?
.mentionsOnly :
.all
)
)
default:
id = legacyThreadId.substring(from: Legacy.contactThreadPrefix.count)
variant = .contact
notificationMode = (thread.isMuted ? .none : .all)
}
try SessionThread(
id: id,
variant: variant,
creationDateTimestamp: thread.creationDate.timeIntervalSince1970,
shouldBeVisible: thread.shouldBeVisible,
isPinned: thread.isPinned,
messageDraft: thread.messageDraft,
notificationMode: notificationMode,
mutedUntilTimestamp: thread.mutedUntilDate?.timeIntervalSince1970
).insert(db)
// Disappearing Messages Configuration
if let config: Legacy.DisappearingMessagesConfiguration = disappearingMessagesConfiguration[id] {
try DisappearingMessagesConfiguration(
id: id,
isEnabled: config.isEnabled,
durationSeconds: TimeInterval(config.durationSeconds)
).insert(db)
}
// Closed Groups
if (thread as? TSGroupThread)?.isClosedGroup == true {
guard
let keyInfo = closedGroupKeys[legacyThreadId],
let name: String = closedGroupName[legacyThreadId],
let groupModel: TSGroupModel = closedGroupModel[legacyThreadId],
let formationTimestamp: UInt64 = closedGroupFormation[legacyThreadId]
else { throw GRDBStorageError.migrationFailed }
try ClosedGroup(
publicKey: keyInfo.keys.publicKey.toHexString(),
name: name,
formationTimestamp: TimeInterval(formationTimestamp)
).insert(db)
try ClosedGroupKeyPair(
publicKey: keyInfo.keys.publicKey.toHexString(),
secretKey: keyInfo.keys.privateKey,
receivedTimestamp: keyInfo.timestamp
).insert(db)
try groupModel.groupMemberIds.forEach { memberId in
try GroupMember(
groupId: id,
profileId: memberId,
role: .standard
).insert(db)
}
try groupModel.groupAdminIds.forEach { adminId in
try GroupMember(
groupId: id,
profileId: adminId,
role: .admin
).insert(db)
}
try (closedGroupZombieMemberIds[legacyThreadId] ?? []).forEach { zombieId in
try GroupMember(
groupId: id,
profileId: zombieId,
role: .zombie
).insert(db) ).insert(db)
} }
} }
}
}
} }

View file

@ -0,0 +1,22 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
public struct Capability: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "capability" }
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression {
case server
case room
case capability
case isMissing
}
public let server: String
public let room: String
public let capability: String
public let isMissing: Bool
}

View file

@ -0,0 +1,48 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
public struct ClosedGroup: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "closedGroup" }
static let keyPairs = hasMany(ClosedGroupKeyPair.self)
static let members = hasMany(GroupMember.self)
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression {
case publicKey
case name
case formationTimestamp
}
public var id: String { publicKey }
public let publicKey: String
public let name: String
public let formationTimestamp: TimeInterval
public var keyPairs: QueryInterfaceRequest<ClosedGroupKeyPair> {
request(for: ClosedGroup.keyPairs)
}
public var memberIds: QueryInterfaceRequest<GroupMember> {
request(for: ClosedGroup.members)
.filter(GroupMember.Columns.role == GroupMember.Role.standard)
}
public var zombieIds: QueryInterfaceRequest<GroupMember> {
request(for: ClosedGroup.members)
.filter(GroupMember.Columns.role == GroupMember.Role.zombie)
}
public var moderatorIds: QueryInterfaceRequest<GroupMember> {
request(for: ClosedGroup.members)
.filter(GroupMember.Columns.role == GroupMember.Role.moderator)
}
public var adminIds: QueryInterfaceRequest<GroupMember> {
request(for: ClosedGroup.members)
.filter(GroupMember.Columns.role == GroupMember.Role.admin)
}
}

View file

@ -0,0 +1,22 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
public struct ClosedGroupKeyPair: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "closedGroupKeyPair" }
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression {
case publicKey
case secretKey
case receivedTimestamp
}
public var id: String { publicKey }
public let publicKey: String
public let secretKey: Data
public let receivedTimestamp: TimeInterval
}

View file

@ -4,7 +4,7 @@ import Foundation
import GRDB import GRDB
import SessionUtilitiesKit import SessionUtilitiesKit
public struct Contact: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public struct Contact: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "contact" } public static var databaseTableName: String { "contact" }
public typealias Columns = CodingKeys public typealias Columns = CodingKeys
@ -22,23 +22,129 @@ public struct Contact: Codable, FetchableRecord, PersistableRecord, TableRecord,
public let id: String public let id: String
/// This flag is used to determine whether we should auto-download files sent by this contact. /// This flag is used to determine whether we should auto-download files sent by this contact.
public var isTrusted = false public let isTrusted: Bool
/// This flag is used to determine whether message requests from this contact are approved /// This flag is used to determine whether message requests from this contact are approved
public var isApproved = false public let isApproved: Bool
/// This flag is used to determine whether message requests from this contact are blocked /// This flag is used to determine whether message requests from this contact are blocked
public var isBlocked = false { public let isBlocked: Bool
didSet {
if isBlocked {
hasBeenBlocked = true
}
}
}
/// This flag is used to determine whether this contact has approved the current users message request /// This flag is used to determine whether this contact has approved the current users message request
public var didApproveMe = false public let didApproveMe: Bool
/// This flag is used to determine whether this contact has ever been blocked (will be included in the config message if so) /// This flag is used to determine whether this contact has ever been blocked (will be included in the config message if so)
public var hasBeenBlocked = false public let hasBeenBlocked: Bool
// MARK: - Initialization
public init(
id: String,
isTrusted: Bool = false,
isApproved: Bool = false,
isBlocked: Bool = false,
didApproveMe: Bool = false,
hasBeenBlocked: Bool = false
) {
self.id = id
self.isTrusted = (
isTrusted ||
id == getUserHexEncodedPublicKey() // Always trust ourselves
)
self.isApproved = isApproved
self.isBlocked = isBlocked
self.didApproveMe = didApproveMe
self.hasBeenBlocked = (isBlocked || hasBeenBlocked)
}
// MARK: - PersistableRecord
public func save(_ db: Database) throws {
let oldContact: Contact? = try? Contact.fetchOne(db, id: id)
try performSave(db)
db.afterNextTransactionCommit { db in
if isBlocked != oldContact?.isBlocked {
NotificationCenter.default.post(name: .contactBlockedStateChanged, object: id)
}
}
}
} }
// MARK: - Convenience
public extension Contact {
func with(
isTrusted: Updatable<Bool> = .existing,
isApproved: Updatable<Bool> = .existing,
isBlocked: Updatable<Bool> = .existing,
didApproveMe: Updatable<Bool> = .existing
) -> Contact {
return Contact(
id: id,
isTrusted: (
(isTrusted ?? self.isTrusted) ||
self.id == getUserHexEncodedPublicKey() // Always trust ourselves
),
isApproved: (isApproved ?? self.isApproved),
isBlocked: (isBlocked ?? self.isBlocked),
didApproveMe: (didApproveMe ?? self.didApproveMe),
hasBeenBlocked: ((isBlocked ?? self.isBlocked) || self.hasBeenBlocked)
)
}
}
// MARK: - GRDB Interactions
public extension Contact {
static func fetchOrCreate(_ db: Database, id: ID) -> Contact {
return ((try? fetchOne(db, id: id)) ?? Contact(id: id))
}
static func fetchAllIds() -> [String] {
return GRDBStorage.shared
.read { db in
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let contacts: [Contact] = try Contact
.filter(Contact.Columns.id != userPublicKey)
.filter(Contact.Columns.didApproveMe == true)
.fetchAll(db)
let profiles: [Profile] = try Profile
.fetchAll(db, ids: contacts.map { $0.id })
// Sort the contacts by their displayName value
return profiles
.sorted(by: { lhs, rhs -> Bool in lhs.displayName() < rhs.displayName() })
.map { $0.id }
}
.defaulting(to: [])
}
}
// MARK: - Objective-C Support
@objc(SMKContact)
public class SMKContact: NSObject {
@objc let isApproved: Bool
@objc let isBlocked: Bool
@objc let didApproveMe: Bool
init(isApproved: Bool, isBlocked: Bool, didApproveMe: Bool) {
self.isApproved = isApproved
self.isBlocked = isBlocked
self.didApproveMe = didApproveMe
}
@objc public static func fetchOrCreate(id: String) -> SMKContact {
let existingContact: Contact? = GRDBStorage.shared.read { db in
try Contact.fetchOne(db, id: id)
}
return SMKContact(
isApproved: existingContact?.isApproved ?? false,
isBlocked: existingContact?.isBlocked ?? false,
didApproveMe: existingContact?.didApproveMe ?? false
)
}
}

View file

@ -0,0 +1,64 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
public struct DisappearingMessagesConfiguration: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "disappearingMessagesConfiguration" }
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression {
case id
case isEnabled
case durationSeconds
}
public let id: String
public let isEnabled: Bool
public let durationSeconds: TimeInterval
}
// MARK: - Convenience
extension DisappearingMessagesConfiguration {
public var durationIndex: Int {
return DisappearingMessagesConfiguration.validDurationsSeconds
.firstIndex(of: durationSeconds)
.defaulting(to: 0)
}
public var durationString: String {
NSString.formatDurationSeconds(UInt32(durationSeconds), useShortFormat: false)
}
}
// MARK: - UI Constraints
extension DisappearingMessagesConfiguration {
public static var validDurationsSeconds: [TimeInterval] {
return [
5,
10,
30,
(1 * 60),
(5 * 60),
(30 * 60),
(1 * 60 * 60),
(6 * 60 * 60),
(12 * 60 * 60),
(24 * 60 * 60),
(7 * 24 * 60 * 60)
]
}
public static var maxDurationSeconds: TimeInterval = {
return (validDurationsSeconds.max() ?? 0)
}()
}
// MARK: - Objective-C Support
@objc(SMKDisappearingMessagesConfiguration)
public class SMKDisappearingMessagesConfiguration: NSObject {
@objc public static var maxDurationSeconds: UInt = UInt(DisappearingMessagesConfiguration.maxDurationSeconds)
}

View file

@ -0,0 +1,27 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
public struct GroupMember: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "groupMember" }
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression {
case groupId
case profileId
case role
}
public enum Role: Int, Codable, DatabaseValueConvertible {
case standard
case zombie
case moderator
case admin
}
public let groupId: String
public let profileId: String
public let role: Role
}

View file

@ -0,0 +1,50 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
public struct OpenGroup: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "openGroup" }
static let capabilities = hasMany(Capability.self)
static let members = hasMany(GroupMember.self)
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression {
case server
case room
case publicKey
case name
case groupDescription = "description"
case imageId
case imageData
case userCount
case infoUpdates
}
public var id: String { "\(server).\(room)" }
public let server: String
public let room: String
public let publicKey: String
public let name: String
public let groupDescription: String?
public let imageId: Int?
public let imageData: Data?
public let userCount: Int
public let infoUpdates: Int
public var capabilities: QueryInterfaceRequest<Capability> {
request(for: OpenGroup.capabilities)
}
public var moderatorIds: QueryInterfaceRequest<GroupMember> {
request(for: OpenGroup.members)
.filter(GroupMember.Columns.role == GroupMember.Role.moderator)
}
public var adminIds: QueryInterfaceRequest<GroupMember> {
request(for: OpenGroup.members)
.filter(GroupMember.Columns.role == GroupMember.Role.admin)
}
}

View file

@ -2,9 +2,10 @@
import Foundation import Foundation
import GRDB import GRDB
import SignalCoreKit
import SessionUtilitiesKit import SessionUtilitiesKit
public struct Profile: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible, CustomStringConvertible { public struct Profile: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible, CustomStringConvertible {
public static var databaseTableName: String { "profile" } public static var databaseTableName: String { "profile" }
public typealias Columns = CodingKeys public typealias Columns = CodingKeys
@ -23,19 +24,19 @@ public struct Profile: Codable, FetchableRecord, PersistableRecord, TableRecord,
public let id: String public let id: String
/// The name of the contact. Use this whenever you need the "real", underlying name of a user (e.g. when sending a message). /// The name of the contact. Use this whenever you need the "real", underlying name of a user (e.g. when sending a message).
public var name: String public let name: String
/// A custom name for the profile set by the current user /// A custom name for the profile set by the current user
public var nickname: String? public let nickname: String?
/// The URL from which to fetch the contact's profile picture. /// The URL from which to fetch the contact's profile picture.
public var profilePictureUrl: String? public let profilePictureUrl: String?
/// The file name of the contact's profile picture on local storage. /// The file name of the contact's profile picture on local storage.
public var profilePictureFileName: String? public let profilePictureFileName: String?
/// The key with which the profile is encrypted. /// The key with which the profile is encrypted.
public var profileEncryptionKey: OWSAES256Key? public let profileEncryptionKey: OWSAES256Key?
// MARK: - Description // MARK: - Description
@ -48,6 +49,33 @@ public struct Profile: Codable, FetchableRecord, PersistableRecord, TableRecord,
) )
""" """
} }
// MARK: - PersistableRecord
public func save(_ db: Database) throws {
let oldProfile: Profile? = try? Profile.fetchOne(db, id: id)
try performSave(db)
db.afterNextTransactionCommit { db in
// Delete old profile picture if needed
if let oldProfilePictureFileName: String = oldProfile?.profilePictureFileName, oldProfilePictureFileName != profilePictureFileName {
let path: String = OWSUserProfile.profileAvatarFilepath(withFilename: oldProfilePictureFileName)
DispatchQueue.global(qos: .default).async {
OWSFileSystem.deleteFileIfExists(path)
}
}
NotificationCenter.default.post(name: .profileUpdated, object: id)
if id == getUserHexEncodedPublicKey(db) {
NotificationCenter.default.post(name: .localProfileDidChange, object: nil)
}
else {
let userInfo = [ Notification.Key.profileRecipientId.rawValue: id ]
NotificationCenter.default.post(name: .otherUsersProfileDidChange, object: nil, userInfo: userInfo)
}
}
}
} }
// MARK: - Codable // MARK: - Codable
@ -149,15 +177,32 @@ public extension Profile {
// MARK: - Convenience // MARK: - Convenience
public extension Profile { public extension Profile {
func with(
name: String? = nil,
nickname: Updatable<String> = .existing,
profilePictureUrl: Updatable<String> = .existing,
profilePictureFileName: Updatable<String> = .existing,
profileEncryptionKey: Updatable<OWSAES256Key> = .existing
) -> Profile {
return Profile(
id: id,
name: (name ?? self.name),
nickname: (nickname ?? self.nickname),
profilePictureUrl: (profilePictureUrl ?? self.profilePictureUrl),
profilePictureFileName: (profilePictureFileName ?? self.profilePictureFileName),
profileEncryptionKey: (profileEncryptionKey ?? self.profileEncryptionKey)
)
}
// MARK: - Context // MARK: - Context
enum Context: Int { @objc enum Context: Int {
case regular case regular
case openGroup case openGroup
} }
/// The name to display in the UI. For local use only. /// The name to display in the UI. For local use only.
func displayName(for context: Context) -> String? { func displayName(for context: Context = .regular) -> String {
if let nickname: String = nickname { return nickname } if let nickname: String = nickname { return nickname }
switch context { switch context {
@ -172,3 +217,150 @@ public extension Profile {
} }
} }
} }
// MARK: - GRDB Interactions
public extension Profile {
static func displayName(for id: ID, thread: TSThread, customFallback: String? = nil) -> String {
return displayName(
for: id,
context: ((thread as? TSGroupThread)?.isOpenGroup == true ? .openGroup : .regular),
customFallback: customFallback
)
}
static func displayName(for id: ID, context: Context = .regular, customFallback: String? = nil) -> String {
let existingDisplayName: String? = GRDBStorage.shared
.read { db in try Profile.fetchOne(db, id: id) }?
.displayName(for: context)
return (existingDisplayName ?? (customFallback ?? id))
}
static func displayNameNoFallback(for id: ID, thread: TSThread) -> String? {
return displayName(
for: id,
context: ((thread as? TSGroupThread)?.isOpenGroup == true ? .openGroup : .regular)
)
}
static func displayNameNoFallback(for id: ID, context: Context = .regular) -> String? {
return GRDBStorage.shared
.read { db in try Profile.fetchOne(db, id: id) }?
.displayName(for: context)
}
// MARK: - Fetch or Create
private static func defaultFor(_ id: String) -> Profile {
return Profile(
id: id,
name: id,
nickname: nil,
profilePictureUrl: nil,
profilePictureFileName: nil,
profileEncryptionKey: nil
)
}
static func fetchOrCreateCurrentUser() -> Profile {
var userPublicKey: String = ""
let exisingProfile: Profile? = GRDBStorage.shared.read { db in
userPublicKey = getUserHexEncodedPublicKey(db)
return try Profile.fetchOne(db, id: userPublicKey)
}
return (exisingProfile ?? defaultFor(userPublicKey))
}
static func fetchOrCreateCurrentUser(_ db: Database) -> Profile {
let userPublicKey: String = getUserHexEncodedPublicKey(db)
return (
(try? Profile.fetchOne(db, id: userPublicKey)) ??
defaultFor(userPublicKey)
)
}
static func fetchOrCreate(id: String) -> Profile {
let exisingProfile: Profile? = GRDBStorage.shared.read { db in
try Profile.fetchOne(db, id: id)
}
return (exisingProfile ?? defaultFor(id))
}
static func fetchOrCreate(_ db: Database, id: String) -> Profile {
return (
(try? Profile.fetchOne(db, id: id)) ??
defaultFor(id)
)
}
}
// MARK: - Objective-C Support
@objc(SMKProfile)
public class SMKProfile: NSObject {
var id: String
@objc var name: String
@objc var nickname: String?
init(id: String, name: String, nickname: String?) {
self.id = id
self.name = name
self.nickname = nickname
}
@objc public static func fetchCurrentUserName() -> String {
let existingProfile: Profile? = GRDBStorage.shared.read { db in
Profile.fetchOrCreateCurrentUser(db)
}
return (existingProfile?.name ?? "")
}
@objc public static func fetchOrCreate(id: String) -> SMKProfile {
let profile: Profile = Profile.fetchOrCreate(id: id)
return SMKProfile(
id: id,
name: profile.name,
nickname: profile.nickname
)
}
@objc public static func saveProfile(_ profile: SMKProfile) {
GRDBStorage.shared.write { db in
try? Profile
.fetchOrCreate(db, id: profile.id)
.with(nickname: .updateTo(profile.nickname))
.save(db)
}
}
@objc public static func displayName(id: String) -> String {
return Profile.displayName(for: id)
}
@objc public static func displayName(id: String, customFallback: String) -> String {
return Profile.displayName(for: id, customFallback: customFallback)
}
@objc public static func displayName(id: String, context: Profile.Context = .regular) -> String {
let existingProfile: Profile? = GRDBStorage.shared.read { db in
Profile.fetchOrCreateCurrentUser(db)
}
return (existingProfile?.name ?? id)
}
@objc public static func displayName(id: String, thread: TSThread) -> String {
return Profile.displayName(for: id, thread: thread)
}
@objc public static var localProfileKey: OWSAES256Key? {
Profile.fetchOrCreateCurrentUser().profileEncryptionKey
}
}

View file

@ -0,0 +1,59 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
public struct SessionThread: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "thread" }
static let disappearingMessagesConfiguration = hasOne(DisappearingMessagesConfiguration.self)
static let closedGroup = hasOne(ClosedGroup.self)
static let openGroup = hasOne(OpenGroup.self)
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression {
case id
case variant
case creationDateTimestamp
case shouldBeVisible
case isPinned
case messageDraft
case notificationMode
case mutedUntilTimestamp
}
public enum Variant: Int, Codable, DatabaseValueConvertible {
case contact
case closedGroup
case openGroup
}
public enum NotificationMode: Int, Codable, DatabaseValueConvertible {
case all
case none
case mentionsOnly // Only applicable to group threads
}
public let id: String
public let variant: Variant
public let creationDateTimestamp: TimeInterval
public let shouldBeVisible: Bool
public let isPinned: Bool
public let messageDraft: String?
public let notificationMode: NotificationMode
public let mutedUntilTimestamp: TimeInterval?
public var disappearingMessagesConfiguration: QueryInterfaceRequest<DisappearingMessagesConfiguration> {
request(for: SessionThread.disappearingMessagesConfiguration)
}
// public var lastInteraction
public var closedGroup: QueryInterfaceRequest<ClosedGroup> {
request(for: SessionThread.closedGroup)
}
public var openGroup: QueryInterfaceRequest<OpenGroup> {
request(for: SessionThread.openGroup)
}
}

View file

@ -1,12 +1,27 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import SessionUtilitiesKit
public extension Notification.Name { public extension Notification.Name {
static let contactUpdated = Notification.Name("contactUpdated") static let profileUpdated = Notification.Name("profileUpdated")
static let localProfileDidChange = Notification.Name("localProfileDidChange")
static let otherUsersProfileDidChange = Notification.Name("otherUsersProfileDidChange")
static let contactBlockedStateChanged = Notification.Name("contactBlockedStateChanged") static let contactBlockedStateChanged = Notification.Name("contactBlockedStateChanged")
} }
@objc public extension NSNotification { @objc public extension NSNotification {
@objc static let contactUpdated = Notification.Name.contactUpdated.rawValue as NSString @objc static let profileUpdated = Notification.Name.profileUpdated.rawValue as NSString
@objc static let localProfileDidChange = Notification.Name.localProfileDidChange.rawValue as NSString
@objc static let otherUsersProfileDidChange = Notification.Name.otherUsersProfileDidChange.rawValue as NSString
@objc static let contactBlockedStateChanged = Notification.Name.contactBlockedStateChanged.rawValue as NSString @objc static let contactBlockedStateChanged = Notification.Name.contactBlockedStateChanged.rawValue as NSString
} }
extension Notification.Key {
static let profileRecipientId = Notification.Key("profileRecipientId")
}
@objc public extension NSNotification {
static let profileRecipientIdKey = Notification.Key.profileRecipientId.rawValue as NSString
}

View file

@ -1,6 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import Sodium
import Curve25519Kit import Curve25519Kit
extension Storage { extension Storage {
@ -13,12 +14,18 @@ extension Storage {
private static let closedGroupFormationTimestampCollection = "SNClosedGroupFormationTimestampCollection" private static let closedGroupFormationTimestampCollection = "SNClosedGroupFormationTimestampCollection"
private static let closedGroupZombieMembersCollection = "SNClosedGroupZombieMembersCollection" private static let closedGroupZombieMembersCollection = "SNClosedGroupZombieMembersCollection"
public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String) -> [ECKeyPair] { public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String) -> [Box.KeyPair] {
var result: [ECKeyPair] = [] var result: [ECKeyPair] = []
Storage.read { transaction in Storage.read { transaction in
result = self.getClosedGroupEncryptionKeyPairs(for: groupPublicKey, using: transaction) result = self.getClosedGroupEncryptionKeyPairs(for: groupPublicKey, using: transaction)
} }
return result return result
.map { keyPair -> Box.KeyPair in
Box.KeyPair(
publicKey: keyPair.publicKey.bytes,
secretKey: keyPair.privateKey.bytes
)
}
} }
public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String, using transaction: YapDatabaseReadTransaction) -> [ECKeyPair] { public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String, using transaction: YapDatabaseReadTransaction) -> [ECKeyPair] {
@ -31,7 +38,7 @@ extension Storage {
return timestampsAndKeyPairs.sorted { $0.timestamp < $1.timestamp }.map { $0.keyPair } return timestampsAndKeyPairs.sorted { $0.timestamp < $1.timestamp }.map { $0.keyPair }
} }
public func getLatestClosedGroupEncryptionKeyPair(for groupPublicKey: String) -> ECKeyPair? { public func getLatestClosedGroupEncryptionKeyPair(for groupPublicKey: String) -> Box.KeyPair? {
return getClosedGroupEncryptionKeyPairs(for: groupPublicKey).last return getClosedGroupEncryptionKeyPairs(for: groupPublicKey).last
} }
@ -39,10 +46,14 @@ extension Storage {
return getClosedGroupEncryptionKeyPairs(for: groupPublicKey, using: transaction).last return getClosedGroupEncryptionKeyPairs(for: groupPublicKey, using: transaction).last
} }
public func addClosedGroupEncryptionKeyPair(_ keyPair: ECKeyPair, for groupPublicKey: String, using transaction: Any) { public func addClosedGroupEncryptionKeyPair(_ keyPair: Box.KeyPair, for groupPublicKey: String, using transaction: Any) {
let ecKeyPair: ECKeyPair = try! ECKeyPair(
publicKeyData: Data(keyPair.publicKey),
privateKeyData: Data(keyPair.secretKey)
)
let collection = Storage.getClosedGroupEncryptionKeyPairCollection(for: groupPublicKey) let collection = Storage.getClosedGroupEncryptionKeyPairCollection(for: groupPublicKey)
let timestamp = String(Date().timeIntervalSince1970) let timestamp = String(Date().timeIntervalSince1970)
(transaction as! YapDatabaseReadWriteTransaction).setObject(keyPair, forKey: timestamp, inCollection: collection) (transaction as! YapDatabaseReadWriteTransaction).setObject(ecKeyPair, forKey: timestamp, inCollection: collection)
} }
public func removeAllClosedGroupEncryptionKeyPairs(for groupPublicKey: String, using transaction: Any) { public func removeAllClosedGroupEncryptionKeyPairs(for groupPublicKey: String, using transaction: Any) {

View file

@ -1,77 +0,0 @@
extension Storage {
private static let contactCollection = "LokiContactCollection"
@objc(getContactWithSessionID:)
public func getContact(with sessionID: String) -> Contact? {
var result: Contact?
Storage.read { transaction in
result = self.getContact(with: sessionID, using: transaction)
}
return result
}
@objc(getContactWithSessionID:using:)
public func getContact(with sessionID: String, using transaction: Any) -> Contact? {
var result: Contact?
let transaction = transaction as! YapDatabaseReadTransaction
result = transaction.object(forKey: sessionID, inCollection: Storage.contactCollection) as? Contact
if let result = result, result.sessionID == getUserHexEncodedPublicKey() {
result.isTrusted = true // Always trust ourselves
}
return result
}
@objc(setContact:usingTransaction:)
public func setContact(_ contact: Contact, using transaction: Any) {
let transaction = transaction as! YapDatabaseReadWriteTransaction
let oldContact = getContact(with: contact.sessionID, using: transaction)
if contact.sessionID == getUserHexEncodedPublicKey() {
contact.isTrusted = true // Always trust ourselves
}
transaction.setObject(contact, forKey: contact.sessionID, inCollection: Storage.contactCollection)
transaction.addCompletionQueue(DispatchQueue.main) {
// Delete old profile picture if needed
if let oldProfilePictureFileName = oldContact?.profilePictureFileName,
oldProfilePictureFileName != contact.profilePictureFileName {
let path = OWSUserProfile.profileAvatarFilepath(withFilename: oldProfilePictureFileName)
DispatchQueue.global(qos: .default).async {
OWSFileSystem.deleteFileIfExists(path)
}
}
// Post notification
let notificationCenter = NotificationCenter.default
notificationCenter.post(name: .contactUpdated, object: contact.sessionID)
if contact.sessionID == getUserHexEncodedPublicKey() {
notificationCenter.post(name: Notification.Name(kNSNotificationName_LocalProfileDidChange), object: nil)
}
else {
let userInfo = [ kNSNotificationKey_ProfileRecipientId : contact.sessionID ]
notificationCenter.post(name: Notification.Name(kNSNotificationName_OtherUsersProfileDidChange), object: nil, userInfo: userInfo)
}
if contact.isBlocked != oldContact?.isBlocked {
notificationCenter.post(name: .contactBlockedStateChanged, object: contact.sessionID)
}
}
}
@objc public func getAllContacts() -> Set<Contact> {
var result: Set<Contact> = []
Storage.read { transaction in
result = self.getAllContacts(with: transaction)
}
return result
}
@objc public func getAllContacts(with transaction: YapDatabaseReadTransaction) -> Set<Contact> {
var result: Set<Contact> = []
transaction.enumerateRows(inCollection: Storage.contactCollection) { _, object, _, _ in
guard let contact = object as? Contact else { return }
result.insert(contact)
}
return result
}
}

View file

@ -1,3 +1,6 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import PromiseKit import PromiseKit
import Sodium import Sodium
@ -16,23 +19,22 @@ extension Storage {
public func writeSync(with block: @escaping (Any) -> Void) { public func writeSync(with block: @escaping (Any) -> Void) {
Storage.writeSync { block($0) } Storage.writeSync { block($0) }
} }
// @objc public func getUser() -> Legacy.Contact? {
@objc public func getUser() -> Contact? { // return getUser(using: nil)
return getUser(using: nil) // }
} //
// public func getUser(using transaction: YapDatabaseReadTransaction?) -> Legacy.Contact? {
public func getUser(using transaction: YapDatabaseReadTransaction?) -> Contact? { // let userPublicKey = getUserHexEncodedPublicKey()
let userPublicKey = getUserHexEncodedPublicKey() // var result: Legacy.Contact?
var result: Contact? //
// if let transaction = transaction {
if let transaction = transaction { // result = Storage.shared.getContact(with: userPublicKey, using: transaction)
result = Storage.shared.getContact(with: userPublicKey, using: transaction) // }
} // else {
else { // Storage.read { transaction in
Storage.read { transaction in // result = Storage.shared.getContact(with: userPublicKey, using: transaction)
result = Storage.shared.getContact(with: userPublicKey, using: transaction) // }
} // }
} // return result
return result // }
}
} }

View file

@ -318,7 +318,7 @@ NSString *const TSLazyRestoreAttachmentsGroup = @"TSLazyRestoreAttachmentsGroup"
if (!thread.isGroupThread) { if (!thread.isGroupThread) {
TSContactThread *contactThead = (TSContactThread *)thread; TSContactThread *contactThead = (TSContactThread *)thread;
SNContact *contact = [LKStorage.shared getContactWithSessionID:[contactThead contactSessionID]]; SMKContact *contact = [SMKContact fetchOrCreateWithId:[contactThead contactSessionID]];
if (contact == nil || !contact.didApproveMe) { if (contact == nil || !contact.didApproveMe) {
return nil; return nil;

View file

@ -1,4 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import PromiseKit import PromiseKit
import SignalCoreKit
import SessionUtilitiesKit import SessionUtilitiesKit
public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility

View file

@ -62,12 +62,15 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSC
JobQueue.currentlyExecutingJobs.insert(id) JobQueue.currentlyExecutingJobs.insert(id)
} }
let (promise, seal) = Promise<Void>.pending() let (promise, seal) = Promise<Void>.pending()
GRDBStorage.shared.writeAsync(
updates: { db in
SNMessagingKitConfiguration.shared.storage.write(with: { transaction in // Intentionally capture self SNMessagingKitConfiguration.shared.storage.write(with: { transaction in // Intentionally capture self
do { do {
let isRetry = (self.failureCount != 0) let isRetry = (self.failureCount != 0)
let (message, proto) = try MessageReceiver.parse(self.data, openGroupMessageServerID: self.openGroupMessageServerID, isRetry: isRetry, using: transaction) let (message, proto) = try MessageReceiver.parse(db, self.data, openGroupMessageServerID: self.openGroupMessageServerID, isRetry: isRetry, using: transaction)
message.serverHash = self.serverHash message.serverHash = self.serverHash
try MessageReceiver.handle(message, associatedWithProto: proto, openGroupID: self.openGroupID, isBackgroundPoll: self.isBackgroundPoll, using: transaction) try MessageReceiver.handle(db, message, associatedWithProto: proto, openGroupID: self.openGroupID, isBackgroundPoll: self.isBackgroundPoll, using: transaction)
self.handleSuccess() self.handleSuccess()
seal.fulfill(()) seal.fulfill(())
} catch { } catch {
@ -81,6 +84,15 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSC
seal.fulfill(()) // The promise is just used to keep track of when we're done seal.fulfill(()) // The promise is just used to keep track of when we're done
} }
}, completion: { }) }, completion: { })
},
completion: { _, result in
switch result {
case .failure(let error): self.handleFailure(error: error)
default: break
}
}
)
return promise return promise
} }

View file

@ -1,6 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import Sodium
import Curve25519Kit import Curve25519Kit
import SessionUtilitiesKit import SessionUtilitiesKit
@ -18,7 +19,7 @@ public final class ClosedGroupControlMessage : ControlMessage {
// MARK: Kind // MARK: Kind
public enum Kind : CustomStringConvertible { public enum Kind : CustomStringConvertible {
case new(publicKey: Data, name: String, encryptionKeyPair: ECKeyPair, members: [Data], admins: [Data], expirationTimer: UInt32) case new(publicKey: Data, name: String, encryptionKeyPair: Box.KeyPair, members: [Data], admins: [Data], expirationTimer: UInt32)
/// An encryption key pair encrypted for each member individually. /// An encryption key pair encrypted for each member individually.
/// ///
/// - Note: `publicKey` is only set when an encryption key pair is sent in a one-to-one context (i.e. not in a group). /// - Note: `publicKey` is only set when an encryption key pair is sent in a one-to-one context (i.e. not in a group).
@ -95,7 +96,7 @@ public final class ClosedGroupControlMessage : ControlMessage {
switch kind { switch kind {
case .new(let publicKey, let name, let encryptionKeyPair, let members, let admins, let expirationTimer): case .new(let publicKey, let name, let encryptionKeyPair, let members, let admins, let expirationTimer):
return !publicKey.isEmpty && !name.isEmpty && !encryptionKeyPair.publicKey.isEmpty return !publicKey.isEmpty && !name.isEmpty && !encryptionKeyPair.publicKey.isEmpty
&& !encryptionKeyPair.privateKey.isEmpty && !members.isEmpty && !admins.isEmpty && !encryptionKeyPair.secretKey.isEmpty && !members.isEmpty && !admins.isEmpty
case .encryptionKeyPair: return true case .encryptionKeyPair: return true
case .nameChange(let name): return !name.isEmpty case .nameChange(let name): return !name.isEmpty
case .membersAdded(let members): return !members.isEmpty case .membersAdded(let members): return !members.isEmpty
@ -113,11 +114,15 @@ public final class ClosedGroupControlMessage : ControlMessage {
case "new": case "new":
guard let publicKey = coder.decodeObject(forKey: "publicKey") as? Data, guard let publicKey = coder.decodeObject(forKey: "publicKey") as? Data,
let name = coder.decodeObject(forKey: "name") as? String, let name = coder.decodeObject(forKey: "name") as? String,
let encryptionKeyPair = coder.decodeObject(forKey: "encryptionKeyPair") as? ECKeyPair, let encryptionKeyPair = coder.decodeObject(forKey: "encryptionKeyPair") as? SessionUtilitiesKit.Legacy.KeyPair,
let members = coder.decodeObject(forKey: "members") as? [Data], let members = coder.decodeObject(forKey: "members") as? [Data],
let admins = coder.decodeObject(forKey: "admins") as? [Data] else { return nil } let admins = coder.decodeObject(forKey: "admins") as? [Data] else { return nil }
let expirationTimer = coder.decodeObject(forKey: "expirationTimer") as? UInt32 ?? 0 let expirationTimer = coder.decodeObject(forKey: "expirationTimer") as? UInt32 ?? 0
self.kind = .new(publicKey: publicKey, name: name, encryptionKeyPair: encryptionKeyPair, members: members, admins: admins, expirationTimer: expirationTimer) let keyPair: Box.KeyPair = Box.KeyPair(
publicKey: encryptionKeyPair.publicKey.bytes,
secretKey: encryptionKeyPair.privateKey.bytes
)
self.kind = .new(publicKey: publicKey, name: name, encryptionKeyPair: keyPair, members: members, admins: admins, expirationTimer: expirationTimer)
case "encryptionKeyPair": case "encryptionKeyPair":
let publicKey = coder.decodeObject(forKey: "publicKey") as? Data let publicKey = coder.decodeObject(forKey: "publicKey") as? Data
guard let wrappers = coder.decodeObject(forKey: "wrappers") as? [KeyPairWrapper] else { return nil } guard let wrappers = coder.decodeObject(forKey: "wrappers") as? [KeyPairWrapper] else { return nil }
@ -172,7 +177,7 @@ public final class ClosedGroupControlMessage : ControlMessage {
} }
// MARK: Proto Conversion // MARK: Proto Conversion
public override class func fromProto(_ proto: SNProtoContent) -> ClosedGroupControlMessage? { public override class func fromProto(_ proto: SNProtoContent, sender: String) -> ClosedGroupControlMessage? {
guard let closedGroupControlMessageProto = proto.dataMessage?.closedGroupControlMessage else { return nil } guard let closedGroupControlMessageProto = proto.dataMessage?.closedGroupControlMessage else { return nil }
let kind: Kind let kind: Kind
switch closedGroupControlMessageProto.type { switch closedGroupControlMessageProto.type {
@ -180,14 +185,9 @@ public final class ClosedGroupControlMessage : ControlMessage {
guard let publicKey = closedGroupControlMessageProto.publicKey, let name = closedGroupControlMessageProto.name, guard let publicKey = closedGroupControlMessageProto.publicKey, let name = closedGroupControlMessageProto.name,
let encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair else { return nil } let encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair else { return nil }
let expirationTimer = closedGroupControlMessageProto.expirationTimer let expirationTimer = closedGroupControlMessageProto.expirationTimer
do { let encryptionKeyPair = Box.KeyPair(publicKey: encryptionKeyPairAsProto.publicKey.removing05PrefixIfNeeded().bytes, secretKey: encryptionKeyPairAsProto.privateKey.bytes)
let encryptionKeyPair = try ECKeyPair(publicKeyData: encryptionKeyPairAsProto.publicKey.removing05PrefixIfNeeded(), privateKeyData: encryptionKeyPairAsProto.privateKey)
kind = .new(publicKey: publicKey, name: name, encryptionKeyPair: encryptionKeyPair, kind = .new(publicKey: publicKey, name: name, encryptionKeyPair: encryptionKeyPair,
members: closedGroupControlMessageProto.members, admins: closedGroupControlMessageProto.admins, expirationTimer: expirationTimer) members: closedGroupControlMessageProto.members, admins: closedGroupControlMessageProto.admins, expirationTimer: expirationTimer)
} catch {
SNLog("Couldn't parse key pair.")
return nil
}
case .encryptionKeyPair: case .encryptionKeyPair:
let publicKey = closedGroupControlMessageProto.publicKey let publicKey = closedGroupControlMessageProto.publicKey
let wrappers = closedGroupControlMessageProto.wrappers.compactMap { KeyPairWrapper.fromProto($0) } let wrappers = closedGroupControlMessageProto.wrappers.compactMap { KeyPairWrapper.fromProto($0) }
@ -219,7 +219,7 @@ public final class ClosedGroupControlMessage : ControlMessage {
closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .new) closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .new)
closedGroupControlMessage.setPublicKey(publicKey) closedGroupControlMessage.setPublicKey(publicKey)
closedGroupControlMessage.setName(name) closedGroupControlMessage.setName(name)
let encryptionKeyPairAsProto = SNProtoKeyPair.builder(publicKey: encryptionKeyPair.publicKey, privateKey: encryptionKeyPair.privateKey) let encryptionKeyPairAsProto = SNProtoKeyPair.builder(publicKey: Data(encryptionKeyPair.publicKey), privateKey: Data(encryptionKeyPair.secretKey))
do { do {
closedGroupControlMessage.setEncryptionKeyPair(try encryptionKeyPairAsProto.build()) closedGroupControlMessage.setEncryptionKeyPair(try encryptionKeyPairAsProto.build())
} catch { } catch {

View file

@ -1,18 +1,21 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit import SessionUtilitiesKit
extension ConfigurationMessage { extension ConfigurationMessage {
public static func getCurrent(with transaction: YapDatabaseReadTransaction) -> ConfigurationMessage? { public static func getCurrent(_ db: Database) throws -> ConfigurationMessage? {
let storage = Storage.shared let profile: Profile = Profile.fetchOrCreateCurrentUser(db)
guard let user = storage.getUser(using: transaction) else { return nil }
let displayName = user.name let displayName: String = profile.name
let profilePictureURL = user.profilePictureURL let profilePictureUrl: String? = profile.profilePictureUrl
let profileKey = user.profileEncryptionKey?.keyData let profileKey: Data? = profile.profileEncryptionKey?.keyData
var closedGroups: Set<ClosedGroup> = [] var closedGroups: Set<ClosedGroup> = []
var openGroups: Set<String> = [] var openGroups: Set<String> = []
var contacts: Set<ConfigurationMessage.Contact> = []
Storage.read { transaction in
TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in
guard let thread = object as? TSGroupThread else { return } guard let thread = object as? TSGroupThread else { return }
@ -24,8 +27,8 @@ extension ConfigurationMessage {
let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID) let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID)
guard guard
storage.isClosedGroup(groupPublicKey, using: transaction), Storage.shared.isClosedGroup(groupPublicKey, using: transaction),
let encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey, using: transaction) let encryptionKeyPair = Storage.shared.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey, using: transaction)
else { else {
return return
} }
@ -41,48 +44,30 @@ extension ConfigurationMessage {
closedGroups.insert(closedGroup) closedGroups.insert(closedGroup)
case .openGroup: case .openGroup:
if let threadId: String = thread.uniqueId, let v2OpenGroup = storage.getV2OpenGroup(for: threadId) { if let threadId: String = thread.uniqueId, let v2OpenGroup = Storage.shared.getV2OpenGroup(for: threadId) {
openGroups.insert("\(v2OpenGroup.server)/\(v2OpenGroup.room)?public_key=\(v2OpenGroup.publicKey)") openGroups.insert("\(v2OpenGroup.server)/\(v2OpenGroup.room)?public_key=\(v2OpenGroup.publicKey)")
} }
default: break default: break
} }
} }
}
let currentUserPublicKey: String = getUserHexEncodedPublicKey() let currentUserPublicKey: String = getUserHexEncodedPublicKey()
contacts = storage.getAllContacts(with: transaction) let contacts: Set<CMContact> = try Contact.fetchAll(db)
.compactMap { contact -> ConfigurationMessage.Contact? in .compactMap { contact -> CMContact? in
let threadID = TSContactThread.threadID(fromContactSessionID: contact.sessionID) guard contact.id != currentUserPublicKey else { return nil }
guard
// Skip the current user
contact.sessionID != currentUserPublicKey &&
// Contacts which have visible threads
TSContactThread.fetch(uniqueId: threadID, transaction: transaction)?.shouldBeVisible == true && (
// Include already approved contacts
contact.isApproved ||
contact.didApproveMe ||
// Sync blocked contacts
contact.isBlocked ||
contact.hasBeenBlocked
)
else {
return nil
}
// Can just default the 'hasX' values to true as they will be set to this // Can just default the 'hasX' values to true as they will be set to this
// when converting to proto anyway // when converting to proto anyway
let profilePictureURL = contact.profilePictureURL let profile: Profile? = try? Profile.fetchOne(db, id: contact.id)
let profileKey = contact.profileEncryptionKey?.keyData
return ConfigurationMessage.Contact( return CMContact(
publicKey: contact.sessionID, publicKey: contact.id,
displayName: (contact.name ?? contact.sessionID), displayName: (profile?.name ?? contact.id),
profilePictureURL: profilePictureURL, profilePictureURL: profile?.profilePictureUrl,
profileKey: profileKey, profileKey: profile?.profileEncryptionKey?.keyData,
hasIsApproved: true, hasIsApproved: true,
isApproved: contact.isApproved, isApproved: contact.isApproved,
hasIsBlocked: true, hasIsBlocked: true,
@ -95,7 +80,7 @@ extension ConfigurationMessage {
return ConfigurationMessage( return ConfigurationMessage(
displayName: displayName, displayName: displayName,
profilePictureURL: profilePictureURL, profilePictureURL: profilePictureUrl,
profileKey: profileKey, profileKey: profileKey,
closedGroups: closedGroups, closedGroups: closedGroups,
openGroups: openGroups, openGroups: openGroups,

View file

@ -11,14 +11,14 @@ public final class ConfigurationMessage : ControlMessage {
public var displayName: String? public var displayName: String?
public var profilePictureURL: String? public var profilePictureURL: String?
public var profileKey: Data? public var profileKey: Data?
public var contacts: Set<Contact> = [] public var contacts: Set<CMContact> = []
public override var isSelfSendValid: Bool { true } public override var isSelfSendValid: Bool { true }
// MARK: Initialization // MARK: Initialization
public override init() { super.init() } public override init() { super.init() }
public init(displayName: String?, profilePictureURL: String?, profileKey: Data?, closedGroups: Set<ClosedGroup>, openGroups: Set<String>, contacts: Set<Contact>) { public init(displayName: String?, profilePictureURL: String?, profileKey: Data?, closedGroups: Set<ClosedGroup>, openGroups: Set<String>, contacts: Set<CMContact>) {
super.init() super.init()
self.displayName = displayName self.displayName = displayName
self.profilePictureURL = profilePictureURL self.profilePictureURL = profilePictureURL
@ -36,7 +36,7 @@ public final class ConfigurationMessage : ControlMessage {
if let displayName = coder.decodeObject(forKey: "displayName") as! String? { self.displayName = displayName } 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 profilePictureURL = coder.decodeObject(forKey: "profilePictureURL") as! String? { self.profilePictureURL = profilePictureURL }
if let profileKey = coder.decodeObject(forKey: "profileKey") as! Data? { self.profileKey = profileKey } if let profileKey = coder.decodeObject(forKey: "profileKey") as! Data? { self.profileKey = profileKey }
if let contacts = coder.decodeObject(forKey: "contacts") as! Set<Contact>? { self.contacts = contacts } if let contacts = coder.decodeObject(forKey: "contacts") as! Set<CMContact>? { self.contacts = contacts }
} }
public override func encode(with coder: NSCoder) { public override func encode(with coder: NSCoder) {
@ -50,14 +50,14 @@ public final class ConfigurationMessage : ControlMessage {
} }
// MARK: Proto Conversion // MARK: Proto Conversion
public override class func fromProto(_ proto: SNProtoContent) -> ConfigurationMessage? { public override class func fromProto(_ proto: SNProtoContent, sender: String) -> ConfigurationMessage? {
guard let configurationProto = proto.configurationMessage else { return nil } guard let configurationProto = proto.configurationMessage else { return nil }
let displayName = configurationProto.displayName let displayName = configurationProto.displayName
let profilePictureURL = configurationProto.profilePicture let profilePictureURL = configurationProto.profilePicture
let profileKey = configurationProto.profileKey let profileKey = configurationProto.profileKey
let closedGroups = Set(configurationProto.closedGroups.compactMap { ClosedGroup.fromProto($0) }) let closedGroups = Set(configurationProto.closedGroups.compactMap { ClosedGroup.fromProto($0) })
let openGroups = Set(configurationProto.openGroups) let openGroups = Set(configurationProto.openGroups)
let contacts = Set(configurationProto.contacts.compactMap { Contact.fromProto($0) }) let contacts = Set(configurationProto.contacts.compactMap { CMContact.fromProto($0) })
return ConfigurationMessage(displayName: displayName, profilePictureURL: profilePictureURL, profileKey: profileKey, return ConfigurationMessage(displayName: displayName, profilePictureURL: profilePictureURL, profileKey: profileKey,
closedGroups: closedGroups, openGroups: openGroups, contacts: contacts) closedGroups: closedGroups, openGroups: openGroups, contacts: contacts)
} }
@ -89,7 +89,7 @@ public final class ConfigurationMessage : ControlMessage {
displayName: \(displayName ?? "null"), displayName: \(displayName ?? "null"),
profilePictureURL: \(profilePictureURL ?? "null"), profilePictureURL: \(profilePictureURL ?? "null"),
profileKey: \(profileKey?.toHexString() ?? "null"), profileKey: \(profileKey?.toHexString() ?? "null"),
contacts: \([Contact](contacts).prettifiedDescription) contacts: \([CMContact](contacts).prettifiedDescription)
) )
""" """
} }
@ -192,7 +192,7 @@ extension ConfigurationMessage {
extension ConfigurationMessage { extension ConfigurationMessage {
@objc(SNConfigurationMessageContact) @objc(SNConfigurationMessageContact)
public final class Contact : NSObject, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility public final class CMContact : NSObject, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility
public var publicKey: String? public var publicKey: String?
public var displayName: String? public var displayName: String?
public var profilePictureURL: String? public var profilePictureURL: String?
@ -259,8 +259,8 @@ extension ConfigurationMessage {
coder.encode(didApproveMe, forKey: "didApproveMe") coder.encode(didApproveMe, forKey: "didApproveMe")
} }
public static func fromProto(_ proto: SNProtoConfigurationMessageContact) -> Contact? { public static func fromProto(_ proto: SNProtoConfigurationMessageContact) -> CMContact? {
let result: Contact = Contact( let result: CMContact = CMContact(
publicKey: proto.publicKey.toHexString(), publicKey: proto.publicKey.toHexString(),
displayName: proto.name, displayName: proto.name,
profilePictureURL: proto.profilePicture, profilePictureURL: proto.profilePicture,

View file

@ -60,7 +60,7 @@ public final class DataExtractionNotification : ControlMessage {
} }
// MARK: Proto Conversion // MARK: Proto Conversion
public override class func fromProto(_ proto: SNProtoContent) -> DataExtractionNotification? { public override class func fromProto(_ proto: SNProtoContent, sender: String) -> DataExtractionNotification? {
guard let dataExtractionNotification = proto.dataExtractionNotification else { return nil } guard let dataExtractionNotification = proto.dataExtractionNotification else { return nil }
let kind: Kind let kind: Kind
switch dataExtractionNotification.type { switch dataExtractionNotification.type {

View file

@ -39,7 +39,7 @@ public final class ExpirationTimerUpdate : ControlMessage {
} }
// MARK: Proto Conversion // MARK: Proto Conversion
public override class func fromProto(_ proto: SNProtoContent) -> ExpirationTimerUpdate? { public override class func fromProto(_ proto: SNProtoContent, sender: String) -> ExpirationTimerUpdate? {
guard let dataMessageProto = proto.dataMessage else { return nil } guard let dataMessageProto = proto.dataMessage else { return nil }
let isExpirationTimerUpdate = (dataMessageProto.flags & UInt32(SNProtoDataMessage.SNProtoDataMessageFlags.expirationTimerUpdate.rawValue)) != 0 let isExpirationTimerUpdate = (dataMessageProto.flags & UInt32(SNProtoDataMessage.SNProtoDataMessageFlags.expirationTimerUpdate.rawValue)) != 0
guard isExpirationTimerUpdate else { return nil } guard isExpirationTimerUpdate else { return nil }

View file

@ -30,7 +30,7 @@ public final class MessageRequestResponse: ControlMessage {
// MARK: - Proto Conversion // MARK: - Proto Conversion
public override class func fromProto(_ proto: SNProtoContent) -> MessageRequestResponse? { public override class func fromProto(_ proto: SNProtoContent, sender: String) -> MessageRequestResponse? {
guard let messageRequestResponseProto = proto.messageRequestResponse else { return nil } guard let messageRequestResponseProto = proto.messageRequestResponse else { return nil }
let isApproved = messageRequestResponseProto.isApproved let isApproved = messageRequestResponseProto.isApproved

View file

@ -31,7 +31,7 @@ public final class ReadReceipt : ControlMessage {
} }
// MARK: Proto Conversion // MARK: Proto Conversion
public override class func fromProto(_ proto: SNProtoContent) -> ReadReceipt? { public override class func fromProto(_ proto: SNProtoContent, sender: String) -> ReadReceipt? {
guard let receiptProto = proto.receiptMessage, receiptProto.type == .read else { return nil } guard let receiptProto = proto.receiptMessage, receiptProto.type == .read else { return nil }
let timestamps = receiptProto.timestamp let timestamps = receiptProto.timestamp
guard !timestamps.isEmpty else { return nil } guard !timestamps.isEmpty else { return nil }

View file

@ -58,7 +58,7 @@ public final class TypingIndicator : ControlMessage {
} }
// MARK: Proto Conversion // MARK: Proto Conversion
public override class func fromProto(_ proto: SNProtoContent) -> TypingIndicator? { public override class func fromProto(_ proto: SNProtoContent, sender: String) -> TypingIndicator? {
guard let typingIndicatorProto = proto.typingMessage else { return nil } guard let typingIndicatorProto = proto.typingMessage else { return nil }
let kind = Kind.fromProto(typingIndicatorProto.action) let kind = Kind.fromProto(typingIndicatorProto.action)
return TypingIndicator(kind: kind) return TypingIndicator(kind: kind)

View file

@ -36,7 +36,7 @@ public final class UnsendRequest: ControlMessage {
} }
// MARK: Proto Conversion // MARK: Proto Conversion
public override class func fromProto(_ proto: SNProtoContent) -> UnsendRequest? { public override class func fromProto(_ proto: SNProtoContent, sender: String) -> UnsendRequest? {
guard let unsendRequestProto = proto.unsendRequest else { return nil } guard let unsendRequestProto = proto.unsendRequest else { return nil }
let timestamp = unsendRequestProto.timestamp let timestamp = unsendRequestProto.timestamp
let author = unsendRequestProto.author let author = unsendRequestProto.author

View file

@ -53,8 +53,8 @@ public class Message : NSObject, NSCoding { // NSObject/NSCoding conformance is
} }
// MARK: Proto Conversion // MARK: Proto Conversion
public class func fromProto(_ proto: SNProtoContent) -> Self? { public class func fromProto(_ proto: SNProtoContent, sender: String) -> Self? {
preconditionFailure("fromProto(_:) is abstract and must be overridden.") preconditionFailure("fromProto(_:sender:) is abstract and must be overridden.")
} }
public func toProto(using transaction: YapDatabaseReadWriteTransaction) -> SNProtoContent? { public func toProto(using transaction: YapDatabaseReadWriteTransaction) -> SNProtoContent? {

View file

@ -144,7 +144,7 @@ const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024;
- (void)setExpiresInSeconds:(uint32_t)expiresInSeconds - (void)setExpiresInSeconds:(uint32_t)expiresInSeconds
{ {
uint32_t maxExpirationDuration = [OWSDisappearingMessagesConfiguration maxDurationSeconds]; uint32_t maxExpirationDuration = [SMKDisappearingMessagesConfiguration maxDurationSeconds];
_expiresInSeconds = MIN(expiresInSeconds, maxExpirationDuration); _expiresInSeconds = MIN(expiresInSeconds, maxExpirationDuration);
[self updateExpiresAt]; [self updateExpiresAt];

View file

@ -1,3 +1,6 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import SessionUtilitiesKit import SessionUtilitiesKit
@objc public extension TSOutgoingMessage { @objc public extension TSOutgoingMessage {
@ -9,11 +12,11 @@ import SessionUtilitiesKit
static func from(_ visibleMessage: VisibleMessage, associatedWith thread: TSThread, using transaction: YapDatabaseReadWriteTransaction? = nil) -> TSOutgoingMessage { static func from(_ visibleMessage: VisibleMessage, associatedWith thread: TSThread, using transaction: YapDatabaseReadWriteTransaction? = nil) -> TSOutgoingMessage {
var expiration: UInt32 = 0 var expiration: UInt32 = 0
let disappearingMessagesConfigurationOrNil: OWSDisappearingMessagesConfiguration? let disappearingMessagesConfigurationOrNil: Legacy.DisappearingMessagesConfiguration?
if let transaction = transaction { if let transaction = transaction {
disappearingMessagesConfigurationOrNil = OWSDisappearingMessagesConfiguration.fetch(uniqueId: thread.uniqueId!, transaction: transaction) disappearingMessagesConfigurationOrNil = Legacy.DisappearingMessagesConfiguration.fetch(uniqueId: thread.uniqueId!, transaction: transaction)
} else { } else {
disappearingMessagesConfigurationOrNil = OWSDisappearingMessagesConfiguration.fetch(uniqueId: thread.uniqueId!) disappearingMessagesConfigurationOrNil = Legacy.DisappearingMessagesConfiguration.fetch(uniqueId: thread.uniqueId!)
} }
if let disappearingMessagesConfiguration = disappearingMessagesConfigurationOrNil { if let disappearingMessagesConfiguration = disappearingMessagesConfigurationOrNil {
expiration = disappearingMessagesConfiguration.isEnabled ? disappearingMessagesConfiguration.durationSeconds : 0 expiration = disappearingMessagesConfiguration.isEnabled ? disappearingMessagesConfiguration.durationSeconds : 0

View file

@ -1,8 +1,7 @@
// // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation import Foundation
import SignalCoreKit
@objc(OWSTypingIndicatorInteraction) @objc(OWSTypingIndicatorInteraction)
public class TypingIndicatorInteraction: TSInteraction { public class TypingIndicatorInteraction: TSInteraction {

View file

@ -1,72 +1,72 @@
import SessionUtilitiesKit //import SessionUtilitiesKit
//
public extension VisibleMessage { //public extension VisibleMessage {
//
@objc(SNProfile) // @objc(SNProfile)
class Profile : NSObject, NSCoding { // class Profile : NSObject, NSCoding {
public var displayName: String? // public var displayName: String?
public var profileKey: Data? // public var profileKey: Data?
public var profilePictureURL: String? // public var profilePictureURL: String?
//
internal init(displayName: String, profileKey: Data? = nil, profilePictureURL: String? = nil) { // internal init(displayName: String, profileKey: Data? = nil, profilePictureURL: String? = nil) {
self.displayName = displayName // self.displayName = displayName
self.profileKey = profileKey // self.profileKey = profileKey
self.profilePictureURL = profilePictureURL // self.profilePictureURL = profilePictureURL
} // }
//
public required init?(coder: NSCoder) { // public required init?(coder: NSCoder) {
if let displayName = coder.decodeObject(forKey: "displayName") as! String? { self.displayName = displayName } // if let displayName = coder.decodeObject(forKey: "displayName") as! String? { self.displayName = displayName }
if let profileKey = coder.decodeObject(forKey: "profileKey") as! Data? { self.profileKey = profileKey } // if let profileKey = coder.decodeObject(forKey: "profileKey") as! Data? { self.profileKey = profileKey }
if let profilePictureURL = coder.decodeObject(forKey: "profilePictureURL") as! String? { self.profilePictureURL = profilePictureURL } // if let profilePictureURL = coder.decodeObject(forKey: "profilePictureURL") as! String? { self.profilePictureURL = profilePictureURL }
} // }
//
public func encode(with coder: NSCoder) { // public func encode(with coder: NSCoder) {
coder.encode(displayName, forKey: "displayName") // coder.encode(displayName, forKey: "displayName")
coder.encode(profileKey, forKey: "profileKey") // coder.encode(profileKey, forKey: "profileKey")
coder.encode(profilePictureURL, forKey: "profilePictureURL") // coder.encode(profilePictureURL, forKey: "profilePictureURL")
} // }
//
public static func fromProto(_ proto: SNProtoDataMessage) -> Profile? { // public static func fromProto(_ proto: SNProtoDataMessage, sessionId: String) -> Profile? {
guard let profileProto = proto.profile, let displayName = profileProto.displayName else { return nil } // guard let profileProto = proto.profile, let displayName = profileProto.displayName else { return nil }
let profileKey = proto.profileKey // let profileKey = proto.profileKey
let profilePictureURL = profileProto.profilePicture // let profilePictureURL = profileProto.profilePicture
if let profileKey = profileKey, let profilePictureURL = profilePictureURL { // if let profileKey = profileKey, let profilePictureURL = profilePictureURL {
return Profile(displayName: displayName, profileKey: profileKey, profilePictureURL: profilePictureURL) // return Profile(displayName: displayName, profileKey: profileKey, profilePictureURL: profilePictureURL)
} else { // } else {
return Profile(displayName: displayName) // return Profile(displayName: displayName)
} // }
} // }
//
public func toProto() -> SNProtoDataMessage? { // public func toProto() -> SNProtoDataMessage? {
guard let displayName = displayName else { // guard let displayName = displayName else {
SNLog("Couldn't construct profile proto from: \(self).") // SNLog("Couldn't construct profile proto from: \(self).")
return nil // return nil
} // }
let dataMessageProto = SNProtoDataMessage.builder() // let dataMessageProto = SNProtoDataMessage.builder()
let profileProto = SNProtoDataMessageLokiProfile.builder() // let profileProto = SNProtoDataMessageLokiProfile.builder()
profileProto.setDisplayName(displayName) // profileProto.setDisplayName(displayName)
if let profileKey = profileKey, let profilePictureURL = profilePictureURL { // if let profileKey = profileKey, let profilePictureURL = profilePictureURL {
dataMessageProto.setProfileKey(profileKey) // dataMessageProto.setProfileKey(profileKey)
profileProto.setProfilePicture(profilePictureURL) // profileProto.setProfilePicture(profilePictureURL)
} // }
do { // do {
dataMessageProto.setProfile(try profileProto.build()) // dataMessageProto.setProfile(try profileProto.build())
return try dataMessageProto.build() // return try dataMessageProto.build()
} catch { // } catch {
SNLog("Couldn't construct profile proto from: \(self).") // SNLog("Couldn't construct profile proto from: \(self).")
return nil // return nil
} // }
} // }
//
// MARK: Description // // MARK: Description
public override var description: String { // public override var description: String {
""" // """
Profile( // Profile(
displayName: \(displayName ?? "null"), // displayName: \(displayName ?? "null"),
profileKey: \(profileKey?.description ?? "null"), // profileKey: \(profileKey?.description ?? "null"),
profilePictureURL: \(profilePictureURL ?? "null") // profilePictureURL: \(profilePictureURL ?? "null")
) // )
""" // """
} // }
} // }
} //}

View file

@ -10,8 +10,8 @@ public final class VisibleMessage : Message {
@objc public var attachmentIDs: [String] = [] @objc public var attachmentIDs: [String] = []
@objc public var quote: Quote? @objc public var quote: Quote?
@objc public var linkPreview: LinkPreview? @objc public var linkPreview: LinkPreview?
@objc public var contact: Contact? @objc public var contact: Legacy.Contact?
@objc public var profile: Profile? @objc public var profile: Legacy.Profile?
@objc public var openGroupInvitation: OpenGroupInvitation? @objc public var openGroupInvitation: OpenGroupInvitation?
public override var isSelfSendValid: Bool { true } public override var isSelfSendValid: Bool { true }
@ -37,7 +37,7 @@ public final class VisibleMessage : Message {
if let quote = coder.decodeObject(forKey: "quote") as! Quote? { self.quote = quote } if let quote = coder.decodeObject(forKey: "quote") as! Quote? { self.quote = quote }
if let linkPreview = coder.decodeObject(forKey: "linkPreview") as! LinkPreview? { self.linkPreview = linkPreview } if let linkPreview = coder.decodeObject(forKey: "linkPreview") as! LinkPreview? { self.linkPreview = linkPreview }
// TODO: Contact // TODO: Contact
if let profile = coder.decodeObject(forKey: "profile") as! Profile? { self.profile = profile } if let profile = coder.decodeObject(forKey: "profile") as! Legacy.Profile? { self.profile = profile }
if let openGroupInvitation = coder.decodeObject(forKey: "openGroupInvitation") as! OpenGroupInvitation? { self.openGroupInvitation = openGroupInvitation } if let openGroupInvitation = coder.decodeObject(forKey: "openGroupInvitation") as! OpenGroupInvitation? { self.openGroupInvitation = openGroupInvitation }
} }
@ -54,7 +54,7 @@ public final class VisibleMessage : Message {
} }
// MARK: Proto Conversion // MARK: Proto Conversion
public override class func fromProto(_ proto: SNProtoContent) -> VisibleMessage? { public override class func fromProto(_ proto: SNProtoContent, sender: String) -> VisibleMessage? {
guard let dataMessage = proto.dataMessage else { return nil } guard let dataMessage = proto.dataMessage else { return nil }
let result = VisibleMessage() let result = VisibleMessage()
result.text = dataMessage.body result.text = dataMessage.body
@ -62,7 +62,7 @@ public final class VisibleMessage : Message {
if let quoteProto = dataMessage.quote, let quote = Quote.fromProto(quoteProto) { result.quote = quote } if let quoteProto = dataMessage.quote, let quote = Quote.fromProto(quoteProto) { result.quote = quote }
if let linkPreviewProto = dataMessage.preview.first, let linkPreview = LinkPreview.fromProto(linkPreviewProto) { result.linkPreview = linkPreview } if let linkPreviewProto = dataMessage.preview.first, let linkPreview = LinkPreview.fromProto(linkPreviewProto) { result.linkPreview = linkPreview }
// TODO: Contact // TODO: Contact
if let profile = Profile.fromProto(dataMessage) { result.profile = profile } if let profile = Legacy.Profile.fromProto(dataMessage) { result.profile = profile }
if let openGroupInvitationProto = dataMessage.openGroupInvitation, if let openGroupInvitationProto = dataMessage.openGroupInvitation,
let openGroupInvitation = OpenGroupInvitation.fromProto(openGroupInvitationProto) { result.openGroupInvitation = openGroupInvitation } let openGroupInvitation = OpenGroupInvitation.fromProto(openGroupInvitationProto) { result.openGroupInvitation = openGroupInvitation }
result.syncTarget = dataMessage.syncTarget result.syncTarget = dataMessage.syncTarget

View file

@ -8,7 +8,7 @@ import SessionUtilitiesKit
@objc(SNOpenGroupAPIV2) @objc(SNOpenGroupAPIV2)
public final class OpenGroupAPIV2 : NSObject { public final class OpenGroupAPIV2 : NSObject {
private static var authTokenPromises: [String: Promise<String>] = [:] private static var authTokenPromises: Atomic<[String: Promise<String>]> = Atomic([:])
private static var hasPerformedInitialPoll: [String: Bool] = [:] private static var hasPerformedInitialPoll: [String: Bool] = [:]
private static var hasUpdatedLastOpenDate = false private static var hasUpdatedLastOpenDate = false
public static let workQueue = DispatchQueue(label: "OpenGroupAPIV2.workQueue", qos: .userInitiated) // It's important that this is a serial queue public static let workQueue = DispatchQueue(label: "OpenGroupAPIV2.workQueue", qos: .userInitiated) // It's important that this is a serial queue
@ -215,7 +215,7 @@ public final class OpenGroupAPIV2 : NSObject {
if let authToken = storage.getAuthToken(for: room, on: server) { if let authToken = storage.getAuthToken(for: room, on: server) {
return Promise.value(authToken) return Promise.value(authToken)
} else { } else {
if let authTokenPromise = authTokenPromises["\(server).\(room)"] { if let authTokenPromise = authTokenPromises.wrappedValue["\(server).\(room)"] {
return authTokenPromise return authTokenPromise
} else { } else {
let promise = requestNewAuthToken(for: room, on: server) let promise = requestNewAuthToken(for: room, on: server)
@ -230,11 +230,11 @@ public final class OpenGroupAPIV2 : NSObject {
return promise return promise
} }
promise.done(on: OpenGroupAPIV2.workQueue) { _ in promise.done(on: OpenGroupAPIV2.workQueue) { _ in
authTokenPromises["\(server).\(room)"] = nil authTokenPromises.mutate { $0["\(server).\(room)"] = nil }
}.catch(on: OpenGroupAPIV2.workQueue) { _ in }.catch(on: OpenGroupAPIV2.workQueue) { _ in
authTokenPromises["\(server).\(room)"] = nil authTokenPromises.mutate { $0["\(server).\(room)"] = nil }
} }
authTokenPromises["\(server).\(room)"] = promise authTokenPromises.mutate { $0["\(server).\(room)"] = promise }
return promise return promise
} }
} }
@ -251,7 +251,7 @@ public final class OpenGroupAPIV2 : NSObject {
let ephemeralPublicKey = Data(base64Encoded: base64EncodedEphemeralPublicKey) else { let ephemeralPublicKey = Data(base64Encoded: base64EncodedEphemeralPublicKey) else {
throw Error.parsingFailed throw Error.parsingFailed
} }
let symmetricKey = try AESGCM.generateSymmetricKey(x25519PublicKey: ephemeralPublicKey, x25519PrivateKey: userKeyPair.privateKey) let symmetricKey = try AESGCM.generateSymmetricKey(x25519PublicKey: ephemeralPublicKey, x25519PrivateKey: Data(userKeyPair.secretKey))
guard let tokenAsData = try? AESGCM.decrypt(ciphertext, with: symmetricKey) else { throw Error.decryptionFailed } guard let tokenAsData = try? AESGCM.decrypt(ciphertext, with: symmetricKey) else { throw Error.decryptionFailed }
return tokenAsData.toHexString() return tokenAsData.toHexString()
} }

View file

@ -17,10 +17,11 @@ public struct OpenGroupMessageV2 {
public func sign() -> OpenGroupMessageV2? { public func sign() -> OpenGroupMessageV2? {
guard guard
let userKeyPair = Identity.fetchUserKeyPair(), let userKeyPair = Identity.fetchUserKeyPair(),
let legacyKeyPair: ECKeyPair = try? ECKeyPair(publicKeyData: Data(userKeyPair.publicKey), privateKeyData: Data(userKeyPair.secretKey)),
let data: Data = Data(base64Encoded: base64EncodedData) let data: Data = Data(base64Encoded: base64EncodedData)
else { return nil } else { return nil }
guard let signature = try? Ed25519.sign(data, with: userKeyPair) else { guard let signature = try? Ed25519.sign(data, with: legacyKeyPair) else {
SNLog("Failed to sign open group message.") SNLog("Failed to sign open group message.")
return nil return nil
} }

View file

@ -1,3 +1,8 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
@objc(SNDataExtractionNotificationInfoMessage) @objc(SNDataExtractionNotificationInfoMessage)
final class DataExtractionNotificationInfoMessage : TSInfoMessage { final class DataExtractionNotificationInfoMessage : TSInfoMessage {
@ -10,19 +15,23 @@ final class DataExtractionNotificationInfoMessage : TSInfoMessage {
super.init(coder: coder) super.init(coder: coder)
} }
required init(dictionary dictionaryValue: [String:Any]!) throws { required init(dictionary dictionaryValue: [String: Any]!) throws {
try super.init(dictionary: dictionaryValue) try super.init(dictionary: dictionaryValue)
} }
override func previewText(with transaction: YapDatabaseReadTransaction) -> String { override func previewText(with transaction: YapDatabaseReadTransaction) -> String {
guard let thread = thread as? TSContactThread else { return "" } // Should never occur guard let thread = thread as? TSContactThread else { return "" } // Should never occur
let sessionID = thread.contactSessionID()
let displayName = Storage.shared.getContact(with: sessionID)?.displayName(for: .regular) ?? sessionID let displayName = Profile.displayName(for: thread.contactSessionID())
switch messageType { switch messageType {
case .screenshotNotification: return String(format: NSLocalizedString("screenshot_taken", comment: ""), displayName) case .screenshotNotification:
return String(format: NSLocalizedString("screenshot_taken", comment: ""), displayName)
case .mediaSavedNotification: case .mediaSavedNotification:
// TODO: Use referencedAttachmentTimestamp to tell the user * which * media was saved // TODO: Use referencedAttachmentTimestamp to tell the user * which * media was saved
return String(format: NSLocalizedString("meida_saved", comment: ""), displayName) return String(format: NSLocalizedString("meida_saved", comment: ""), displayName)
default: preconditionFailure() default: preconditionFailure()
} }
} }

View file

@ -3,9 +3,10 @@
// //
#import "OWSDisappearingConfigurationUpdateInfoMessage.h" #import "OWSDisappearingConfigurationUpdateInfoMessage.h"
#import "OWSDisappearingMessagesConfiguration.h" #import <SessionMessagingKit/SessionMessagingKit-Swift.h>
#import <SignalCoreKit/NSString+OWS.h> #import <SignalCoreKit/NSString+OWS.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@interface OWSDisappearingConfigurationUpdateInfoMessage () @interface OWSDisappearingConfigurationUpdateInfoMessage ()

View file

@ -1,35 +1,35 @@
////
//// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
////
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. //#import <SessionUtilitiesKit/TSYapDatabaseObject.h>
//#import <SignalCoreKit/SignalCoreKit.h>
// //
//NS_ASSUME_NONNULL_BEGIN
#import <SessionUtilitiesKit/TSYapDatabaseObject.h> //
#import <SignalCoreKit/SignalCoreKit.h> //#define OWSDisappearingMessagesConfigurationDefaultExpirationDuration kDayInterval
//
NS_ASSUME_NONNULL_BEGIN //@class YapDatabaseReadTransaction;
//
#define OWSDisappearingMessagesConfigurationDefaultExpirationDuration kDayInterval //@interface OWSDisappearingMessagesConfiguration : TSYapDatabaseObject
//
@class YapDatabaseReadTransaction; //- (instancetype)initDefaultWithThreadId:(NSString *)threadId;
//
@interface OWSDisappearingMessagesConfiguration : TSYapDatabaseObject //- (instancetype)initWithThreadId:(NSString *)threadId enabled:(BOOL)isEnabled durationSeconds:(uint32_t)seconds;
//
- (instancetype)initDefaultWithThreadId:(NSString *)threadId; //@property (nonatomic, getter=isEnabled) BOOL enabled;
//@property (nonatomic) uint32_t durationSeconds;
- (instancetype)initWithThreadId:(NSString *)threadId enabled:(BOOL)isEnabled durationSeconds:(uint32_t)seconds; //@property (nonatomic, readonly) NSUInteger durationIndex;
//@property (nonatomic, readonly) NSString *durationString;
@property (nonatomic, getter=isEnabled) BOOL enabled; //@property (nonatomic, readonly) BOOL dictionaryValueDidChange;
@property (nonatomic) uint32_t durationSeconds; //@property (readonly, getter=isNewRecord) BOOL newRecord;
@property (nonatomic, readonly) NSUInteger durationIndex; //
@property (nonatomic, readonly) NSString *durationString; //+ (instancetype)fetchOrBuildDefaultWithThreadId:(NSString *)threadId
@property (nonatomic, readonly) BOOL dictionaryValueDidChange; // transaction:(YapDatabaseReadTransaction *)transaction;
@property (readonly, getter=isNewRecord) BOOL newRecord; //
//+ (NSArray<NSNumber *> *)validDurationsSeconds;
+ (instancetype)fetchOrBuildDefaultWithThreadId:(NSString *)threadId //+ (uint32_t)maxDurationSeconds;
transaction:(YapDatabaseReadTransaction *)transaction; //
//@end
+ (NSArray<NSNumber *> *)validDurationsSeconds; //
+ (uint32_t)maxDurationSeconds; //NS_ASSUME_NONNULL_END
@end
NS_ASSUME_NONNULL_END

View file

@ -1,130 +1,130 @@
////
//// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
////
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. //#import "OWSDisappearingMessagesConfiguration.h"
//#import <SignalCoreKit/NSDate+OWS.h>
//#import <SignalCoreKit/NSString+OWS.h>
// //
//NS_ASSUME_NONNULL_BEGIN
#import "OWSDisappearingMessagesConfiguration.h" //
#import <SignalCoreKit/NSDate+OWS.h> //@interface OWSDisappearingMessagesConfiguration ()
#import <SignalCoreKit/NSString+OWS.h> //
//// Transient record lifecycle attributes.
NS_ASSUME_NONNULL_BEGIN //@property (atomic) NSDictionary *originalDictionaryValue;
//@property (atomic, getter=isNewRecord) BOOL newRecord;
@interface OWSDisappearingMessagesConfiguration () //
//@end
// Transient record lifecycle attributes. //
@property (atomic) NSDictionary *originalDictionaryValue; //@implementation OWSDisappearingMessagesConfiguration
@property (atomic, getter=isNewRecord) BOOL newRecord; //
//- (instancetype)initDefaultWithThreadId:(NSString *)threadId
@end //{
// return [self initWithThreadId:threadId
@implementation OWSDisappearingMessagesConfiguration // enabled:NO
// durationSeconds:(NSTimeInterval)OWSDisappearingMessagesConfigurationDefaultExpirationDuration];
- (instancetype)initDefaultWithThreadId:(NSString *)threadId //}
{ //
return [self initWithThreadId:threadId //- (nullable instancetype)initWithCoder:(NSCoder *)coder
enabled:NO //{
durationSeconds:(NSTimeInterval)OWSDisappearingMessagesConfigurationDefaultExpirationDuration]; // self = [super initWithCoder:coder];
} //
// _originalDictionaryValue = [self dictionaryValue];
- (nullable instancetype)initWithCoder:(NSCoder *)coder // _newRecord = NO;
{ //
self = [super initWithCoder:coder]; // return self;
//}
_originalDictionaryValue = [self dictionaryValue]; //
_newRecord = NO; //- (instancetype)initWithThreadId:(NSString *)threadId enabled:(BOOL)isEnabled durationSeconds:(uint32_t)seconds
//{
return self; // self = [super initWithUniqueId:threadId];
} // if (!self) {
// return self;
- (instancetype)initWithThreadId:(NSString *)threadId enabled:(BOOL)isEnabled durationSeconds:(uint32_t)seconds // }
{ //
self = [super initWithUniqueId:threadId]; // _enabled = isEnabled;
if (!self) { // _durationSeconds = seconds;
return self; // _newRecord = YES;
} // _originalDictionaryValue = self.dictionaryValue;
//
_enabled = isEnabled; // return self;
_durationSeconds = seconds; //}
_newRecord = YES; //
_originalDictionaryValue = self.dictionaryValue; //+ (instancetype)fetchOrBuildDefaultWithThreadId:(NSString *)threadId
// transaction:(YapDatabaseReadTransaction *)transaction
return self; //{
} // OWSDisappearingMessagesConfiguration *savedConfiguration =
// [self fetchObjectWithUniqueID:threadId transaction:transaction];
+ (instancetype)fetchOrBuildDefaultWithThreadId:(NSString *)threadId // if (savedConfiguration) {
transaction:(YapDatabaseReadTransaction *)transaction // return savedConfiguration;
{ // } else {
OWSDisappearingMessagesConfiguration *savedConfiguration = // return [[self alloc] initDefaultWithThreadId:threadId];
[self fetchObjectWithUniqueID:threadId transaction:transaction]; // }
if (savedConfiguration) { //}
return savedConfiguration; //
} else { //+ (NSArray<NSNumber *> *)validDurationsSeconds
return [[self alloc] initDefaultWithThreadId:threadId]; //{
} // return @[
} // @(5 * kSecondInterval),
// @(10 * kSecondInterval),
+ (NSArray<NSNumber *> *)validDurationsSeconds // @(30 * kSecondInterval),
{ // @(1 * kMinuteInterval),
return @[ // @(5 * kMinuteInterval),
@(5 * kSecondInterval), // @(30 * kMinuteInterval),
@(10 * kSecondInterval), // @(1 * kHourInterval),
@(30 * kSecondInterval), // @(6 * kHourInterval),
@(1 * kMinuteInterval), // @(12 * kHourInterval),
@(5 * kMinuteInterval), // @(24 * kHourInterval),
@(30 * kMinuteInterval), // @(1 * kWeekInterval)
@(1 * kHourInterval), // ];
@(6 * kHourInterval), //}
@(12 * kHourInterval), //
@(24 * kHourInterval), //+ (uint32_t)maxDurationSeconds
@(1 * kWeekInterval) //{
]; // static uint32_t max;
} // static dispatch_once_t onceToken;
// dispatch_once(&onceToken, ^{
+ (uint32_t)maxDurationSeconds // max = [[self.validDurationsSeconds valueForKeyPath:@"@max.intValue"] unsignedIntValue];
{ // });
static uint32_t max; //
static dispatch_once_t onceToken; // return max;
dispatch_once(&onceToken, ^{ //}
max = [[self.validDurationsSeconds valueForKeyPath:@"@max.intValue"] unsignedIntValue]; //
}); //- (NSUInteger)durationIndex
//{
return max; // return [[self.class validDurationsSeconds] indexOfObject:@(self.durationSeconds)];
} //}
//
- (NSUInteger)durationIndex //- (NSString *)durationString
{ //{
return [[self.class validDurationsSeconds] indexOfObject:@(self.durationSeconds)]; // return [NSString formatDurationSeconds:self.durationSeconds useShortFormat:NO];
} //}
//
- (NSString *)durationString //#pragma mark - Dirty Tracking
{ //
return [NSString formatDurationSeconds:self.durationSeconds useShortFormat:NO]; //+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey
} //{
// // Don't persist transient properties
#pragma mark - Dirty Tracking // if ([propertyKey isEqualToString:@"originalDictionaryValue"]
// ||[propertyKey isEqualToString:@"newRecord"]) {
+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey // return MTLPropertyStorageNone;
{ // } else {
// Don't persist transient properties // return [super storageBehaviorForPropertyWithKey:propertyKey];
if ([propertyKey isEqualToString:@"originalDictionaryValue"] // }
||[propertyKey isEqualToString:@"newRecord"]) { //}
return MTLPropertyStorageNone; //
} else { //- (BOOL)dictionaryValueDidChange
return [super storageBehaviorForPropertyWithKey:propertyKey]; //{
} // return ![self.originalDictionaryValue isEqual:[self dictionaryValue]];
} //}
//
- (BOOL)dictionaryValueDidChange //- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{ //{
return ![self.originalDictionaryValue isEqual:[self dictionaryValue]]; // [super saveWithTransaction:transaction];
} // self.originalDictionaryValue = [self dictionaryValue];
// self.newRecord = NO;
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction //}
{ //
[super saveWithTransaction:transaction]; //@end
self.originalDictionaryValue = [self dictionaryValue]; //
self.newRecord = NO; //NS_ASSUME_NONNULL_END
}
@end
NS_ASSUME_NONNULL_END

View file

@ -188,8 +188,7 @@ void AssertIsOnDisappearingMessagesQueue()
NSString *_Nullable remoteContactName = nil; NSString *_Nullable remoteContactName = nil;
if (remoteRecipientId) { if (remoteRecipientId) {
SNContactContext context = [SNContact contextForThread:thread]; remoteContactName = [SMKProfile displayNameWithId:remoteRecipientId thread:thread];
remoteContactName = [[LKStorage.shared getContactWithSessionID:remoteRecipientId] displayNameFor:context] ?: remoteRecipientId;
} }
// Become eventually consistent in the case that the remote changed their settings at the same time. // Become eventually consistent in the case that the remote changed their settings at the same time.
@ -198,9 +197,9 @@ void AssertIsOnDisappearingMessagesQueue()
[thread disappearingMessagesConfigurationWithTransaction:transaction]; [thread disappearingMessagesConfigurationWithTransaction:transaction];
if (duration == 0) { if (duration == 0) {
disappearingMessagesConfiguration.enabled = NO; disappearingMessagesConfiguration.isEnabled = NO;
} else { } else {
disappearingMessagesConfiguration.enabled = YES; disappearingMessagesConfiguration.isEnabled = YES;
disappearingMessagesConfiguration.durationSeconds = duration; disappearingMessagesConfiguration.durationSeconds = duration;
} }

View file

@ -1,10 +1,9 @@
// // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import AFNetworking import AFNetworking
import Foundation import Foundation
import PromiseKit import PromiseKit
import SignalCoreKit
@objc @objc
public enum LinkPreviewError: Int, Error { public enum LinkPreviewError: Int, Error {

View file

@ -33,9 +33,9 @@ public final class MentionsManager : NSObject {
let openGroupV2 = Storage.shared.getV2OpenGroup(for: threadID) let openGroupV2 = Storage.shared.getV2OpenGroup(for: threadID)
storage.dbReadConnection.read { transaction in storage.dbReadConnection.read { transaction in
candidates = cache.compactMap { publicKey in candidates = cache.compactMap { publicKey in
let context: Contact.Context = (openGroupV2 != nil) ? .openGroup : .regular guard let displayName: String = Profile.displayNameNoFallback(for: publicKey, context: (openGroupV2 != nil ? .openGroup : .regular)) else {
let displayNameOrNil = Storage.shared.getContact(with: publicKey)?.displayName(for: context) return nil
guard let displayName = displayNameOrNil else { return nil } }
guard !displayName.hasPrefix("Anonymous") else { return nil } guard !displayName.hasPrefix("Anonymous") else { return nil }
return Mention(publicKey: publicKey, displayName: displayName) return Mention(publicKey: publicKey, displayName: displayName)
} }

View file

@ -8,9 +8,9 @@ import SessionUtilitiesKit
extension MessageReceiver { extension MessageReceiver {
internal static func decryptWithSessionProtocol(ciphertext: Data, using x25519KeyPair: ECKeyPair) throws -> (plaintext: Data, senderX25519PublicKey: String) { internal static func decryptWithSessionProtocol(ciphertext: Data, using x25519KeyPair: Box.KeyPair) throws -> (plaintext: Data, senderX25519PublicKey: String) {
let recipientX25519PrivateKey = x25519KeyPair.privateKey let recipientX25519PrivateKey = x25519KeyPair.secretKey
let recipientX25519PublicKey = Data(hex: x25519KeyPair.hexEncodedPublicKey.removing05PrefixIfNeeded()) let recipientX25519PublicKey = x25519KeyPair.publicKey
let sodium = Sodium() let sodium = Sodium()
let signatureSize = sodium.sign.Bytes let signatureSize = sodium.sign.Bytes
let ed25519PublicKeySize = sodium.sign.PublicKeyBytes let ed25519PublicKeySize = sodium.sign.PublicKeyBytes

View file

@ -1,23 +1,25 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import GRDB
import Sodium
import Curve25519Kit import Curve25519Kit
import SignalCoreKit import SignalCoreKit
import SessionSnodeKit import SessionSnodeKit
extension MessageReceiver { extension MessageReceiver {
public static func handle(_ message: Message, associatedWithProto proto: SNProtoContent, openGroupID: String?, isBackgroundPoll: Bool, using transaction: Any) throws { public static func handle(_ db: Database, _ message: Message, associatedWithProto proto: SNProtoContent, openGroupID: String?, isBackgroundPoll: Bool, using transaction: Any) throws {
switch message { switch message {
case let message as ReadReceipt: handleReadReceipt(message, using: transaction) case let message as ReadReceipt: handleReadReceipt(message, using: transaction)
case let message as TypingIndicator: handleTypingIndicator(message, using: transaction) case let message as TypingIndicator: handleTypingIndicator(message, using: transaction)
case let message as ClosedGroupControlMessage: handleClosedGroupControlMessage(message, using: transaction) case let message as ClosedGroupControlMessage: handleClosedGroupControlMessage(db, message, using: transaction)
case let message as DataExtractionNotification: handleDataExtractionNotification(message, using: transaction) case let message as DataExtractionNotification: handleDataExtractionNotification(message, using: transaction)
case let message as ExpirationTimerUpdate: handleExpirationTimerUpdate(message, using: transaction) case let message as ExpirationTimerUpdate: handleExpirationTimerUpdate(message, using: transaction)
case let message as ConfigurationMessage: handleConfigurationMessage(message, using: transaction) case let message as ConfigurationMessage: handleConfigurationMessage(db, message, using: transaction)
case let message as UnsendRequest: handleUnsendRequest(message, using: transaction) case let message as UnsendRequest: handleUnsendRequest(message, using: transaction)
case let message as MessageRequestResponse: handleMessageRequestResponse(message, using: transaction) case let message as MessageRequestResponse: handleMessageRequestResponse(db, message, using: transaction)
case let message as VisibleMessage: try handleVisibleMessage(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction) case let message as VisibleMessage: try handleVisibleMessage(db, message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction)
default: fatalError() default: fatalError()
} }
@ -145,11 +147,11 @@ extension MessageReceiver {
threadOrNil = TSContactThread.getWithContactSessionID(syncTarget ?? senderPublicKey, transaction: transaction) threadOrNil = TSContactThread.getWithContactSessionID(syncTarget ?? senderPublicKey, transaction: transaction)
} }
guard let thread = threadOrNil else { return } guard let thread = threadOrNil else { return }
let configuration = OWSDisappearingMessagesConfiguration(threadId: thread.uniqueId!, enabled: true, durationSeconds: duration) let configuration = Legacy.DisappearingMessagesConfiguration(threadId: thread.uniqueId!, enabled: true, durationSeconds: duration)
configuration.save(with: transaction) configuration.save(with: transaction)
var senderDisplayName: String? = nil var senderDisplayName: String? = nil
if senderPublicKey != getUserHexEncodedPublicKey() { if senderPublicKey != getUserHexEncodedPublicKey() {
senderDisplayName = Storage.shared.getContact(with: senderPublicKey)?.displayName(for: .regular) ?? senderPublicKey senderDisplayName = Profile.displayName(for: senderPublicKey)
} }
let message = OWSDisappearingConfigurationUpdateInfoMessage(timestamp: messageSentTimestamp, thread: thread, let message = OWSDisappearingConfigurationUpdateInfoMessage(timestamp: messageSentTimestamp, thread: thread,
configuration: configuration, createdByRemoteName: senderDisplayName, createdInExistingGroup: false) configuration: configuration, createdByRemoteName: senderDisplayName, createdInExistingGroup: false)
@ -168,11 +170,11 @@ extension MessageReceiver {
threadOrNil = TSContactThread.getWithContactSessionID(syncTarget ?? senderPublicKey, transaction: transaction) threadOrNil = TSContactThread.getWithContactSessionID(syncTarget ?? senderPublicKey, transaction: transaction)
} }
guard let thread = threadOrNil else { return } guard let thread = threadOrNil else { return }
let configuration = OWSDisappearingMessagesConfiguration(threadId: thread.uniqueId!, enabled: false, durationSeconds: 24 * 60 * 60) let configuration = Legacy.DisappearingMessagesConfiguration(threadId: thread.uniqueId!, enabled: false, durationSeconds: 24 * 60 * 60)
configuration.save(with: transaction) configuration.save(with: transaction)
var senderDisplayName: String? = nil var senderDisplayName: String? = nil
if senderPublicKey != getUserHexEncodedPublicKey() { if senderPublicKey != getUserHexEncodedPublicKey() {
senderDisplayName = Storage.shared.getContact(with: senderPublicKey)?.displayName(for: .regular) ?? senderPublicKey senderDisplayName = Profile.displayName(for: senderPublicKey)
} }
let message = OWSDisappearingConfigurationUpdateInfoMessage(timestamp: messageSentTimestamp, thread: thread, let message = OWSDisappearingConfigurationUpdateInfoMessage(timestamp: messageSentTimestamp, thread: thread,
configuration: configuration, createdByRemoteName: senderDisplayName, createdInExistingGroup: false) configuration: configuration, createdByRemoteName: senderDisplayName, createdInExistingGroup: false)
@ -184,7 +186,7 @@ extension MessageReceiver {
// MARK: - Configuration Messages // MARK: - Configuration Messages
private static func handleConfigurationMessage(_ message: ConfigurationMessage, using transaction: Any) { private static func handleConfigurationMessage(_ db: Database, _ message: ConfigurationMessage, using transaction: Any) {
let userPublicKey = getUserHexEncodedPublicKey() let userPublicKey = getUserHexEncodedPublicKey()
guard message.sender == userPublicKey else { return } guard message.sender == userPublicKey else { return }
SNLog("Configuration message received.") SNLog("Configuration message received.")
@ -195,10 +197,14 @@ extension MessageReceiver {
let lastConfigTimestamp: TimeInterval = (UserDefaults.standard[.lastConfigurationSync]?.timeIntervalSince1970 ?? Date(timeIntervalSince1970: 0).timeIntervalSince1970) let lastConfigTimestamp: TimeInterval = (UserDefaults.standard[.lastConfigurationSync]?.timeIntervalSince1970 ?? Date(timeIntervalSince1970: 0).timeIntervalSince1970)
// Profile // Profile
var userProfileKey: OWSAES256Key? = nil updateProfileIfNeeded(
if let profileKey = message.profileKey { userProfileKey = OWSAES256Key(data: profileKey) } publicKey: userPublicKey,
updateProfileIfNeeded(publicKey: userPublicKey, name: message.displayName, profilePictureURL: message.profilePictureURL, name: message.displayName,
profileKey: userProfileKey, sentTimestamp: message.sentTimestamp!, transaction: transaction) profilePictureURL: message.profilePictureURL,
profileKey: OWSAES256Key(data: message.profileKey),
sentTimestamp: message.sentTimestamp!,
transaction: transaction
)
if isInitialSync || messageSentTimestamp > lastConfigTimestamp { if isInitialSync || messageSentTimestamp > lastConfigTimestamp {
if isInitialSync { if isInitialSync {
@ -211,11 +217,18 @@ extension MessageReceiver {
// Contacts // Contacts
for contactInfo in message.contacts { for contactInfo in message.contacts {
let sessionID = contactInfo.publicKey! let sessionID = contactInfo.publicKey!
let contact = (Storage.shared.getContact(with: sessionID, using: transaction) ?? Contact(sessionID: sessionID)) let contact: Contact = Contact.fetchOrCreate(db, id: sessionID)
let contactWasBlocked: Bool = contact.isBlocked let profile: Profile = Profile.fetchOrCreate(db, id: sessionID)
if let profileKey = contactInfo.profileKey { contact.profileEncryptionKey = OWSAES256Key(data: profileKey) }
contact.profilePictureURL = contactInfo.profilePictureURL try? profile
contact.name = contactInfo.displayName .with(
name: contactInfo.displayName,
profilePictureUrl: .updateIf(contactInfo.profilePictureURL),
profileEncryptionKey: .updateIf(
contactInfo.profileKey.map { OWSAES256Key(data: $0) }
)
)
.save(db)
// Note: We only update these values if the proto actually has values for them (this is to // Note: 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 // prevent an edge case where an old client could override the values with default values
@ -225,12 +238,22 @@ extension MessageReceiver {
// config message setting *isApproved* and *didApproveMe* to true. This may prevent some // config message setting *isApproved* and *didApproveMe* to true. This may prevent some
// weird edge cases where a config message swapping *isApproved* and *didApproveMe* to // weird edge cases where a config message swapping *isApproved* and *didApproveMe* to
// false. // false.
if contactInfo.hasIsApproved && contactInfo.isApproved { contact.isApproved = true } try? contact
if contactInfo.hasDidApproveMe && contactInfo.didApproveMe { contact.didApproveMe = true } .with(
isApproved: (contactInfo.hasIsApproved && contactInfo.isApproved ?
if contactInfo.hasIsBlocked { contact.isBlocked = contactInfo.isBlocked } .existing :
true
Storage.shared.setContact(contact, using: transaction) ),
isBlocked: (contactInfo.hasIsBlocked && contactInfo.isBlocked ?
.existing :
true
),
didApproveMe: (contactInfo.hasDidApproveMe && contactInfo.didApproveMe ?
.existing :
true
)
)
.save(db)
// If the contact is blocked // If the contact is blocked
if contactInfo.hasIsBlocked && contactInfo.isBlocked { if contactInfo.hasIsBlocked && contactInfo.isBlocked {
@ -238,7 +261,7 @@ extension MessageReceiver {
// associated with them that is a message request thread then delete it (assume // associated with them that is a message request thread then delete it (assume
// that the current user had deleted that message request) // that the current user had deleted that message request)
if if
contactInfo.isBlocked != contactWasBlocked, contactInfo.isBlocked != contact.isBlocked,
let thread: TSContactThread = TSContactThread.getWithContactSessionID(sessionID, transaction: transaction), let thread: TSContactThread = TSContactThread.getWithContactSessionID(sessionID, transaction: transaction),
thread.isMessageRequest(using: transaction) thread.isMessageRequest(using: transaction)
{ {
@ -258,7 +281,11 @@ extension MessageReceiver {
let allClosedGroupPublicKeys = storage.getUserClosedGroupPublicKeys() let allClosedGroupPublicKeys = storage.getUserClosedGroupPublicKeys()
for closedGroup in message.closedGroups { for closedGroup in message.closedGroups {
guard !allClosedGroupPublicKeys.contains(closedGroup.publicKey) else { continue } guard !allClosedGroupPublicKeys.contains(closedGroup.publicKey) else { continue }
handleNewClosedGroup(groupPublicKey: closedGroup.publicKey, name: closedGroup.name, encryptionKeyPair: closedGroup.encryptionKeyPair, let keyPair: Box.KeyPair = Box.KeyPair(
publicKey: closedGroup.encryptionKeyPair.publicKey.bytes,
secretKey: closedGroup.encryptionKeyPair.privateKey.bytes
)
handleNewClosedGroup(db, groupPublicKey: closedGroup.publicKey, name: closedGroup.name, encryptionKeyPair: keyPair,
members: [String](closedGroup.members), admins: [String](closedGroup.admins), expirationTimer: closedGroup.expirationTimer, members: [String](closedGroup.members), admins: [String](closedGroup.admins), expirationTimer: closedGroup.expirationTimer,
messageSentTimestamp: message.sentTimestamp!, using: transaction) messageSentTimestamp: message.sentTimestamp!, using: transaction)
} }
@ -313,7 +340,8 @@ extension MessageReceiver {
// MARK: - Visible Messages // MARK: - Visible Messages
@discardableResult @discardableResult
public static func handleVisibleMessage(_ message: VisibleMessage, associatedWithProto proto: SNProtoContent, openGroupID: String?, isBackgroundPoll: Bool, using transaction: Any) throws -> String { public static func handleVisibleMessage(_ db: Database, _ message: VisibleMessage, associatedWithProto proto: SNProtoContent, openGroupID: String?, isBackgroundPoll: Bool, using transaction: Any) throws -> String {
let sender: String = message.sender!
let storage = SNMessagingKitConfiguration.shared.storage let storage = SNMessagingKitConfiguration.shared.storage
let transaction = transaction as! YapDatabaseReadWriteTransaction let transaction = transaction as! YapDatabaseReadWriteTransaction
var isMainAppAndActive = false var isMainAppAndActive = false
@ -337,7 +365,7 @@ extension MessageReceiver {
profileKey: contactProfileKey, sentTimestamp: message.sentTimestamp!, transaction: transaction) profileKey: contactProfileKey, sentTimestamp: message.sentTimestamp!, transaction: transaction)
} }
// Get or create thread // Get or create thread
guard let threadID = storage.getOrCreateThread(for: message.syncTarget ?? message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: openGroupID, using: transaction) else { throw Error.noThread } guard let threadID = storage.getOrCreateThread(for: message.syncTarget ?? sender, groupPublicKey: message.groupPublicKey, openGroupID: openGroupID, using: transaction) else { throw Error.noThread }
// Parse quote if needed // Parse quote if needed
var tsQuotedMessage: TSQuotedMessage? = nil var tsQuotedMessage: TSQuotedMessage? = nil
if message.quote != nil && proto.dataMessage?.quote != nil, let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) { if message.quote != nil && proto.dataMessage?.quote != nil, let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) {
@ -359,7 +387,9 @@ extension MessageReceiver {
groupPublicKey: message.groupPublicKey, openGroupID: openGroupID, using: transaction) else { throw Error.duplicateMessage } groupPublicKey: message.groupPublicKey, openGroupID: openGroupID, using: transaction) else { throw Error.duplicateMessage }
message.threadID = threadID message.threadID = threadID
// Start attachment downloads if needed // Start attachment downloads if needed
let isContactTrusted = Storage.shared.getContact(with: message.sender!)?.isTrusted ?? false // TODO: Swap this back
let isContactTrusted: Bool = (GRDBStorage.shared.read({ db in try Contact.fetchOne(db, id: sender) })?.isTrusted ?? false)
// let isContactTrusted: Bool = ((try? Contact.fetchOne(db, id: sender))?.isTrusted ?? false)
let isGroup = message.groupPublicKey != nil || openGroupID != nil let isGroup = message.groupPublicKey != nil || openGroupID != nil
attachmentsToDownload.forEach { attachmentID in attachmentsToDownload.forEach { attachmentID in
let downloadJob = AttachmentDownloadJob(attachmentID: attachmentID, tsMessageID: tsMessageID, threadID: threadID) let downloadJob = AttachmentDownloadJob(attachmentID: attachmentID, tsMessageID: tsMessageID, threadID: threadID)
@ -403,10 +433,10 @@ extension MessageReceiver {
// by using the approval process // by using the approval process
if !isGroup, let senderSessionId: String = message.sender { if !isGroup, let senderSessionId: String = message.sender {
updateContactApprovalStatusIfNeeded( updateContactApprovalStatusIfNeeded(
db,
senderSessionId: senderSessionId, senderSessionId: senderSessionId,
threadId: message.threadID, threadId: message.threadID,
forceConfigSync: false, forceConfigSync: false
using: transaction
) )
} }
@ -427,9 +457,10 @@ extension MessageReceiver {
profileKey: OWSAES256Key?, sentTimestamp: UInt64, transaction: YapDatabaseReadWriteTransaction) { profileKey: OWSAES256Key?, sentTimestamp: UInt64, transaction: YapDatabaseReadWriteTransaction) {
let isCurrentUser = (publicKey == getUserHexEncodedPublicKey()) let isCurrentUser = (publicKey == getUserHexEncodedPublicKey())
let userDefaults = UserDefaults.standard let userDefaults = UserDefaults.standard
let contact = Storage.shared.getContact(with: publicKey) ?? Contact(sessionID: publicKey) // New API var profile: Profile = Profile.fetchOrCreate(id: publicKey)
// Name // Name
if let name = name, name != contact.name { if let name = name, name != profile.name {
let shouldUpdate: Bool let shouldUpdate: Bool
if isCurrentUser { if isCurrentUser {
shouldUpdate = given(userDefaults[.lastDisplayNameUpdate]) { sentTimestamp > UInt64($0.timeIntervalSince1970 * 1000) } ?? true shouldUpdate = given(userDefaults[.lastDisplayNameUpdate]) { sentTimestamp > UInt64($0.timeIntervalSince1970 * 1000) } ?? true
@ -440,40 +471,54 @@ extension MessageReceiver {
if isCurrentUser { if isCurrentUser {
userDefaults[.lastDisplayNameUpdate] = Date(timeIntervalSince1970: TimeInterval(sentTimestamp / 1000)) userDefaults[.lastDisplayNameUpdate] = Date(timeIntervalSince1970: TimeInterval(sentTimestamp / 1000))
} }
contact.name = name
profile = profile.with(name: name)
} }
} }
// Profile picture & profile key // Profile picture & profile key
if let profileKey = profileKey, let profilePictureURL = profilePictureURL, if
profileKey.keyData.count == kAES256_KeyByteLength, profileKey != contact.profileEncryptionKey { let profileKey = profileKey,
let profilePictureURL = profilePictureURL,
profileKey.keyData.count == kAES256_KeyByteLength,
profileKey != profile.profileEncryptionKey
{
let shouldUpdate: Bool let shouldUpdate: Bool
if isCurrentUser { if isCurrentUser {
shouldUpdate = given(userDefaults[.lastProfilePictureUpdate]) { sentTimestamp > UInt64($0.timeIntervalSince1970 * 1000) } ?? true shouldUpdate = given(userDefaults[.lastProfilePictureUpdate]) { sentTimestamp > UInt64($0.timeIntervalSince1970 * 1000) } ?? true
} else { } else {
shouldUpdate = true shouldUpdate = true
} }
if shouldUpdate { if shouldUpdate {
if isCurrentUser { if isCurrentUser {
userDefaults[.lastProfilePictureUpdate] = Date(timeIntervalSince1970: TimeInterval(sentTimestamp / 1000)) userDefaults[.lastProfilePictureUpdate] = Date(timeIntervalSince1970: TimeInterval(sentTimestamp / 1000))
} }
contact.profilePictureURL = profilePictureURL
contact.profileEncryptionKey = profileKey profile = profile.with(
profilePictureUrl: .update(profilePictureURL),
profileEncryptionKey: .update(profileKey)
)
} }
} }
// Persist changes // Persist changes
Storage.shared.setContact(contact, using: transaction) GRDBStorage.shared.write { db in
try profile.save(db)
}
// Download the profile picture if needed // Download the profile picture if needed
transaction.addCompletionQueue(DispatchQueue.main) { transaction.addCompletionQueue(DispatchQueue.main) {
SSKEnvironment.shared.profileManager.downloadAvatar(forUserProfile: contact) ProfileManager.downloadAvatar(for: profile)
} }
} }
// MARK: - Closed Groups // MARK: - Closed Groups
public static func handleClosedGroupControlMessage(_ message: ClosedGroupControlMessage, using transaction: Any) { public static func handleClosedGroupControlMessage(_ db: Database, _ message: ClosedGroupControlMessage, using transaction: Any) {
switch message.kind! { switch message.kind! {
case .new: handleNewClosedGroup(message, using: transaction) case .new: handleNewClosedGroup(db, message, using: transaction)
case .encryptionKeyPair: handleClosedGroupEncryptionKeyPair(message, using: transaction) case .encryptionKeyPair: handleClosedGroupEncryptionKeyPair(message, using: transaction)
case .nameChange: handleClosedGroupNameChanged(message, using: transaction) case .nameChange: handleClosedGroupNameChanged(message, using: transaction)
case .membersAdded: handleClosedGroupMembersAdded(message, using: transaction) case .membersAdded: handleClosedGroupMembersAdded(message, using: transaction)
@ -483,16 +528,16 @@ extension MessageReceiver {
} }
} }
private static func handleNewClosedGroup(_ message: ClosedGroupControlMessage, using transaction: Any) { private static func handleNewClosedGroup(_ db: Database, _ message: ClosedGroupControlMessage, using transaction: Any) {
guard case let .new(publicKeyAsData, name, encryptionKeyPair, membersAsData, adminsAsData, expirationTimer) = message.kind else { return } guard case let .new(publicKeyAsData, name, encryptionKeyPair, membersAsData, adminsAsData, expirationTimer) = message.kind else { return }
let groupPublicKey = publicKeyAsData.toHexString() let groupPublicKey = publicKeyAsData.toHexString()
let members = membersAsData.map { $0.toHexString() } let members = membersAsData.map { $0.toHexString() }
let admins = adminsAsData.map { $0.toHexString() } let admins = adminsAsData.map { $0.toHexString() }
handleNewClosedGroup(groupPublicKey: groupPublicKey, name: name, encryptionKeyPair: encryptionKeyPair, handleNewClosedGroup(db, groupPublicKey: groupPublicKey, name: name, encryptionKeyPair: encryptionKeyPair,
members: members, admins: admins, expirationTimer: expirationTimer, messageSentTimestamp: message.sentTimestamp!, using: transaction) members: members, admins: admins, expirationTimer: expirationTimer, messageSentTimestamp: message.sentTimestamp!, using: transaction)
} }
private static func handleNewClosedGroup(groupPublicKey: String, name: String, encryptionKeyPair: ECKeyPair, members: [String], admins: [String], expirationTimer: UInt32, messageSentTimestamp: UInt64, using transaction: Any) { private static func handleNewClosedGroup(_ db: Database, groupPublicKey: String, name: String, encryptionKeyPair: Box.KeyPair, members: [String], admins: [String], expirationTimer: UInt32, messageSentTimestamp: UInt64, using transaction: Any) {
let transaction = transaction as! YapDatabaseReadWriteTransaction let transaction = transaction as! YapDatabaseReadWriteTransaction
// With new closed groups we only want to create them if the admin creating the closed group is an // With new closed groups we only want to create them if the admin creating the closed group is an
@ -501,7 +546,7 @@ extension MessageReceiver {
var hasApprovedAdmin: Bool = false var hasApprovedAdmin: Bool = false
for adminId in admins { for adminId in admins {
if let contact: Contact = Storage.shared.getContact(with: adminId), contact.isApproved { if let contact: Contact = try? Contact.fetchOne(db, id: adminId), contact.isApproved {
hasApprovedAdmin = true hasApprovedAdmin = true
break break
} }
@ -533,7 +578,7 @@ extension MessageReceiver {
let isExpirationTimerEnabled = (expirationTimer > 0) let isExpirationTimerEnabled = (expirationTimer > 0)
let expirationTimerDuration = (isExpirationTimerEnabled ? expirationTimer : 24 * 60 * 60) let expirationTimerDuration = (isExpirationTimerEnabled ? expirationTimer : 24 * 60 * 60)
let configuration = OWSDisappearingMessagesConfiguration( let configuration = Legacy.DisappearingMessagesConfiguration(
threadId: thread.uniqueId!, threadId: thread.uniqueId!,
enabled: isExpirationTimerEnabled, enabled: isExpirationTimerEnabled,
durationSeconds: expirationTimerDuration durationSeconds: expirationTimerDuration
@ -560,7 +605,7 @@ extension MessageReceiver {
let groupPublicKey = explicitGroupPublicKey?.toHexString() ?? message.groupPublicKey else { return } let groupPublicKey = explicitGroupPublicKey?.toHexString() ?? message.groupPublicKey else { return }
let transaction = transaction as! YapDatabaseReadWriteTransaction let transaction = transaction as! YapDatabaseReadWriteTransaction
let userPublicKey = getUserHexEncodedPublicKey() let userPublicKey = getUserHexEncodedPublicKey()
guard let userKeyPair = Identity.fetchUserKeyPair() else { guard let userKeyPair: Box.KeyPair = Identity.fetchUserKeyPair() else {
return SNLog("Couldn't find user X25519 key pair.") return SNLog("Couldn't find user X25519 key pair.")
} }
let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey)
@ -586,12 +631,10 @@ extension MessageReceiver {
} catch { } catch {
return SNLog("Couldn't parse closed group encryption key pair.") return SNLog("Couldn't parse closed group encryption key pair.")
} }
let keyPair: ECKeyPair let keyPair: Box.KeyPair = Box.KeyPair(
do { publicKey: proto.publicKey.removing05PrefixIfNeeded().bytes,
keyPair = try ECKeyPair(publicKeyData: proto.publicKey.removing05PrefixIfNeeded(), privateKeyData: proto.privateKey) secretKey: proto.privateKey.bytes
} catch { )
return SNLog("Couldn't parse closed group encryption key pair.")
}
// Store it if needed // Store it if needed
let closedGroupEncryptionKeyPairs = Storage.shared.getClosedGroupEncryptionKeyPairs(for: groupPublicKey) let closedGroupEncryptionKeyPairs = Storage.shared.getClosedGroupEncryptionKeyPairs(for: groupPublicKey)
guard !closedGroupEncryptionKeyPairs.contains(keyPair) else { guard !closedGroupEncryptionKeyPairs.contains(keyPair) else {
@ -711,6 +754,8 @@ extension MessageReceiver {
/// Unsubscribe from PNs, delete the group public key, etc. as the group will be disbanded. /// Unsubscribe from PNs, delete the group public key, etc. as the group will be disbanded.
private static func handleClosedGroupMemberLeft(_ message: ClosedGroupControlMessage, using transaction: Any) { private static func handleClosedGroupMemberLeft(_ message: ClosedGroupControlMessage, using transaction: Any) {
guard case .memberLeft = message.kind else { return } guard case .memberLeft = message.kind else { return }
let sender: String = message.sender!
let transaction = transaction as! YapDatabaseReadWriteTransaction let transaction = transaction as! YapDatabaseReadWriteTransaction
guard let groupPublicKey = message.groupPublicKey else { return } guard let groupPublicKey = message.groupPublicKey else { return }
performIfValid(for: message, using: transaction) { groupID, thread, group in performIfValid(for: message, using: transaction) { groupID, thread, group in
@ -731,13 +776,16 @@ extension MessageReceiver {
thread.setGroupModel(newGroupModel, with: transaction) thread.setGroupModel(newGroupModel, with: transaction)
// Notify the user if needed // Notify the user if needed
guard members != Set(group.groupMemberIds) else { return } guard members != Set(group.groupMemberIds) else { return }
let contact = Storage.shared.getContact(with: message.sender!)
let updateInfo: String let updateInfo: String
if let displayName = contact?.displayName(for: Contact.Context.regular) {
if let displayName = Profile.displayNameNoFallback(for: sender) {
updateInfo = String(format: NSLocalizedString("GROUP_MEMBER_LEFT", comment: ""), displayName) updateInfo = String(format: NSLocalizedString("GROUP_MEMBER_LEFT", comment: ""), displayName)
} else { }
else {
updateInfo = NSLocalizedString("GROUP_UPDATED", comment: "") updateInfo = NSLocalizedString("GROUP_UPDATED", comment: "")
} }
let infoMessage = TSInfoMessage(timestamp: message.sentTimestamp!, in: thread, messageType: .groupUpdated, customMessage: updateInfo) let infoMessage = TSInfoMessage(timestamp: message.sentTimestamp!, in: thread, messageType: .groupUpdated, customMessage: updateInfo)
infoMessage.save(with: transaction) infoMessage.save(with: transaction)
} }
@ -787,14 +835,12 @@ extension MessageReceiver {
// MARK: - Message Requests // MARK: - Message Requests
private static func updateContactApprovalStatusIfNeeded( private static func updateContactApprovalStatusIfNeeded(
_ db: Database,
senderSessionId: String, senderSessionId: String,
threadId: String?, threadId: String?,
forceConfigSync: Bool, forceConfigSync: Bool
using transaction: Any
) { ) {
guard let transaction: YapDatabaseReadWriteTransaction = transaction as? YapDatabaseReadWriteTransaction else { return } let userPublicKey: String = getUserHexEncodedPublicKey(db)
let userPublicKey: String = getUserHexEncodedPublicKey()
// If the sender of the message was the current user // If the sender of the message was the current user
if senderSessionId == userPublicKey { if senderSessionId == userPublicKey {
@ -824,8 +870,8 @@ extension MessageReceiver {
MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete() MessageSender.syncConfiguration(forceSyncNow: true).retainUntilComplete()
} }
public static func handleMessageRequestResponse(_ message: MessageRequestResponse, using transaction: Any) { public static func handleMessageRequestResponse(_ db: Database, _ message: MessageRequestResponse, using transaction: Any) {
let userPublicKey = getUserHexEncodedPublicKey() let userPublicKey = getUserHexEncodedPublicKey(db)
// Ignore messages which were sent from the current user // Ignore messages which were sent from the current user
guard message.sender != userPublicKey else { return } guard message.sender != userPublicKey else { return }
@ -842,10 +888,10 @@ extension MessageReceiver {
} }
updateContactApprovalStatusIfNeeded( updateContactApprovalStatusIfNeeded(
db,
senderSessionId: senderId, senderSessionId: senderId,
threadId: nil, threadId: nil,
forceConfigSync: true, forceConfigSync: true
using: transaction
) )
} }
} }

View file

@ -1,6 +1,8 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import GRDB
import Sodium
import SessionUtilitiesKit import SessionUtilitiesKit
public enum MessageReceiver { public enum MessageReceiver {
@ -51,7 +53,7 @@ public enum MessageReceiver {
} }
} }
public static func parse(_ data: Data, openGroupMessageServerID: UInt64?, isRetry: Bool = false, using transaction: Any) throws -> (Message, SNProtoContent) { public static func parse(_ db: Database, _ data: Data, openGroupMessageServerID: UInt64?, isRetry: Bool = false, using transaction: Any) throws -> (Message, SNProtoContent) {
let userPublicKey = getUserHexEncodedPublicKey() let userPublicKey = getUserHexEncodedPublicKey()
let isOpenGroupMessage = (openGroupMessageServerID != nil) let isOpenGroupMessage = (openGroupMessageServerID != nil)
// Parse the envelope // Parse the envelope
@ -67,7 +69,7 @@ public enum MessageReceiver {
} else { } else {
switch envelope.type { switch envelope.type {
case .sessionMessage: case .sessionMessage:
guard let userX25519KeyPair = Identity.fetchUserKeyPair() else { throw Error.noUserX25519KeyPair } guard let userX25519KeyPair: Box.KeyPair = Identity.fetchUserKeyPair() else { throw Error.noUserX25519KeyPair }
(plaintext, sender) = try decryptWithSessionProtocol(ciphertext: ciphertext, using: userX25519KeyPair) (plaintext, sender) = try decryptWithSessionProtocol(ciphertext: ciphertext, using: userX25519KeyPair)
case .closedGroupMessage: case .closedGroupMessage:
guard let hexEncodedGroupPublicKey = envelope.source, SNMessagingKitConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey) else { throw Error.invalidGroupPublicKey } guard let hexEncodedGroupPublicKey = envelope.source, SNMessagingKitConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey) else { throw Error.invalidGroupPublicKey }
@ -111,7 +113,8 @@ public enum MessageReceiver {
} }
// Don't process the envelope any further if the sender is blocked // Don't process the envelope any further if the sender is blocked
guard Storage.shared.getContact(with: sender, using: transaction)?.isBlocked != true else { guard GRDBStorage.shared.read({ db in try Contact.fetchOne(db, id: sender) })?.isBlocked != true else {
// guard (try? Contact.fetchOne(db, id: sender))?.isBlocked != true else {
throw Error.senderBlocked throw Error.senderBlocked
} }
@ -125,15 +128,15 @@ public enum MessageReceiver {
} }
// Parse the message // Parse the message
let message: Message? = { let message: Message? = {
if let readReceipt = ReadReceipt.fromProto(proto) { return readReceipt } if let readReceipt = ReadReceipt.fromProto(proto, sender: sender) { return readReceipt }
if let typingIndicator = TypingIndicator.fromProto(proto) { return typingIndicator } if let typingIndicator = TypingIndicator.fromProto(proto, sender: sender) { return typingIndicator }
if let closedGroupControlMessage = ClosedGroupControlMessage.fromProto(proto) { return closedGroupControlMessage } if let closedGroupControlMessage = ClosedGroupControlMessage.fromProto(proto, sender: sender) { return closedGroupControlMessage }
if let dataExtractionNotification = DataExtractionNotification.fromProto(proto) { return dataExtractionNotification } if let dataExtractionNotification = DataExtractionNotification.fromProto(proto, sender: sender) { return dataExtractionNotification }
if let expirationTimerUpdate = ExpirationTimerUpdate.fromProto(proto) { return expirationTimerUpdate } if let expirationTimerUpdate = ExpirationTimerUpdate.fromProto(proto, sender: sender) { return expirationTimerUpdate }
if let configurationMessage = ConfigurationMessage.fromProto(proto) { return configurationMessage } if let configurationMessage = ConfigurationMessage.fromProto(proto, sender: sender) { return configurationMessage }
if let unsendRequest = UnsendRequest.fromProto(proto) { return unsendRequest } if let unsendRequest = UnsendRequest.fromProto(proto, sender: sender) { return unsendRequest }
if let messageRequestResponse = MessageRequestResponse.fromProto(proto) { return messageRequestResponse } if let messageRequestResponse = MessageRequestResponse.fromProto(proto, sender: sender) { return messageRequestResponse }
if let visibleMessage = VisibleMessage.fromProto(proto) { return visibleMessage } if let visibleMessage = VisibleMessage.fromProto(proto, sender: sender) { return visibleMessage }
return nil return nil
}() }()
if let message = message { if let message = message {

View file

@ -1,11 +1,12 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import Sodium
import Curve25519Kit import Curve25519Kit
import PromiseKit import PromiseKit
extension MessageSender { extension MessageSender {
public static var distributingClosedGroupEncryptionKeyPairs: [String: [ECKeyPair]] = [:] public static var distributingClosedGroupEncryptionKeyPairs: [String: [Box.KeyPair]] = [:]
public static func createClosedGroup(name: String, members: Set<String>, transaction: YapDatabaseReadWriteTransaction) -> Promise<TSGroupThread> { public static func createClosedGroup(name: String, members: Set<String>, transaction: YapDatabaseReadWriteTransaction) -> Promise<TSGroupThread> {
// Prepare // Prepare
@ -30,8 +31,12 @@ extension MessageSender {
for member in members { for member in members {
let thread = TSContactThread.getOrCreateThread(withContactSessionID: member, transaction: transaction) let thread = TSContactThread.getOrCreateThread(withContactSessionID: member, transaction: transaction)
thread.save(with: transaction) thread.save(with: transaction)
let keyPair: Box.KeyPair = Box.KeyPair(
publicKey: encryptionKeyPair.publicKey.bytes,
secretKey: encryptionKeyPair.privateKey.bytes
)
let closedGroupControlMessageKind = ClosedGroupControlMessage.Kind.new(publicKey: Data(hex: groupPublicKey), name: name, let closedGroupControlMessageKind = ClosedGroupControlMessage.Kind.new(publicKey: Data(hex: groupPublicKey), name: name,
encryptionKeyPair: encryptionKeyPair, members: membersAsData, admins: adminsAsData, expirationTimer: 0) encryptionKeyPair: keyPair, members: membersAsData, admins: adminsAsData, expirationTimer: 0)
let closedGroupControlMessage = ClosedGroupControlMessage(kind: closedGroupControlMessageKind) let closedGroupControlMessage = ClosedGroupControlMessage(kind: closedGroupControlMessageKind)
// Sending this non-durably is okay because we show a loader to the user. If they close the app while the // Sending this non-durably is okay because we show a loader to the user. If they close the app while the
// loader is still showing, it's within expectation that the group creation might be incomplete. // loader is still showing, it's within expectation that the group creation might be incomplete.
@ -41,7 +46,11 @@ extension MessageSender {
// Add the group to the user's set of public keys to poll for // Add the group to the user's set of public keys to poll for
Storage.shared.addClosedGroupPublicKey(groupPublicKey, using: transaction) Storage.shared.addClosedGroupPublicKey(groupPublicKey, using: transaction)
// Store the key pair // Store the key pair
Storage.shared.addClosedGroupEncryptionKeyPair(encryptionKeyPair, for: groupPublicKey, using: transaction) let keyPair: Box.KeyPair = Box.KeyPair(
publicKey: encryptionKeyPair.publicKey.bytes,
secretKey: encryptionKeyPair.privateKey.bytes
)
Storage.shared.addClosedGroupEncryptionKeyPair(keyPair, for: groupPublicKey, using: transaction)
// Notify the PN server // Notify the PN server
promises.append(PushNotificationAPI.performOperation(.subscribe, for: groupPublicKey, publicKey: userPublicKey)) promises.append(PushNotificationAPI.performOperation(.subscribe, for: groupPublicKey, publicKey: userPublicKey))
// Notify the user // Notify the user
@ -72,10 +81,14 @@ extension MessageSender {
return Promise(error: Error.invalidClosedGroupUpdate) return Promise(error: Error.invalidClosedGroupUpdate)
} }
// Generate the new encryption key pair // Generate the new encryption key pair
let newKeyPair = Curve25519.generateKeyPair() let newLegacyKeyPair = Curve25519.generateKeyPair()
let newKeyPair: Box.KeyPair = Box.KeyPair(
publicKey: newLegacyKeyPair.publicKey.bytes,
secretKey: newLegacyKeyPair.privateKey.bytes
)
// Distribute it // Distribute it
let proto = try! SNProtoKeyPair.builder(publicKey: newKeyPair.publicKey, let proto = try! SNProtoKeyPair.builder(publicKey: Data(newKeyPair.publicKey),
privateKey: newKeyPair.privateKey).build() privateKey: Data(newKeyPair.secretKey)).build()
let plaintext = try! proto.serializedData() let plaintext = try! proto.serializedData()
let wrappers = targetMembers.compactMap { publicKey -> ClosedGroupControlMessage.KeyPairWrapper in let wrappers = targetMembers.compactMap { publicKey -> ClosedGroupControlMessage.KeyPairWrapper in
let ciphertext = try! MessageSender.encryptWithSessionProtocol(plaintext, for: publicKey) let ciphertext = try! MessageSender.encryptWithSessionProtocol(plaintext, for: publicKey)
@ -329,8 +342,8 @@ extension MessageSender {
guard let encryptionKeyPair = distributingClosedGroupEncryptionKeyPairs[groupPublicKey]?.last guard let encryptionKeyPair = distributingClosedGroupEncryptionKeyPairs[groupPublicKey]?.last
?? Storage.shared.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey) else { return } ?? Storage.shared.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey) else { return }
// Send it // Send it
guard let proto = try? SNProtoKeyPair.builder(publicKey: encryptionKeyPair.publicKey, guard let proto = try? SNProtoKeyPair.builder(publicKey: Data(encryptionKeyPair.publicKey),
privateKey: encryptionKeyPair.privateKey).build(), let plaintext = try? proto.serializedData() else { return } privateKey: Data(encryptionKeyPair.secretKey)).build(), let plaintext = try? proto.serializedData() else { return }
let contactThread = TSContactThread.getOrCreateThread(withContactSessionID: publicKey, transaction: transaction) let contactThread = TSContactThread.getOrCreateThread(withContactSessionID: publicKey, transaction: transaction)
guard let ciphertext = try? MessageSender.encryptWithSessionProtocol(plaintext, for: publicKey) else { return } guard let ciphertext = try? MessageSender.encryptWithSessionProtocol(plaintext, for: publicKey) else { return }
SNLog("Sending latest encryption key pair to: \(publicKey).") SNLog("Sending latest encryption key pair to: \(publicKey).")

View file

@ -171,7 +171,7 @@ public final class MessageSender : NSObject {
case .contact(let publicKey): ciphertext = try encryptWithSessionProtocol(plaintext, for: publicKey) case .contact(let publicKey): ciphertext = try encryptWithSessionProtocol(plaintext, for: publicKey)
case .closedGroup(let groupPublicKey): case .closedGroup(let groupPublicKey):
guard let encryptionKeyPair = Storage.shared.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey) else { throw Error.noKeyPair } guard let encryptionKeyPair = Storage.shared.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey) else { throw Error.noKeyPair }
ciphertext = try encryptWithSessionProtocol(plaintext, for: encryptionKeyPair.hexEncodedPublicKey) ciphertext = try encryptWithSessionProtocol(plaintext, for: "05\(encryptionKeyPair.publicKey.toHexString())")
case .openGroup(_, _), .openGroupV2(_, _): preconditionFailure() case .openGroup(_, _), .openGroupV2(_, _): preconditionFailure()
} }
} catch { } catch {

View file

@ -65,6 +65,7 @@ public final class OpenGroupPollerV2 : NSObject {
// Sorting the messages by server ID before importing them fixes an issue where messages that quote older messages can't find those older messages // Sorting the messages by server ID before importing them fixes an issue where messages that quote older messages can't find those older messages
let openGroupID = "\(server).\(body.room)" let openGroupID = "\(server).\(body.room)"
let messages = body.messages.sorted { $0.serverID! < $1.serverID! } // Safe because messages with a nil serverID are filtered out let messages = body.messages.sorted { $0.serverID! < $1.serverID! } // Safe because messages with a nil serverID are filtered out
GRDBStorage.shared.write { db in
storage.write { transaction in storage.write { transaction in
messages.forEach { message in messages.forEach { message in
guard let data = Data(base64Encoded: message.base64EncodedData) else { guard let data = Data(base64Encoded: message.base64EncodedData) else {
@ -75,13 +76,14 @@ public final class OpenGroupPollerV2 : NSObject {
envelope.setSource(message.sender!) // Safe because messages with a nil sender are filtered out envelope.setSource(message.sender!) // Safe because messages with a nil sender are filtered out
do { do {
let data = try envelope.buildSerializedData() let data = try envelope.buildSerializedData()
let (message, proto) = try MessageReceiver.parse(data, openGroupMessageServerID: UInt64(message.serverID!), isRetry: false, using: transaction) let (message, proto) = try MessageReceiver.parse(db, data, openGroupMessageServerID: UInt64(message.serverID!), isRetry: false, using: transaction)
try MessageReceiver.handle(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction) try MessageReceiver.handle(db, message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction)
} catch { } catch {
SNLog("Couldn't receive open group message due to error: \(error).") SNLog("Couldn't receive open group message due to error: \(error).")
} }
} }
} }
}
// - Moderators // - Moderators
if var x = OpenGroupAPIV2.moderators[server] { if var x = OpenGroupAPIV2.moderators[server] {

View file

@ -12,13 +12,6 @@ public protocol SessionMessagingKitStorageProtocol {
func write(with block: @escaping (Any) -> Void, completion: @escaping () -> Void) -> Promise<Void> func write(with block: @escaping (Any) -> Void, completion: @escaping () -> Void) -> Promise<Void>
func writeSync(with block: @escaping (Any) -> Void) func writeSync(with block: @escaping (Any) -> Void)
// MARK: - General
func getUser() -> Contact?
func getUser(using transaction: YapDatabaseReadTransaction?) -> Contact?
func getAllContacts() -> Set<Contact>
func getAllContacts(with transaction: YapDatabaseReadTransaction) -> Set<Contact>
// MARK: - Closed Groups // MARK: - Closed Groups
func getUserClosedGroupPublicKeys() -> Set<String> func getUserClosedGroupPublicKeys() -> Set<String>
@ -95,4 +88,4 @@ public protocol SessionMessagingKitStorageProtocol {
func persist(_ stream: TSAttachmentStream, associatedWith tsIncomingMessageID: String, using transaction: Any) func persist(_ stream: TSAttachmentStream, associatedWith tsIncomingMessageID: String, using transaction: Any)
} }
extension Storage: SessionMessagingKitStorageProtocol, SessionSnodeKitStorageProtocol {} extension Storage: SessionMessagingKitStorageProtocol {}

View file

@ -59,7 +59,7 @@ NSString *const TSContactThreadPrefix = @"c";
- (BOOL)isMessageRequest { - (BOOL)isMessageRequest {
NSString *sessionID = self.contactSessionID; NSString *sessionID = self.contactSessionID;
SNContact *contact = [LKStorage.shared getContactWithSessionID:sessionID]; SMKContact *contact = [SMKContact fetchOrCreateWithId: sessionID];
return ( return (
self.shouldBeVisible && self.shouldBeVisible &&
@ -72,7 +72,7 @@ NSString *const TSContactThreadPrefix = @"c";
- (BOOL)isMessageRequestUsingTransaction:(YapDatabaseReadTransaction *)transaction { - (BOOL)isMessageRequestUsingTransaction:(YapDatabaseReadTransaction *)transaction {
NSString *sessionID = self.contactSessionID; NSString *sessionID = self.contactSessionID;
SNContact *contact = [LKStorage.shared getContactWithSessionID:sessionID using:transaction]; SMKContact *contact = [SMKContact fetchOrCreateWithId: sessionID];
return ( return (
self.shouldBeVisible && self.shouldBeVisible &&
@ -85,14 +85,14 @@ NSString *const TSContactThreadPrefix = @"c";
- (BOOL)isBlocked { - (BOOL)isBlocked {
NSString *sessionID = self.contactSessionID; NSString *sessionID = self.contactSessionID;
SNContact *contact = [LKStorage.shared getContactWithSessionID:sessionID]; SMKContact *contact = [SMKContact fetchOrCreateWithId: sessionID];
return (contact.isBlocked == YES); return (contact.isBlocked == YES);
} }
- (BOOL)isBlockedUsingTransaction:(YapDatabaseReadTransaction *)transaction { - (BOOL)isBlockedUsingTransaction:(YapDatabaseReadTransaction *)transaction {
NSString *sessionID = self.contactSessionID; NSString *sessionID = self.contactSessionID;
SNContact *contact = [LKStorage.shared getContactWithSessionID:sessionID using:transaction]; SMKContact *contact = [SMKContact fetchOrCreateWithId: sessionID];
return (contact.isBlocked == YES); return (contact.isBlocked == YES);
} }
@ -105,15 +105,13 @@ NSString *const TSContactThreadPrefix = @"c";
- (NSString *)name - (NSString *)name
{ {
NSString *sessionID = self.contactSessionID; NSString *sessionID = self.contactSessionID;
SNContact *contact = [LKStorage.shared getContactWithSessionID:sessionID]; return [SMKProfile displayNameWithId:sessionID];
return [contact displayNameFor:SNContactContextRegular] ?: sessionID;
} }
- (NSString *)nameWithTransaction:(YapDatabaseReadTransaction *)transaction - (NSString *)nameWithTransaction:(YapDatabaseReadTransaction *)transaction
{ {
NSString *sessionID = self.contactSessionID; NSString *sessionID = self.contactSessionID;
SNContact *contact = [LKStorage.shared getContactWithSessionID:sessionID using:transaction]; return [SMKProfile displayNameWithId:sessionID];
return [contact displayNameFor:SNContactContextRegular] ?: sessionID;
} }
+ (NSString *)threadIDFromContactSessionID:(NSString *)contactSessionID { + (NSString *)threadIDFromContactSessionID:(NSString *)contactSessionID {

View file

@ -123,8 +123,7 @@ const int32_t kGroupIdLength = 16;
if (removedMembersMinusSelf.count > 0) { if (removedMembersMinusSelf.count > 0) {
NSArray *removedMemberNames = [removedMembers.allObjects map:^NSString *(NSString *publicKey) { NSArray *removedMemberNames = [removedMembers.allObjects map:^NSString *(NSString *publicKey) {
SNContact *contact = [LKStorage.shared getContactWithSessionID:publicKey]; return [SMKProfile displayNameWithId:publicKey];
return [contact displayNameFor:SNContactContextRegular] ?: publicKey;
}]; }];
NSString *format = removedMembers.count > 1 ? NSLocalizedString(@"GROUP_MEMBERS_REMOVED", @"") : NSLocalizedString(@"GROUP_MEMBER_REMOVED", @""); NSString *format = removedMembers.count > 1 ? NSLocalizedString(@"GROUP_MEMBERS_REMOVED", @"") : NSLocalizedString(@"GROUP_MEMBER_REMOVED", @"");
updatedGroupInfoString = [updatedGroupInfoString updatedGroupInfoString = [updatedGroupInfoString
@ -135,8 +134,7 @@ const int32_t kGroupIdLength = 16;
if (addedMembers.count > 0) { if (addedMembers.count > 0) {
NSArray *addedMemberNames = [[addedMembers allObjects] map:^NSString*(NSString* publicKey) { NSArray *addedMemberNames = [[addedMembers allObjects] map:^NSString*(NSString* publicKey) {
SNContact *contact = [LKStorage.shared getContactWithSessionID:publicKey]; return [SMKProfile displayNameWithId:publicKey];
return [contact displayNameFor:SNContactContextRegular] ?: publicKey;
}]; }];
updatedGroupInfoString = [updatedGroupInfoString updatedGroupInfoString = [updatedGroupInfoString
stringByAppendingString:[NSString stringByAppendingString:[NSString

View file

@ -22,6 +22,7 @@ BOOL IsNoteToSelfEnabled(void);
@property (nonatomic, readonly, nullable) NSDate *lastInteractionDate; @property (nonatomic, readonly, nullable) NSDate *lastInteractionDate;
@property (nonatomic, readonly) TSInteraction *lastInteraction; @property (nonatomic, readonly) TSInteraction *lastInteraction;
@property (atomic, readonly) BOOL isMuted; @property (atomic, readonly) BOOL isMuted;
@property (nonatomic, copy, nullable) NSString *messageDraft;
@property (atomic, readonly, nullable) NSDate *mutedUntilDate; @property (atomic, readonly, nullable) NSDate *mutedUntilDate;
/** /**

View file

@ -23,7 +23,6 @@ BOOL IsNoteToSelfEnabled(void)
@property (nonatomic) NSDate *creationDate; @property (nonatomic) NSDate *creationDate;
@property (nonatomic, nullable) NSDate *lastInteractionDate; @property (nonatomic, nullable) NSDate *lastInteractionDate;
@property (nonatomic, nullable) NSNumber *archivedAsOfMessageSortId; @property (nonatomic, nullable) NSNumber *archivedAsOfMessageSortId;
@property (nonatomic, copy, nullable) NSString *messageDraft;
@property (atomic, nullable) NSDate *mutedUntilDate; @property (atomic, nullable) NSDate *mutedUntilDate;
@end @end

View file

@ -6,9 +6,9 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
extern NSString *const kNSNotificationName_LocalProfileDidChange; //extern NSString *const kNSNotificationName_LocalProfileDidChange;
extern NSString *const kNSNotificationName_OtherUsersProfileDidChange; //extern NSString *const kNSNotificationName_OtherUsersProfileDidChange;
extern NSString *const kNSNotificationKey_ProfileRecipientId; //extern NSString *const kNSNotificationKey_ProfileRecipientId;
@interface OWSUserProfile : TSYapDatabaseObject @interface OWSUserProfile : TSYapDatabaseObject
@ -18,7 +18,7 @@ extern NSString *const kNSNotificationKey_ProfileRecipientId;
+ (NSString *)sharedDataProfileAvatarsDirPath; + (NSString *)sharedDataProfileAvatarsDirPath;
+ (NSString *)profileAvatarsDirPath; + (NSString *)profileAvatarsDirPath;
+ (void)resetProfileStorage; + (void)resetProfileStorage;
+ (NSSet<NSString *> *)allProfileAvatarFilePaths; //+ (NSSet<NSString *> *)allProfileAvatarFilePaths;
@end @end

View file

@ -18,10 +18,6 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
NSString *const kNSNotificationName_LocalProfileDidChange = @"kNSNotificationName_LocalProfileDidChange";
NSString *const kNSNotificationName_OtherUsersProfileDidChange = @"kNSNotificationName_OtherUsersProfileDidChange";
NSString *const kNSNotificationKey_ProfileRecipientId = @"kNSNotificationKey_ProfileRecipientId";
@interface OWSUserProfile () @interface OWSUserProfile ()
@end @end
@ -69,21 +65,21 @@ NSString *const kNSNotificationKey_ProfileRecipientId = @"kNSNotificationKey_Pro
[[NSFileManager defaultManager] removeItemAtPath:[self profileAvatarsDirPath] error:&error]; [[NSFileManager defaultManager] removeItemAtPath:[self profileAvatarsDirPath] error:&error];
} }
+ (NSSet<NSString *> *)allProfileAvatarFilePaths //+ (NSSet<NSString *> *)allProfileAvatarFilePaths
{ //{
NSString *profileAvatarsDirPath = self.profileAvatarsDirPath; // NSString *profileAvatarsDirPath = self.profileAvatarsDirPath;
NSMutableSet<NSString *> *profileAvatarFilePaths = [NSMutableSet new]; // NSMutableSet<NSString *> *profileAvatarFilePaths = [NSMutableSet new];
//
NSSet<SNContact *> *allContacts = [LKStorage.shared getAllContacts]; // NSSet<SNContact *> *allContacts = [LKStorage.shared getAllContacts];
//
for (SNContact *contact in allContacts) { // for (SNContact *contact in allContacts) {
if (contact.profilePictureFileName == nil) { continue; } // if (contact.profilePictureFileName == nil) { continue; }
NSString *filePath = [profileAvatarsDirPath stringByAppendingPathComponent:contact.profilePictureFileName]; // NSString *filePath = [profileAvatarsDirPath stringByAppendingPathComponent:contact.profilePictureFileName];
[profileAvatarFilePaths addObject:filePath]; // [profileAvatarFilePaths addObject:filePath];
} // }
//
return [profileAvatarFilePaths copy]; // return [profileAvatarFilePaths copy];
} //}
@end @end

View file

@ -1,30 +1,30 @@
////
//// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
////
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. //@class OWSAES256Key;
//@class TSThread;
//@class YapDatabaseReadWriteTransaction;
//@class SNContact;
// //
//NS_ASSUME_NONNULL_BEGIN
@class OWSAES256Key; //
@class TSThread; //@protocol ProfileManagerProtocol <NSObject>
@class YapDatabaseReadWriteTransaction; //
@class SNContact; //#pragma mark - Local Profile
//
NS_ASSUME_NONNULL_BEGIN //- (void)updateServiceWithProfileName:(nullable NSString *)localProfileName avatarURL:(nullable NSString *)avatarURL;
//
@protocol ProfileManagerProtocol <NSObject> //#pragma mark - Other User's Profiles
//
#pragma mark - Local Profile //- (nullable NSData *)profileKeyDataForRecipientId:(NSString *)recipientId;
//- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId;
- (void)updateServiceWithProfileName:(nullable NSString *)localProfileName avatarURL:(nullable NSString *)avatarURL; //- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId avatarURL:(nullable NSString *)avatarURL;
//
#pragma mark - Other User's Profiles //#pragma mark - Other
//
- (nullable NSData *)profileKeyDataForRecipientId:(NSString *)recipientId; //- (void)downloadAvatarForUserProfile:(SNContact *)userProfile;
- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId; //
- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId avatarURL:(nullable NSString *)avatarURL; //@end
//
#pragma mark - Other //NS_ASSUME_NONNULL_END
- (void)downloadAvatarForUserProfile:(SNContact *)userProfile;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,12 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import Sodium
extension Box.KeyPair: Equatable {
public static func == (lhs: Box.KeyPair, rhs: Box.KeyPair) -> Bool {
return (
lhs.publicKey == rhs.publicKey &&
lhs.secretKey == rhs.secretKey
)
}
}

View file

@ -178,12 +178,16 @@ public class FullTextSearchFinder: NSObject {
} }
private static let recipientIndexer: SearchIndexer<String> = SearchIndexer { (recipientId: String, transaction: YapDatabaseReadTransaction) in private static let recipientIndexer: SearchIndexer<String> = SearchIndexer { (recipientId: String, transaction: YapDatabaseReadTransaction) in
var result = "\(recipientId)" let profile: Profile? = GRDBStorage.shared.read { db in try Profile.fetchOne(db, id: recipientId) }
if let contact = Storage.shared.getContact(with: recipientId) {
if let name = contact.name { result += " \(name)" } return [
if let nickname = contact.nickname { result += " \(nickname)" } recipientId,
} profile?.name,
return result profile?.nickname
]
.compactMap { $0 }
.filter { !$0.isEmpty }
.joined(separator: " ")
} }
private static let messageIndexer: SearchIndexer<TSMessage> = SearchIndexer { (message: TSMessage, transaction: YapDatabaseReadTransaction) in private static let messageIndexer: SearchIndexer<TSMessage> = SearchIndexer { (message: TSMessage, transaction: YapDatabaseReadTransaction) in

View file

@ -0,0 +1,12 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import SignalCoreKit
public extension OWSAES256Key {
convenience init?(data: Data?) {
guard let existingData: Data = data else { return nil }
self.init(data: existingData)
}
}

View file

@ -1,9 +1,8 @@
// // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation import Foundation
import AVFoundation import AVFoundation
import SignalCoreKit
@objc(OWSAudioActivity) @objc(OWSAudioActivity)
public class AudioActivity: NSObject { public class AudioActivity: NSObject {

View file

@ -1,3 +1,8 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import SignalCoreKit
extension OWSSound { extension OWSSound {
public func notificationSound(isQuiet: Bool) -> UNNotificationSound { public func notificationSound(isQuiet: Bool) -> UNNotificationSound {

View file

@ -0,0 +1,351 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import GRDB
import PromiseKit
import SignalCoreKit
import SessionUtilitiesKit
public struct ProfileManager {
public enum Error: LocalizedError {
case avatarImageTooLarge
case avatarWriteFailed
case avatarEncryptionFailed
case avatarUploadFailed
case avatarUploadMaxFileSizeExceeded
var localizedDescription: String {
switch self {
case .avatarImageTooLarge: return "Avatar image too large."
case .avatarWriteFailed: return "Avatar write failed."
case .avatarEncryptionFailed: return "Avatar encryption failed."
case .avatarUploadFailed: return "Avatar upload failed."
case .avatarUploadMaxFileSizeExceeded: return "Maximum file size exceeded."
}
}
}
// The max bytes for a user's profile name, encoded in UTF8.
// Before encrypting and submitting we NULL pad the name data to this length.
private static let nameDataLength: UInt = 26
public static let maxAvatarDiameter: CGFloat = 640
private static var profileAvatarCache: Atomic<[String: UIImage]> = Atomic([:])
private static var currentAvatarDownloads: Atomic<Set<String>> = Atomic([])
// MARK: - Functions
public static func isToLong(profileName: String) -> Bool {
return ((profileName.data(using: .utf8)?.count ?? 0) > nameDataLength)
}
public static func profileAvatar(for id: String) -> UIImage? {
guard let profile: Profile = GRDBStorage.shared.read({ db in try Profile.fetchOne(db, id: id) }) else {
return nil
}
if let profileFileName: String = profile.profilePictureFileName, !profileFileName.isEmpty {
return loadProfileAvatar(for: profileFileName)
}
if let profilePictureUrl: String = profile.profilePictureUrl, !profilePictureUrl.isEmpty {
downloadAvatar(for: profile)
}
return nil
}
private static func loadProfileAvatar(for fileName: String) -> UIImage? {
if let cachedImage: UIImage = profileAvatarCache.wrappedValue[fileName] {
return cachedImage
}
guard
!fileName.isEmpty,
let data: Data = loadProfileData(with: fileName),
data.isValidImage,
let image: UIImage = UIImage(data: data)
else {
return nil
}
profileAvatarCache.mutate { $0[fileName] = image }
return image
}
private static func loadProfileData(with fileName: String) -> Data? {
let filePath: String = OWSUserProfile.profileAvatarFilepath(withFilename: fileName)
return try? Data(contentsOf: URL(fileURLWithPath: filePath))
}
// MARK: - Profile Encryption
private static func encryptProfileData(data: Data, key: OWSAES256Key) -> Data? {
guard key.keyData.count == kAES256_KeyByteLength else { return nil }
return Cryptography.encryptAESGCMProfileData(plainTextData: data, key: key)
}
private static func decryptProfileData(data: Data, key: OWSAES256Key) -> Data? {
guard key.keyData.count == kAES256_KeyByteLength else { return nil }
return Cryptography.decryptAESGCMProfileData(encryptedData: data, key: key)
}
// MARK: - Other Users' Profiles
public static func downloadAvatar(for profile: Profile, funcName: String = #function) {
guard !currentAvatarDownloads.wrappedValue.contains(profile.id) else {
// Download already in flight; ignore
return
}
guard
let profileUrlStringAtStart: String = profile.profilePictureUrl,
let profileUrlAtStart: URL = URL(string: profileUrlStringAtStart)
else {
SNLog("Skipping downloading avatar for \(profile.id) because url is not set")
return
}
guard
let fileId: UInt64 = UInt64(profileUrlAtStart.lastPathComponent),
let profileKeyAtStart: OWSAES256Key = profile.profileEncryptionKey,
profileKeyAtStart.keyData.count > 0
else {
return
}
let fileName: String = UUID().uuidString.appendingFileExtension("jpg")
let filePath: String = OWSUserProfile.profileAvatarFilepath(withFilename: fileName)
var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: funcName)
DispatchQueue.global(qos: .default).async {
OWSLogger.verbose("downloading profile avatar: \(profile.id)")
currentAvatarDownloads.mutate { $0.insert(profile.id) }
let useOldServer: Bool = (profileUrlStringAtStart.contains(FileServerAPIV2.oldServer))
FileServerAPIV2
.download(fileId, useOldServer: useOldServer)
.done { data in
currentAvatarDownloads.mutate { $0.remove(profile.id) }
GRDBStorage.shared.write { db in
guard let latestProfile: Profile = try Profile.fetchOne(db, id: profile.id) else {
return
}
guard
let latestProfileKey: OWSAES256Key = latestProfile.profileEncryptionKey,
!latestProfileKey.keyData.isEmpty,
latestProfileKey == profileKeyAtStart
else {
OWSLogger.warn("Ignoring avatar download for obsolete user profile.")
return
}
guard profileUrlStringAtStart == latestProfile.profilePictureUrl else {
OWSLogger.warn("Avatar url has changed during download.")
if latestProfile.profilePictureUrl?.isEmpty == false {
self.downloadAvatar(for: latestProfile)
}
return
}
guard let decryptedData: Data = decryptProfileData(data: data, key: profileKeyAtStart) else {
OWSLogger.warn("Avatar data for \(profile.id) could not be decrypted.")
return
}
try? decryptedData.write(to: URL(fileURLWithPath: filePath), options: [.atomic])
guard let image: UIImage = UIImage(contentsOfFile: filePath) else {
OWSLogger.warn("Avatar image for \(profile.id) could not be loaded.")
return
}
try? latestProfile
.with(profilePictureFileName: .update(fileName))
.update(db)
profileAvatarCache.mutate { $0[fileName] = image }
}
// Redundant but without reading 'backgroundTask' it will warn that the variable
// isn't used
if backgroundTask != nil { backgroundTask = nil }
}
.retainUntilComplete()
}
}
// MARK: - Current User Profile
public static func updateLocal(
profileName: String,
avatarImage: UIImage?,
requiredSync: Bool,
success: (() -> ())? = nil,
failure: ((Error) -> ())? = nil
) {
DispatchQueue.global(qos: .default).async {
// If the profile avatar was updated or removed then encrypt with a new profile key
// to ensure that other users know that our profile picture was updated
let newProfileKey: OWSAES256Key = OWSAES256Key.generateRandom()
guard let avatarImage: UIImage = avatarImage else {
// If we have no image then we need to make sure to remove it from the profile
GRDBStorage.shared.writeAsync(
updates: { db in
let existingProfile: Profile = Profile.fetchOrCreateCurrentUser(db)
OWSLogger.verbose(existingProfile.profilePictureUrl != nil ?
"Updating local profile on service with cleared avatar." :
"Updating local profile on service with no avatar."
)
try? existingProfile
.with(
name: profileName,
profilePictureUrl: nil,
profilePictureFileName: nil,
profileEncryptionKey: (existingProfile.profilePictureUrl != nil ?
.update(newProfileKey) :
.existing
)
)
.save(db)
// Remove any cached avatar image value
if let fileName: String = existingProfile.profilePictureFileName {
profileAvatarCache.mutate { $0[fileName] = nil }
}
},
completion: { _, _ in
SNLog("Successfully updated service with profile.")
DispatchQueue.main.async {
success?()
}
}
)
return
}
// If we have a new avatar image, we must first:
//
// * Encode it to JPEG.
// * Write it to disk.
// * Encrypt it
// * Upload it to asset service
// * Send asset service info to Signal Service
OWSLogger.verbose("Updating local profile on service with new avatar.")
let maxAvatarBytes: UInt = (5 * 1000 * 1000)
var image: UIImage = avatarImage
if image.size.width != maxAvatarDiameter || image.size.height != maxAvatarDiameter {
// To help ensure the user is being shown the same cropping of their avatar as
// everyone else will see, we want to be sure that the image was resized before this point.
SNLog("Avatar image should have been resized before trying to upload")
image = image.resizedImage(toFillPixelSize: CGSize(width: maxAvatarDiameter, height: maxAvatarDiameter))
}
guard let data: Data = image.jpegData(compressionQuality: 0.95) else {
DispatchQueue.main.async {
SNLog("Updating service with profile failed.")
failure?(.avatarWriteFailed)
}
return
}
guard data.count <= maxAvatarBytes else {
// Our avatar dimensions are so small that it's incredibly unlikely we wouldn't
// be able to fit our profile photo (eg. generating pure noise at our resolution
// compresses to ~200k)
DispatchQueue.main.async {
SNLog("Suprised to find profile avatar was too large. Was it scaled properly? image: \(image)")
SNLog("Updating service with profile failed.")
failure?(.avatarImageTooLarge)
}
return
}
let fileName: String = UUID().uuidString.appendingFileExtension("jpg")
let filePath: String = OWSUserProfile.profileAvatarFilepath(withFilename: fileName)
// Write the avatar to disk
do { try data.write(to: URL(fileURLWithPath: filePath), options: [.atomic]) }
catch {
DispatchQueue.main.async {
SNLog("Updating service with profile failed.")
failure?(.avatarWriteFailed)
}
return
}
// Encrypt the avatar for upload
guard let encryptedAvatarData: Data = encryptProfileData(data: data, key: newProfileKey) else {
DispatchQueue.main.async {
SNLog("Updating service with profile failed.")
failure?(.avatarEncryptionFailed)
}
return
}
// Upload the avatar to the FileServer
FileServerAPIV2
.upload(encryptedAvatarData)
.done { fileId in
let downloadUrl: String = "\(FileServerAPIV2.server)/files/\(fileId)"
UserDefaults.standard[.lastProfilePictureUpload] = Date()
GRDBStorage.shared.writeAsync(
updates: { db in
try? Profile
.fetchOrCreateCurrentUser(db)
.with(
name: profileName,
profilePictureUrl: .update(downloadUrl),
profilePictureFileName: .update(fileName),
profileEncryptionKey: .update(newProfileKey)
)
.save(db)
},
completion: { _, _ in
// Update the cached avatar image value
profileAvatarCache.mutate { $0[fileName] = avatarImage }
DispatchQueue.main.async {
SNLog("Successfully updated service with profile.")
success?()
}
}
)
}
.recover { error in
DispatchQueue.main.async {
SNLog("Updating service with profile failed.")
let isMaxFileSizeExceeded: Bool = ((error as? FileServerAPIV2.Error) == FileServerAPIV2.Error.maxFileSizeExceeded)
failure?(isMaxFileSizeExceeded ?
.avatarUploadMaxFileSizeExceeded :
.avatarUploadFailed
)
}
}
.retainUntilComplete()
}
}
}
// MARK: - Objective-C Support
@objc(SMKProfileManager)
public class SMKProfileManager: NSObject {
@objc public static func profileAvatar(recipientId: String) -> UIImage? {
return ProfileManager.profileAvatar(for: recipientId)
}
@objc public static func updateLocal(profileName: String, avatarImage: UIImage?, requiresSync: Bool) {
ProfileManager.updateLocal(profileName: profileName, avatarImage: avatarImage, requiredSync: requiresSync)
}
}

Some files were not shown because too many files have changed in this diff Show more