mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
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:
parent
0f4df804ed
commit
cf66edb723
138 changed files with 4255 additions and 2397 deletions
|
@ -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 */,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]] &&
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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?
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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() } }
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
22
SessionMessagingKit/Database/Models/Capability.swift
Normal file
22
SessionMessagingKit/Database/Models/Capability.swift
Normal 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
|
||||||
|
}
|
48
SessionMessagingKit/Database/Models/ClosedGroup.swift
Normal file
48
SessionMessagingKit/Database/Models/ClosedGroup.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
22
SessionMessagingKit/Database/Models/ClosedGroupKeyPair.swift
Normal file
22
SessionMessagingKit/Database/Models/ClosedGroupKeyPair.swift
Normal 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
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
27
SessionMessagingKit/Database/Models/GroupMember.swift
Normal file
27
SessionMessagingKit/Database/Models/GroupMember.swift
Normal 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
|
||||||
|
}
|
50
SessionMessagingKit/Database/Models/OpenGroup.swift
Normal file
50
SessionMessagingKit/Database/Models/OpenGroup.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
59
SessionMessagingKit/Database/Models/SessionThread.swift
Normal file
59
SessionMessagingKit/Database/Models/SessionThread.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
// }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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? {
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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")
|
||||||
)
|
// )
|
||||||
"""
|
// """
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ()
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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).")
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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] {
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
12
SessionMessagingKit/Utilities/BoxKeyPair+Utilities.swift
Normal file
12
SessionMessagingKit/Utilities/BoxKeyPair+Utilities.swift
Normal 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
12
SessionMessagingKit/Utilities/OWSAES256Key+Utilities.swift
Normal file
12
SessionMessagingKit/Utilities/OWSAES256Key+Utilities.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
351
SessionMessagingKit/Utilities/ProfileManager.swift
Normal file
351
SessionMessagingKit/Utilities/ProfileManager.swift
Normal 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
Loading…
Reference in a new issue